From 00ac342239bef4567a1478c6949224d8acac11ca Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Thu, 4 Jun 2026 19:11:05 +0545 Subject: [PATCH 01/11] add: ldap, keycloak oidc setup & sample configurations --- .gitignore | 9 ++ docker-compose.yml | 1 + docker/.env.auth.example | 33 +++++ docker/docker-compose.auth.yml | 95 +++++++++++++++ docker/keycloak/realm-mapstore.sample.json | 80 ++++++++++++ docker/mapstore.auth.conf | 115 ++++++++++++++++++ docker/openldap/Dockerfile | 4 + docker/openldap/ldif.sample/02-users.ldif | 29 +++++ .../ldif/01-organizational-units.ldif | 7 ++ docker/openldap/ldif/03-groups.ldif | 16 +++ .../configs/localConfig.json.patch | 17 +++ .../geostore-datasource-ovr.properties | 10 ++ docker/sample-datadir/ldap.properties | 27 ++++ docker/sample-datadir/mapstore-ovr.properties | 13 ++ 14 files changed, 456 insertions(+) create mode 100644 docker/.env.auth.example create mode 100644 docker/docker-compose.auth.yml create mode 100644 docker/keycloak/realm-mapstore.sample.json create mode 100644 docker/mapstore.auth.conf create mode 100644 docker/openldap/Dockerfile create mode 100644 docker/openldap/ldif.sample/02-users.ldif create mode 100644 docker/openldap/ldif/01-organizational-units.ldif create mode 100644 docker/openldap/ldif/03-groups.ldif create mode 100644 docker/sample-datadir/configs/localConfig.json.patch create mode 100644 docker/sample-datadir/geostore-datasource-ovr.properties create mode 100644 docker/sample-datadir/ldap.properties create mode 100644 docker/sample-datadir/mapstore-ovr.properties diff --git a/.gitignore b/.gitignore index 26f506f728..130bd6a923 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,12 @@ GEMINI.md AGENTS.md .copilot/ .github/copilot-instructions.md +mapstore.war +.env + +# Local auth overrides and runtime bootstrap files +/docker/keycloak/realm-mapstore.json +/docker/openldap/ldif/02-users.ldif +# datadir runtime files +datadir/ +datadir/* diff --git a/docker-compose.yml b/docker-compose.yml index 8b2cccdd90..95d4e5fe28 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: interval: 5s timeout: 10s retries: 120 + platform: linux/amd64 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres diff --git a/docker/.env.auth.example b/docker/.env.auth.example new file mode 100644 index 0000000000..7c3b914e8b --- /dev/null +++ b/docker/.env.auth.example @@ -0,0 +1,33 @@ +# Copy to .env and adjust if needed. +# The variables below are SAMPLE values for local development only. +# Do NOT use these credentials in production. +# The auth compose overlay provides sample defaults, but for any real deployment +# you should create a `.env` file with secure values and override defaults. +# NOTE: `datadir/ldap.properties` must use the same bind password as `LDAP_READONLY_PASSWORD`. + +# LDAP sample values +LDAP_ORGANISATION=Acme +LDAP_DOMAIN=acme.org +LDAP_ADMIN_PASSWORD=admin +LDAP_CONFIG_PASSWORD=config +LDAP_READONLY_USER=true +LDAP_READONLY_USER_USERNAME=readonly +LDAP_READONLY_PASSWORD=readonly +LDAP_TLS=false + +# Keycloak dev admin (admin console at http://localhost/keycloak/admin/) +KEYCLOAK_ADMIN=admin +KEYCLOAK_ADMIN_PASSWORD=admin +KEYCLOAK_HTTP_RELATIVE_PATH=/keycloak +KEYCLOAK_HOSTNAME=http://localhost/keycloak +KEYCLOAK_HOSTNAME_STRICT=false +KEYCLOAK_PROXY_HEADERS=xforwarded +KEYCLOAK_REALM_FILE=realm-mapstore.json +# The auth compose expects a realm file named `realm-mapstore.json` in `docker/keycloak/` by default. +# To use the shipped sample, copy `docker/keycloak/realm-mapstore.sample.json` to +# `docker/keycloak/realm-mapstore.json` or set `KEYCLOAK_REALM_FILE` to the sample filename. + +# If you need to change the Keycloak image or version for the auth stack, +# set a value like: KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:26.0.7 +# (the compose auth fragment falls back to the embedded image value if unset) +#KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:26.0.7 diff --git a/docker/docker-compose.auth.yml b/docker/docker-compose.auth.yml new file mode 100644 index 0000000000..0ef33ee305 --- /dev/null +++ b/docker/docker-compose.auth.yml @@ -0,0 +1,95 @@ +version: "3.8" +networks: + mapstore-network: + driver: bridge + +services: + ldap: + build: + context: ./docker/openldap/ + image: geosolutions-mapstore/openldap + container_name: ldap + restart: on-failure + environment: + LDAP_ORGANISATION: "${LDAP_ORGANISATION:-Acme}" + LDAP_DOMAIN: "${LDAP_DOMAIN:-acme.org}" + LDAP_ADMIN_PASSWORD: "${LDAP_ADMIN_PASSWORD:-admin}" + LDAP_CONFIG_PASSWORD: "${LDAP_CONFIG_PASSWORD:-config}" + LDAP_READONLY_USER: "true" + LDAP_READONLY_USER_USERNAME: "${LDAP_READONLY_USER_USERNAME:-readonly}" + LDAP_READONLY_USER_PASSWORD: "${LDAP_READONLY_PASSWORD:-readonly}" + LDAP_TLS: "${LDAP_TLS:-false}" + volumes: + - ldap_data:/var/lib/ldap + - ldap_config:/etc/ldap/slapd.d + healthcheck: + test: ["CMD-SHELL", "ldapsearch -x -H ldap://localhost -D cn=readonly,dc=acme,dc=org -w \"$LDAP_READONLY_PASSWORD\" -b ou=people,dc=acme,dc=org -s one \"(uid=msadmin)\""] + interval: 5s + timeout: 5s + retries: 30 + start_period: 15s + networks: + - mapstore-network + + keycloak: + image: quay.io/keycloak/keycloak:26.0.7 + container_name: keycloak + restart: on-failure + environment: + KC_BOOTSTRAP_ADMIN_USERNAME: "${KEYCLOAK_ADMIN:-admin}" + KC_BOOTSTRAP_ADMIN_PASSWORD: "${KEYCLOAK_ADMIN_PASSWORD:-admin}" + KC_HTTP_ENABLED: "true" + KC_HTTP_RELATIVE_PATH: "${KEYCLOAK_HTTP_RELATIVE_PATH:-/keycloak}" + KC_HOSTNAME: "${KEYCLOAK_HOSTNAME:-http://localhost/keycloak}" + KC_HOSTNAME_STRICT: "${KEYCLOAK_HOSTNAME_STRICT:-false}" + KC_PROXY_HEADERS: "${KEYCLOAK_PROXY_HEADERS:-xforwarded}" + KC_HEALTH_ENABLED: "true" + KC_HTTP_MANAGEMENT_RELATIVE_PATH: / + command: start-dev --import-realm + volumes: + - ./docker/keycloak/realm-mapstore.json:/opt/keycloak/data/import/realm-mapstore.json:ro + - keycloak_data:/opt/keycloak/data + healthcheck: + test: ["CMD-SHELL", "{ printf 'HEAD /health/ready HTTP/1.0\\r\\n\\r\\n' >&0; grep 'HTTP/1.0 200'; } 0<>/dev/tcp/127.0.0.1/9000"] + interval: 5s + timeout: 3s + retries: 12 + start_period: 30s + networks: + - mapstore-network + + proxy: + image: nginx + container_name: proxy + volumes: + - ./docker/mapstore.auth.conf:/etc/nginx/conf.d/default.conf:rw + ports: + - 80:80 + depends_on: + - mapstore + - keycloak + networks: + - mapstore-network + + mapstore: + build: + context: . + dockerfile: Dockerfile + args: + MAPSTORE_WEBAPP_SRC: "mapstore.war" + depends_on: + ldap: + condition: service_healthy + keycloak: + condition: service_healthy + extra_hosts: + - "localhost:host-gateway" + volumes: + - "${DATADIR_PATH:-./datadir}:/usr/local/tomcat/datadir:ro" + networks: + - mapstore-network + +volumes: + ldap_data: + ldap_config: + keycloak_data: diff --git a/docker/keycloak/realm-mapstore.sample.json b/docker/keycloak/realm-mapstore.sample.json new file mode 100644 index 0000000000..60490e73ae --- /dev/null +++ b/docker/keycloak/realm-mapstore.sample.json @@ -0,0 +1,80 @@ +{ + "realm": "mapstore", + "enabled": true, + "registrationAllowed": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "sslRequired": "external", + "roles": { + "realm": [ + { + "name": "admin", + "description": "Mapped to MapStore ADMIN (see mapstore-ovr.properties roleMappings)" + }, + { + "name": "user", + "description": "Optional; default MapStore role is USER if unmapped" + } + ] + }, + "users": [ + { + "username": "kcuser", + "enabled": true, + "emailVerified": true, + "firstName": "Keycloak", + "lastName": "User", + "email": "kcuser@acme.org", + "realmRoles": ["user"], + "credentials": [ + { + "type": "password", + "value": "kcuser123", + "temporary": false + } + ] + }, + { + "username": "kcadmin", + "enabled": true, + "emailVerified": true, + "firstName": "Keycloak", + "lastName": "Admin", + "email": "kcadmin@acme.org", + "realmRoles": ["admin"], + "credentials": [ + { + "type": "password", + "value": "kcadmin123", + "temporary": false + } + ] + } + ], + "clients": [ + { + "clientId": "mapstore-server", + "name": "MapStore OpenID (server)", + "enabled": true, + "protocol": "openid-connect", + "publicClient": false, + "clientAuthenticatorType": "client-secret", + "secret": "mapstore-server-secret", + "standardFlowEnabled": true, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "redirectUris": [ + "http://localhost/mapstore/*" + ], + "webOrigins": [ + "http://localhost", + "+" + ], + "attributes": { + "post.logout.redirect.uris": "http://localhost/mapstore/*" + } + } + ] +} diff --git a/docker/mapstore.auth.conf b/docker/mapstore.auth.conf new file mode 100644 index 0000000000..6791f6042d --- /dev/null +++ b/docker/mapstore.auth.conf @@ -0,0 +1,115 @@ +server { + server_name localhost; + + listen 80; + listen [::]:80; + + server_tokens off; + + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_disable "msie6"; + + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types + text/plain + text/css + application/json + application/javascript + application/x-javascript + text/xml application/xm + application/xml+rss + text/javascript; + + underscores_in_headers on; + proxy_set_header HOST $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_buffering on; + proxy_buffer_size 1k; + proxy_buffers 24 4k; + proxy_busy_buffers_size 8k; + proxy_max_temp_file_size 2048m; + proxy_temp_file_write_size 32k; + + location / { + deny all; + return 301 /mapstore; + } + + # Keycloak (same host as MapStore — required for OIDC redirects and MapStore backend) + location /keycloak/ { + proxy_pass http://keycloak:8080/keycloak/; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Prefix /keycloak; + + # Keycloak auth responses set large Set-Cookie headers; default 1k buffers cause 502. + proxy_buffer_size 128k; + proxy_buffers 8 256k; + proxy_busy_buffers_size 256k; + proxy_temp_file_write_size 256k; + } + + location /mapstore { + proxy_pass http://mapstore:8080/mapstore; + + # OIDC callback returns 302 + Set-Cookie; large request Cookie headers are common too. + proxy_buffer_size 128k; + proxy_buffers 8 256k; + proxy_busy_buffers_size 256k; + proxy_temp_file_write_size 256k; + + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '$http_origin'; + add_header 'Access-Control-Allow-Credentials' 'true'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + if ($request_method = 'DELETE') { + add_header 'Access-Control-Allow-Origin' '$http_origin'; + add_header 'Access-Control-Allow-Credentials' 'true'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; + } + if ($request_method = 'PUT') { + add_header 'Access-Control-Allow-Origin' '$http_origin'; + add_header 'Access-Control-Allow-Credentials' 'true'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; + } + if ($request_method = 'POST') { + add_header 'Access-Control-Allow-Origin' '$http_origin'; + add_header 'Access-Control-Allow-Credentials' 'true'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; + } + if ($request_method = 'GET') { + add_header 'Access-Control-Allow-Origin' '$http_origin'; + add_header 'Access-Control-Allow-Credentials' 'true'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; + } + } +} diff --git a/docker/openldap/Dockerfile b/docker/openldap/Dockerfile new file mode 100644 index 0000000000..9cbee98558 --- /dev/null +++ b/docker/openldap/Dockerfile @@ -0,0 +1,4 @@ +FROM osixia/openldap:1.5.0 + +# Bootstrap LDIF is baked into the image (runs once when ldap_data volume is empty). +COPY ldif/ /container/service/slapd/assets/config/bootstrap/ldif/custom/ diff --git a/docker/openldap/ldif.sample/02-users.ldif b/docker/openldap/ldif.sample/02-users.ldif new file mode 100644 index 0000000000..728fdc41b7 --- /dev/null +++ b/docker/openldap/ldif.sample/02-users.ldif @@ -0,0 +1,29 @@ +dn: uid=msadmin,ou=people,dc=acme,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +uid: msadmin +sn: Admin +cn: MapStore Admin +mail: msadmin@acme.org +userPassword: admin123 + +dn: uid=msuser,ou=people,dc=acme,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +uid: msuser +sn: User +cn: MapStore User +mail: msuser@acme.org +userPassword: user123 + +dn: uid=msdev,ou=people,dc=acme,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +uid: msdev +sn: Developer +cn: MapStore Developer +mail: msdev@acme.org +userPassword: dev123 diff --git a/docker/openldap/ldif/01-organizational-units.ldif b/docker/openldap/ldif/01-organizational-units.ldif new file mode 100644 index 0000000000..d4b9cb1701 --- /dev/null +++ b/docker/openldap/ldif/01-organizational-units.ldif @@ -0,0 +1,7 @@ +dn: ou=people,dc=acme,dc=org +objectClass: organizationalUnit +ou: people + +dn: ou=groups,dc=acme,dc=org +objectClass: organizationalUnit +ou: groups diff --git a/docker/openldap/ldif/03-groups.ldif b/docker/openldap/ldif/03-groups.ldif new file mode 100644 index 0000000000..6a37d97934 --- /dev/null +++ b/docker/openldap/ldif/03-groups.ldif @@ -0,0 +1,16 @@ +dn: cn=ROLE_ADMIN,ou=groups,dc=acme,dc=org +objectClass: groupOfNames +cn: ROLE_ADMIN +member: uid=msadmin,ou=people,dc=acme,dc=org + +dn: cn=mapstore-users,ou=groups,dc=acme,dc=org +objectClass: groupOfNames +cn: mapstore-users +member: uid=msadmin,ou=people,dc=acme,dc=org +member: uid=msuser,ou=people,dc=acme,dc=org +member: uid=msdev,ou=people,dc=acme,dc=org + +dn: cn=mapstore-devs,ou=groups,dc=acme,dc=org +objectClass: groupOfNames +cn: mapstore-devs +member: uid=msdev,ou=people,dc=acme,dc=org diff --git a/docker/sample-datadir/configs/localConfig.json.patch b/docker/sample-datadir/configs/localConfig.json.patch new file mode 100644 index 0000000000..b82ce46682 --- /dev/null +++ b/docker/sample-datadir/configs/localConfig.json.patch @@ -0,0 +1,17 @@ +[ + { + "op": "add", + "path": "/authenticationProviders", + "value": [ + { + "type": "openID", + "provider": "keycloak", + "title": "Keycloak" + }, + { + "type": "basic", + "provider": "geostore" + } + ] + } +] diff --git a/docker/sample-datadir/geostore-datasource-ovr.properties b/docker/sample-datadir/geostore-datasource-ovr.properties new file mode 100644 index 0000000000..8ac26db202 --- /dev/null +++ b/docker/sample-datadir/geostore-datasource-ovr.properties @@ -0,0 +1,10 @@ +# Postgres connection for GeoStore (externalized via datadir) +geostoreDataSource.driverClassName=org.postgresql.Driver +geostoreDataSource.url=jdbc:postgresql://postgres:5432/geostore +geostoreDataSource.username=geostore +geostoreDataSource.password=geostore +geostoreVendorAdapter.databasePlatform=org.hibernate.dialect.PostgreSQLDialect +geostoreEntityManagerFactory.jpaPropertyMap[hibernate.hbm2ddl.auto]=update +geostoreEntityManagerFactory.jpaPropertyMap[hibernate.default_schema]=geostore +geostoreVendorAdapter.generateDdl=true +geostoreVendorAdapter.showSql=false diff --git a/docker/sample-datadir/ldap.properties b/docker/sample-datadir/ldap.properties new file mode 100644 index 0000000000..347a8c6cd8 --- /dev/null +++ b/docker/sample-datadir/ldap.properties @@ -0,0 +1,27 @@ +## MapStore LDAP — OpenLDAP (docker service: ldap) +## https://docs.mapstore.geosolutionsgroup.com/en/latest/developer-guide/integrations/users/ldap/ +## +## Passwords must match .env / docker-compose (LDAP_READONLY_PASSWORD). + +ldap.host=ldap +ldap.port=389 +ldap.root=dc=acme,dc=org +ldap.userDn=cn=readonly,dc=acme,dc=org +ldap.password=readonly + +ldap.userBase=ou=people +ldap.groupBase=ou=groups +ldap.roleBase=ou=groups +ldap.userFilter=(uid={0}) +ldap.groupFilter=(member={0}) +ldap.roleFilter=(member={0}) + +ldap.hierachicalGroups=false +ldap.nestedGroupFilter=(member={0}) +ldap.nestedGroupLevels=3 +ldap.searchSubtree=true +ldap.convertToUpperCase=true + +# GeoStore: groups named ROLE_* are application roles +ldap.rolePrefix=ROLE_ +ldap.adminRole=ROLE_ADMIN diff --git a/docker/sample-datadir/mapstore-ovr.properties b/docker/sample-datadir/mapstore-ovr.properties new file mode 100644 index 0000000000..4c746e6c81 --- /dev/null +++ b/docker/sample-datadir/mapstore-ovr.properties @@ -0,0 +1,13 @@ +# Keycloak OpenID — https://docs.mapstore.geosolutionsgroup.com/en/latest/developer-guide/integrations/users/openId/#keycloak +# Browser and redirects use http://localhost (nginx on :80). +# MapStore container reaches the same URLs via extra_hosts localhost:host-gateway. + +keycloakOAuth2Config.enabled=true +keycloakOAuth2Config.jsonConfig={"realm":"mapstore","auth-server-url":"http://localhost/keycloak/","ssl-required":"external","resource":"mapstore-server","credentials":{"secret":"mapstore-server-secret"}} +keycloakOAuth2Config.redirectUri=http://localhost/mapstore/rest/geostore/openid/keycloak/callback +keycloakOAuth2Config.internalRedirectUri=http://localhost/mapstore/ +keycloakOAuth2Config.autoCreateUser=true +keycloakOAuth2Config.forceConfiguredRedirectURI=true +keycloakOAuth2Config.authenticatedDefaultRole=USER +# Keycloak realm role name -> MapStore role (USER or ADMIN) +keycloakOAuth2Config.roleMappings=admin:ADMIN From 74d2beef4682a4b02a7a2e24cfb3391900aef07a Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Thu, 4 Jun 2026 20:31:42 +0545 Subject: [PATCH 02/11] docs: add auth-docker-setup for configuring ldap/keycloak --- .../integrations/auth-docker-setup.md | 182 ++++++++++++++++++ .../integrations/infrastructure-setups.md | 13 ++ mkdocs.yml | 1 + 3 files changed, 196 insertions(+) create mode 100644 docs/developer-guide/integrations/auth-docker-setup.md diff --git a/docs/developer-guide/integrations/auth-docker-setup.md b/docs/developer-guide/integrations/auth-docker-setup.md new file mode 100644 index 0000000000..f5901e1f82 --- /dev/null +++ b/docs/developer-guide/integrations/auth-docker-setup.md @@ -0,0 +1,182 @@ +# Auth Docker Setup + +This page describes a quick local authentication environment for MapStore developers and environment maintainers. It extends the default Docker setup with Keycloak, OpenLDAP and an authentication-aware Nginx proxy, so you can quickly test MapStore login flows and sample user/group mappings. + +The stack is intended as a starting point. For the full configuration details, see the dedicated guides for [LDAP](users/ldap.md#ldap-integration-with-mapstore), [OpenID Connect](users/openId.md#integration-with-openid-connect), [Keycloak](users/keycloak.md#keycloak-integrations), [MapStore authentication](auth.md#mapstore-authentication---implementation-details) and the available [infrastructure setups](infrastructure-setups.md#possible-setups). + +!!! warning + The provided files contain sample credentials, sample users, a sample Keycloak realm and sample LDAP entries. They are useful for local development and testing, but they must be replaced or reviewed before using this setup in production. + +## Services + +The auth Docker setup includes: + +- **MapStore**: the application container, configured with a mounted datadir. +- **PostgreSQL/PostGIS**: the GeoStore database from the base `docker-compose.yml`. +- **Nginx**: the reverse proxy exposed on `http://localhost`. +- **Keycloak**: an OpenID Connect identity provider exposed at `http://localhost/keycloak`. +- **OpenLDAP**: a sample LDAP directory used to test LDAP users, groups and roles. + +The proxy exposes the applications on the same host: + +- MapStore: [http://localhost/mapstore](http://localhost/mapstore) +- Keycloak: [http://localhost/keycloak/](http://localhost/keycloak/) +- Keycloak admin console: [http://localhost/keycloak/admin/](http://localhost/keycloak/admin/) + +## Prerequisites + +Before starting the stack, make sure you have: + +- Docker and Docker Compose installed. +- A MapStore WAR available as `mapstore.war` in the repository root, or adjust the `MAPSTORE_WEBAPP_SRC` build argument in `docker/docker-compose.auth.yml`. +- A MapStore WAR built with the LDAP profile if you want to test LDAP username/password login. See [building and deploying](../building-and-deploying.md#building-and-deploying) and the [LDAP integration guide](users/ldap.md#building-mapstore-with-ldap-support). + +For Keycloak OpenID login only, the sample datadir enables the Keycloak provider through `mapstore-ovr.properties`. For LDAP login, the MapStore backend must also include the LDAP security configuration. + +## Prepare Environment Variables + +Copy the sample environment file to the repository root: + +```sh +cp docker/.env.auth.example .env +``` + +Review the values before starting the stack. The most relevant variables are: + +- `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD`: credentials for the Keycloak admin console. +- `KEYCLOAK_HOSTNAME`: public Keycloak URL used in redirects. The local default is `http://localhost/keycloak`. +- `LDAP_ADMIN_PASSWORD`, `LDAP_CONFIG_PASSWORD` and `LDAP_READONLY_PASSWORD`: OpenLDAP administrative and read-only bind credentials. +- `DATADIR_PATH`: optional path to the MapStore datadir mounted in the container. If omitted, `./datadir` is used. + +!!! warning + Do not reuse the sample passwords in shared, staging or production environments. Update `.env`, the Keycloak realm, LDIF files and MapStore datadir properties consistently. + +## Prepare The Datadir + +Create a local datadir from the provided sample: + +```sh +mkdir -p datadir +cp -R docker/sample-datadir/. datadir/ +``` + +The sample datadir contains: + +- `mapstore-ovr.properties`: enables Keycloak OpenID login and configures the `mapstore-server` client. +- `ldap.properties`: sample LDAP connection, search filters and role mapping properties. +- `geostore-datasource-ovr.properties`: sample PostgreSQL datasource configuration for GeoStore. +- `configs/localConfig.json.patch`: adds Keycloak as an authentication provider in the login UI. + +These files are regular MapStore externalized configuration files. See [configuration files](../configuration-files.md#configuration-files) and [externalized configuration](../externalized-configuration.md#externalized-configuration) for more details about datadir-based overrides. + +## Prepare Keycloak + +The auth stack imports a Keycloak realm at first startup. The realm defines the `mapstore` realm, sample users, roles and the confidential OpenID Connect client used by MapStore. + +Copy the sample realm into the filename expected by the compose file: + +```sh +cp docker/keycloak/realm-mapstore.sample.json docker/keycloak/realm-mapstore.json +``` + +The sample realm configures: + +- Realm: `mapstore` +- Client: `mapstore-server` +- Client secret: `mapstore-server-secret` +- Redirect URI: `http://localhost/mapstore/*` +- Realm roles: `admin`, `user` +- Sample users: `kcuser`, `kcadmin` + +The client and secret must match the values in `datadir/mapstore-ovr.properties`. If you change the realm file, update the MapStore datadir configuration accordingly. For details about Keycloak OpenID configuration, see the [Keycloak section of the OpenID Connect guide](users/openId.md#keycloak). + +### Keycloak Test Users + +| Username | Password | Email | Keycloak role | MapStore role | +| --- | --- | --- | --- | --- | +| `kcuser` | `kcuser123` | `kcuser@acme.org` | `user` | `USER` | +| `kcadmin` | `kcadmin123` | `kcadmin@acme.org` | `admin` | `ADMIN` | + +The Keycloak admin console is available at [http://localhost/keycloak/admin/](http://localhost/keycloak/admin/) with the `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` values configured in `.env`. The admin user belongs to the Keycloak `master` realm and is used to manage Keycloak, not to log in to MapStore. + +## Prepare OpenLDAP + +OpenLDAP is initialized from LDIF files in `docker/openldap/ldif/` when the LDAP data volume is empty. The repository keeps the sample users in `docker/openldap/ldif.sample/02-users.ldif` so you can review or customize them before importing. + +To use the provided test users, copy the sample users LDIF into the active bootstrap directory before the first startup: + +```sh +cp docker/openldap/ldif.sample/02-users.ldif docker/openldap/ldif/02-users.ldif +``` + +The LDAP bootstrap files define: + +- Organizational units, such as `ou=people` and `ou=groups`. +- Sample users, if `02-users.ldif` is copied into `docker/openldap/ldif/`. +- Groups and roles, including `ROLE_ADMIN`, `mapstore-users` and `mapstore-devs`. + +The LDIF files are copied into the OpenLDAP image at build time and imported only when the LDAP volumes are empty. If you edit LDIF files after the first startup, rebuild the LDAP image and remove the LDAP volumes before starting again. + +```sh +docker compose -f docker-compose.yml -f docker/docker-compose.auth.yml down +docker volume rm mapstore2_ldap_data mapstore2_ldap_config +docker compose -f docker-compose.yml -f docker/docker-compose.auth.yml up --build +``` + +!!! note + Docker Compose may use a different volume prefix depending on the project directory or `COMPOSE_PROJECT_NAME`. Run `docker volume ls` if the volume names differ. + +### LDAP Test Users + +| Login | Password | MapStore role | LDAP groups | +| --- | --- | --- | --- | +| `msadmin` | `admin123` | `ADMIN` | `ROLE_ADMIN`, `mapstore-users` | +| `msuser` | `user123` | `USER` | `mapstore-users` | +| `msdev` | `dev123` | `USER` | `mapstore-users`, `mapstore-devs` | + +Log in with the LDAP `uid`, for example `msadmin`, not the full DN. With `ldap.convertToUpperCase=true`, synchronized user and group names may appear uppercase in MapStore. + +For a full explanation of LDAP synchronized and direct modes, see the [LDAP integration guide](users/ldap.md#ldap-integration-with-mapstore). + +## Start The Stack + +Start MapStore with the auth compose overlay: + +```sh +docker compose -f docker-compose.yml -f docker/docker-compose.auth.yml up --build +``` + +Open [http://localhost/mapstore](http://localhost/mapstore) when the services are ready. + +To stop the stack: + +```sh +docker compose -f docker-compose.yml -f docker/docker-compose.auth.yml down +``` + +To remove containers and volumes for a clean re-import: + +```sh +docker compose -f docker-compose.yml -f docker/docker-compose.auth.yml down -v +``` + +## Test Login + +For Keycloak OpenID login: + +1. Open [http://localhost/mapstore](http://localhost/mapstore). +2. Click login and choose **Keycloak**. +3. Log in with `kcuser` / `kcuser123` or `kcadmin` / `kcadmin123`. +4. Keycloak redirects back to MapStore. + +For LDAP login, use the standard username/password form with one of the LDAP users, such as `msadmin` / `admin123`. This requires a MapStore WAR built and configured with LDAP support. See [LDAP integration with MapStore](users/ldap.md#ldap-integration-with-mapstore). + +## Troubleshooting + +- **Keycloak realm not imported**: Keycloak imports realm files only on first startup. Remove the `keycloak_data` volume and restart the stack. +- **LDAP users not found**: confirm `docker/openldap/ldif/02-users.ldif` exists before first startup. If the LDAP volume already exists, remove the LDAP volumes and restart. +- **MapStore waits for LDAP healthcheck**: the LDAP healthcheck searches for `msadmin`, so the sample user LDIF must be active or the healthcheck must be adapted to your custom users. +- **Redirect or callback errors**: check that the Keycloak redirect URI matches `http://localhost/mapstore/*` and that `keycloakOAuth2Config.redirectUri` in the datadir points to `http://localhost/mapstore/rest/geostore/openid/keycloak/callback`. +- **502 Bad Gateway during login or callback**: the auth proxy config in `docker/mapstore.auth.conf` increases proxy buffers for Keycloak and MapStore callback responses. Restart the proxy if you edit the file. +- **LDAP LDIF edits are ignored**: LDIF bootstrap runs only on an empty LDAP volume. Rebuild the LDAP image and remove LDAP volumes before restarting. +- **LDAP login does not appear to work**: verify the WAR was built with the LDAP profile and that `ldap.properties` is available in the datadir. diff --git a/docs/developer-guide/integrations/infrastructure-setups.md b/docs/developer-guide/integrations/infrastructure-setups.md index 6d7a414aa4..6a6e25baa4 100644 --- a/docs/developer-guide/integrations/infrastructure-setups.md +++ b/docs/developer-guide/integrations/infrastructure-setups.md @@ -12,6 +12,19 @@ flowchart TB GeoServer <--> |authkey| MapStore ``` +## MapStore-Keycloak (OIDC) + MapStore-GeoServer + +```mermaid +flowchart TB + Browser((Browser)) -->| Login | MapStore + MapStore <--> |OIDC login/logout| Keycloak[(Keycloak)] + MapStore -->|"Resources
(e.g. maps)"| DB[(MapStore
Database)] + MapStore -->| Sync Users, Groups, Roles| DB + MapStore -->| OGC requests with authkey| GeoServer + GeoServer -->| Resolve authkey user| MapStore + GeoServer -->| Users, Groups, Roles| DB +``` + ## MapStore-LDAP + MapStore-GeoServer ```mermaid diff --git a/mkdocs.yml b/mkdocs.yml index fba94cce46..9e00050af9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -181,6 +181,7 @@ nav: - General: - GeoServer: 'developer-guide/integrations/geoserver.md' - Authentication: 'developer-guide/integrations/auth.md' + - Auth Docker Setup: 'developer-guide/integrations/auth-docker-setup.md' - Infrastructure: 'developer-guide/integrations/infrastructure-setups.md' - Projects: - MapStore Projects: 'developer-guide/mapstore-projects.md' From 7384604cf772513bcdcfce0a1345597ca7d33e03 Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Thu, 4 Jun 2026 20:33:06 +0545 Subject: [PATCH 03/11] ref: docs to mention mapstore src url --- docs/developer-guide/integrations/auth-docker-setup.md | 10 ++++++++++ docs/quick-start.md | 3 +++ 2 files changed, 13 insertions(+) diff --git a/docs/developer-guide/integrations/auth-docker-setup.md b/docs/developer-guide/integrations/auth-docker-setup.md index f5901e1f82..babfb0790d 100644 --- a/docs/developer-guide/integrations/auth-docker-setup.md +++ b/docs/developer-guide/integrations/auth-docker-setup.md @@ -33,6 +33,16 @@ Before starting the stack, make sure you have: For Keycloak OpenID login only, the sample datadir enables the Keycloak provider through `mapstore-ovr.properties`. For LDAP login, the MapStore backend must also include the LDAP security configuration. +### MapStore WAR source + +The auth compose overlay builds the MapStore image with this default argument: + +```yaml +MAPSTORE_WEBAPP_SRC: "mapstore.war" +``` + +This means Docker expects a `mapstore.war` file in the repository root. If the file is missing, the MapStore image build will fail. You can either copy your built WAR to `./mapstore.war` or edit `docker/docker-compose.auth.yml` to point `MAPSTORE_WEBAPP_SRC` to another local WAR path or a downloadable WAR URL. + ## Prepare Environment Variables Copy the sample environment file to the repository root: diff --git a/docs/quick-start.md b/docs/quick-start.md index 1455a1179b..52f5bb6ed5 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -84,6 +84,9 @@ The provided `docker-compose.yml` sets up a full production stack with: * **MapStore** — the application container * **Nginx** — reverse proxy handling external traffic on port 80 +!!! note + If you need a local Docker setup with sample authentication services, see the [Auth Docker Setup](developer-guide/integrations/auth-docker-setup.md#auth-docker-setup) guide for the optional Keycloak and OpenLDAP compose overlay. + ### Prerequisites * A server with [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed From 862c57033a31a6c1571d9fba2a31527df56c703e Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Mon, 8 Jun 2026 13:53:33 +0545 Subject: [PATCH 04/11] removed insecure fallbacks, updated .env.sample and update docs --- docker/.env.auth.example | 6 +++--- docker/docker-compose.auth.yml | 8 ++++---- docs/developer-guide/integrations/auth-docker-setup.md | 3 +++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docker/.env.auth.example b/docker/.env.auth.example index 7c3b914e8b..06413a6815 100644 --- a/docker/.env.auth.example +++ b/docker/.env.auth.example @@ -8,11 +8,11 @@ # LDAP sample values LDAP_ORGANISATION=Acme LDAP_DOMAIN=acme.org -LDAP_ADMIN_PASSWORD=admin -LDAP_CONFIG_PASSWORD=config +LDAP_ADMIN_PASSWORD= +LDAP_CONFIG_PASSWORD= LDAP_READONLY_USER=true LDAP_READONLY_USER_USERNAME=readonly -LDAP_READONLY_PASSWORD=readonly +LDAP_READONLY_PASSWORD= LDAP_TLS=false # Keycloak dev admin (admin console at http://localhost/keycloak/admin/) diff --git a/docker/docker-compose.auth.yml b/docker/docker-compose.auth.yml index 0ef33ee305..a4e8c756fb 100644 --- a/docker/docker-compose.auth.yml +++ b/docker/docker-compose.auth.yml @@ -13,11 +13,11 @@ services: environment: LDAP_ORGANISATION: "${LDAP_ORGANISATION:-Acme}" LDAP_DOMAIN: "${LDAP_DOMAIN:-acme.org}" - LDAP_ADMIN_PASSWORD: "${LDAP_ADMIN_PASSWORD:-admin}" - LDAP_CONFIG_PASSWORD: "${LDAP_CONFIG_PASSWORD:-config}" + LDAP_ADMIN_PASSWORD: "${LDAP_ADMIN_PASSWORD}" + LDAP_CONFIG_PASSWORD: "${LDAP_CONFIG_PASSWORD}" LDAP_READONLY_USER: "true" LDAP_READONLY_USER_USERNAME: "${LDAP_READONLY_USER_USERNAME:-readonly}" - LDAP_READONLY_USER_PASSWORD: "${LDAP_READONLY_PASSWORD:-readonly}" + LDAP_READONLY_USER_PASSWORD: "${LDAP_READONLY_PASSWORD}" LDAP_TLS: "${LDAP_TLS:-false}" volumes: - ldap_data:/var/lib/ldap @@ -37,7 +37,7 @@ services: restart: on-failure environment: KC_BOOTSTRAP_ADMIN_USERNAME: "${KEYCLOAK_ADMIN:-admin}" - KC_BOOTSTRAP_ADMIN_PASSWORD: "${KEYCLOAK_ADMIN_PASSWORD:-admin}" + KC_BOOTSTRAP_ADMIN_PASSWORD: "${KEYCLOAK_ADMIN_PASSWORD}" KC_HTTP_ENABLED: "true" KC_HTTP_RELATIVE_PATH: "${KEYCLOAK_HTTP_RELATIVE_PATH:-/keycloak}" KC_HOSTNAME: "${KEYCLOAK_HOSTNAME:-http://localhost/keycloak}" diff --git a/docs/developer-guide/integrations/auth-docker-setup.md b/docs/developer-guide/integrations/auth-docker-setup.md index babfb0790d..0a3fc8edac 100644 --- a/docs/developer-guide/integrations/auth-docker-setup.md +++ b/docs/developer-guide/integrations/auth-docker-setup.md @@ -77,6 +77,9 @@ The sample datadir contains: - `geostore-datasource-ovr.properties`: sample PostgreSQL datasource configuration for GeoStore. - `configs/localConfig.json.patch`: adds Keycloak as an authentication provider in the login UI. +!!! note + The sample Keycloak realm and LDAP credentials are intended for local testing only. Keep `docker/keycloak/realm-mapstore.sample.json` and the shipped LDIF data as local test data, and replace client secrets and user passwords before using the setup on another machine or in a shared environment. + These files are regular MapStore externalized configuration files. See [configuration files](../configuration-files.md#configuration-files) and [externalized configuration](../externalized-configuration.md#externalized-configuration) for more details about datadir-based overrides. ## Prepare Keycloak From bf78edad1f23c6b9a57d479f3fdf0987049f041f Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Mon, 8 Jun 2026 19:56:58 +0545 Subject: [PATCH 05/11] support direct ldap, updated samples/envs --- docker/.env.auth.example | 8 +------- docker/docker-compose.auth.yml | 2 +- docker/openldap/ldif.sample/02-users.ldif | 11 +++++++---- docker/openldap/ldif/01-organizational-units.ldif | 2 +- docker/openldap/ldif/03-groups.ldif | 12 +++--------- 5 files changed, 13 insertions(+), 22 deletions(-) diff --git a/docker/.env.auth.example b/docker/.env.auth.example index 06413a6815..944799d14a 100644 --- a/docker/.env.auth.example +++ b/docker/.env.auth.example @@ -3,7 +3,6 @@ # Do NOT use these credentials in production. # The auth compose overlay provides sample defaults, but for any real deployment # you should create a `.env` file with secure values and override defaults. -# NOTE: `datadir/ldap.properties` must use the same bind password as `LDAP_READONLY_PASSWORD`. # LDAP sample values LDAP_ORGANISATION=Acme @@ -25,9 +24,4 @@ KEYCLOAK_PROXY_HEADERS=xforwarded KEYCLOAK_REALM_FILE=realm-mapstore.json # The auth compose expects a realm file named `realm-mapstore.json` in `docker/keycloak/` by default. # To use the shipped sample, copy `docker/keycloak/realm-mapstore.sample.json` to -# `docker/keycloak/realm-mapstore.json` or set `KEYCLOAK_REALM_FILE` to the sample filename. - -# If you need to change the Keycloak image or version for the auth stack, -# set a value like: KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:26.0.7 -# (the compose auth fragment falls back to the embedded image value if unset) -#KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:26.0.7 +# `docker/keycloak/realm-mapstore.json` or set `KEYCLOAK_REALM_FILE` to the sample filename. \ No newline at end of file diff --git a/docker/docker-compose.auth.yml b/docker/docker-compose.auth.yml index a4e8c756fb..33c3f6f745 100644 --- a/docker/docker-compose.auth.yml +++ b/docker/docker-compose.auth.yml @@ -23,7 +23,7 @@ services: - ldap_data:/var/lib/ldap - ldap_config:/etc/ldap/slapd.d healthcheck: - test: ["CMD-SHELL", "ldapsearch -x -H ldap://localhost -D cn=readonly,dc=acme,dc=org -w \"$LDAP_READONLY_PASSWORD\" -b ou=people,dc=acme,dc=org -s one \"(uid=msadmin)\""] + test: ["CMD-SHELL", "ldapsearch -x -H ldap://localhost -D cn=readonly,dc=acme,dc=org -w \"${LDAP_READONLY_PASSWORD}\" -b ou=people,dc=acme,dc=org -s one \"(uid=msadmin)\""] interval: 5s timeout: 5s retries: 30 diff --git a/docker/openldap/ldif.sample/02-users.ldif b/docker/openldap/ldif.sample/02-users.ldif index 728fdc41b7..ca5595c084 100644 --- a/docker/openldap/ldif.sample/02-users.ldif +++ b/docker/openldap/ldif.sample/02-users.ldif @@ -3,8 +3,9 @@ objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person uid: msadmin +cn: msadmin sn: Admin -cn: MapStore Admin +givenName: MapStore Admin mail: msadmin@acme.org userPassword: admin123 @@ -13,8 +14,9 @@ objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person uid: msuser +cn: msuser sn: User -cn: MapStore User +givenName: MapStore User mail: msuser@acme.org userPassword: user123 @@ -23,7 +25,8 @@ objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person uid: msdev +cn: msdev sn: Developer -cn: MapStore Developer +givenName: MapStore Developer mail: msdev@acme.org -userPassword: dev123 +userPassword: dev123 \ No newline at end of file diff --git a/docker/openldap/ldif/01-organizational-units.ldif b/docker/openldap/ldif/01-organizational-units.ldif index d4b9cb1701..a9064a1e38 100644 --- a/docker/openldap/ldif/01-organizational-units.ldif +++ b/docker/openldap/ldif/01-organizational-units.ldif @@ -4,4 +4,4 @@ ou: people dn: ou=groups,dc=acme,dc=org objectClass: organizationalUnit -ou: groups +ou: groups \ No newline at end of file diff --git a/docker/openldap/ldif/03-groups.ldif b/docker/openldap/ldif/03-groups.ldif index 6a37d97934..4a2379c4d0 100644 --- a/docker/openldap/ldif/03-groups.ldif +++ b/docker/openldap/ldif/03-groups.ldif @@ -3,14 +3,8 @@ objectClass: groupOfNames cn: ROLE_ADMIN member: uid=msadmin,ou=people,dc=acme,dc=org -dn: cn=mapstore-users,ou=groups,dc=acme,dc=org +dn: cn=ROLE_USER,ou=groups,dc=acme,dc=org objectClass: groupOfNames -cn: mapstore-users -member: uid=msadmin,ou=people,dc=acme,dc=org +cn: ROLE_USER member: uid=msuser,ou=people,dc=acme,dc=org -member: uid=msdev,ou=people,dc=acme,dc=org - -dn: cn=mapstore-devs,ou=groups,dc=acme,dc=org -objectClass: groupOfNames -cn: mapstore-devs -member: uid=msdev,ou=people,dc=acme,dc=org +member: uid=msdev,ou=people,dc=acme,dc=org \ No newline at end of file From c06f592e7fa3861bde4df5ee10fd45523cbf1a21 Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Mon, 8 Jun 2026 19:57:44 +0545 Subject: [PATCH 06/11] disable autocreate user that breaks callback if direct ldap is configured --- docker/sample-datadir/mapstore-ovr.properties | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/sample-datadir/mapstore-ovr.properties b/docker/sample-datadir/mapstore-ovr.properties index 4c746e6c81..911853e1fd 100644 --- a/docker/sample-datadir/mapstore-ovr.properties +++ b/docker/sample-datadir/mapstore-ovr.properties @@ -6,7 +6,9 @@ keycloakOAuth2Config.enabled=true keycloakOAuth2Config.jsonConfig={"realm":"mapstore","auth-server-url":"http://localhost/keycloak/","ssl-required":"external","resource":"mapstore-server","credentials":{"secret":"mapstore-server-secret"}} keycloakOAuth2Config.redirectUri=http://localhost/mapstore/rest/geostore/openid/keycloak/callback keycloakOAuth2Config.internalRedirectUri=http://localhost/mapstore/ -keycloakOAuth2Config.autoCreateUser=true +# Keep false when the WAR is in LDAP-direct mode: auto-create tries to sync Keycloak +# users/groups through the LDAP-backed GeoStore DAOs and breaks callback login. +keycloakOAuth2Config.autoCreateUser=false keycloakOAuth2Config.forceConfiguredRedirectURI=true keycloakOAuth2Config.authenticatedDefaultRole=USER # Keycloak realm role name -> MapStore role (USER or ADMIN) From 739080ae3430f907f8929cf2d4bf186ac43765dc Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Tue, 9 Jun 2026 11:40:22 +0545 Subject: [PATCH 07/11] infra: improve auth stack setup for LDAP, Keycloak, build, and docs - Fix auth failures across ldap-direct, ldap-sync, and Keycloak login flows - Update Jenkins build flow to support the LDAP_MODE parameter - Align LDAP LDIFs, datadir config, sample env files, and compose settings - Simplify LDAP and Keycloak healthchecks - Replace sample secrets with disposable placeholder credentials - Update docs with new requirements and setup procedure --- .gitignore | 1 + docker/.env.auth.example | 12 +++--- docker/docker-compose.auth.yml | 20 +++++++--- docker/keycloak/realm-mapstore.sample.json | 6 +-- docker/openldap/ldif.sample/02-users.ldif | 6 +-- docker/sample-datadir/ldap.properties | 7 ++-- docker/sample-datadir/mapstore-ovr.properties | 2 +- .../integrations/auth-docker-setup.md | 40 +++++++++---------- 8 files changed, 50 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index 130bd6a923..6ea3337962 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ AGENTS.md .copilot/ .github/copilot-instructions.md mapstore.war +mapstore**.war .env # Local auth overrides and runtime bootstrap files diff --git a/docker/.env.auth.example b/docker/.env.auth.example index 944799d14a..57e5c4e23b 100644 --- a/docker/.env.auth.example +++ b/docker/.env.auth.example @@ -7,16 +7,16 @@ # LDAP sample values LDAP_ORGANISATION=Acme LDAP_DOMAIN=acme.org -LDAP_ADMIN_PASSWORD= -LDAP_CONFIG_PASSWORD= +LDAP_ADMIN_PASSWORD=changeme-ldap-admin-pw-123 +LDAP_CONFIG_PASSWORD=changeme-ldap-config-pw-123 LDAP_READONLY_USER=true -LDAP_READONLY_USER_USERNAME=readonly -LDAP_READONLY_PASSWORD= +LDAP_READONLY_USER_USERNAME=svc_mapstore_ldap +LDAP_READONLY_PASSWORD=changeme-ldap-bind-pw-123 LDAP_TLS=false # Keycloak dev admin (admin console at http://localhost/keycloak/admin/) KEYCLOAK_ADMIN=admin -KEYCLOAK_ADMIN_PASSWORD=admin +KEYCLOAK_ADMIN_PASSWORD=changeme-keycloak-admin-pw-123 KEYCLOAK_HTTP_RELATIVE_PATH=/keycloak KEYCLOAK_HOSTNAME=http://localhost/keycloak KEYCLOAK_HOSTNAME_STRICT=false @@ -24,4 +24,4 @@ KEYCLOAK_PROXY_HEADERS=xforwarded KEYCLOAK_REALM_FILE=realm-mapstore.json # The auth compose expects a realm file named `realm-mapstore.json` in `docker/keycloak/` by default. # To use the shipped sample, copy `docker/keycloak/realm-mapstore.sample.json` to -# `docker/keycloak/realm-mapstore.json` or set `KEYCLOAK_REALM_FILE` to the sample filename. \ No newline at end of file +# `docker/keycloak/realm-mapstore.json` or set `KEYCLOAK_REALM_FILE` to the sample filename. diff --git a/docker/docker-compose.auth.yml b/docker/docker-compose.auth.yml index 33c3f6f745..c87b97d0ae 100644 --- a/docker/docker-compose.auth.yml +++ b/docker/docker-compose.auth.yml @@ -10,24 +10,32 @@ services: image: geosolutions-mapstore/openldap container_name: ldap restart: on-failure + entrypoint: + - /bin/sh + - -lc + - > + rm -rf /container/service/slapd/assets/config/bootstrap/ldif/custom/* && + cp -R /ldif-src/. /container/service/slapd/assets/config/bootstrap/ldif/custom/ && + exec /container/tool/run environment: LDAP_ORGANISATION: "${LDAP_ORGANISATION:-Acme}" LDAP_DOMAIN: "${LDAP_DOMAIN:-acme.org}" - LDAP_ADMIN_PASSWORD: "${LDAP_ADMIN_PASSWORD}" - LDAP_CONFIG_PASSWORD: "${LDAP_CONFIG_PASSWORD}" + LDAP_ADMIN_PASSWORD: "${LDAP_ADMIN_PASSWORD:-changeme-ldap-admin-pw-123}" + LDAP_CONFIG_PASSWORD: "${LDAP_CONFIG_PASSWORD:-changeme-ldap-config-pw-123}" LDAP_READONLY_USER: "true" - LDAP_READONLY_USER_USERNAME: "${LDAP_READONLY_USER_USERNAME:-readonly}" - LDAP_READONLY_USER_PASSWORD: "${LDAP_READONLY_PASSWORD}" + LDAP_READONLY_USER_USERNAME: "${LDAP_READONLY_USER_USERNAME:-svc_mapstore_ldap}" + LDAP_READONLY_USER_PASSWORD: "${LDAP_READONLY_PASSWORD:-changeme-ldap-bind-pw-123}" LDAP_TLS: "${LDAP_TLS:-false}" volumes: - ldap_data:/var/lib/ldap - ldap_config:/etc/ldap/slapd.d + - ./docker/openldap/ldif:/ldif-src:ro healthcheck: - test: ["CMD-SHELL", "ldapsearch -x -H ldap://localhost -D cn=readonly,dc=acme,dc=org -w \"${LDAP_READONLY_PASSWORD}\" -b ou=people,dc=acme,dc=org -s one \"(uid=msadmin)\""] + test: ["CMD-SHELL", "ldapsearch -x -H ldap://localhost -b \"\" -s base \"(objectClass=*)\" namingContexts >/dev/null 2>&1"] interval: 5s timeout: 5s retries: 30 - start_period: 15s + start_period: 20s networks: - mapstore-network diff --git a/docker/keycloak/realm-mapstore.sample.json b/docker/keycloak/realm-mapstore.sample.json index 60490e73ae..6112ccc223 100644 --- a/docker/keycloak/realm-mapstore.sample.json +++ b/docker/keycloak/realm-mapstore.sample.json @@ -31,7 +31,7 @@ "credentials": [ { "type": "password", - "value": "kcuser123", + "value": "changeme-kcuser-pw-123", "temporary": false } ] @@ -47,7 +47,7 @@ "credentials": [ { "type": "password", - "value": "kcadmin123", + "value": "changeme-kcadmin-pw-123", "temporary": false } ] @@ -61,7 +61,7 @@ "protocol": "openid-connect", "publicClient": false, "clientAuthenticatorType": "client-secret", - "secret": "mapstore-server-secret", + "secret": "changeme-mapstore-oidc-client-secret-123", "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, diff --git a/docker/openldap/ldif.sample/02-users.ldif b/docker/openldap/ldif.sample/02-users.ldif index ca5595c084..41b3752cf4 100644 --- a/docker/openldap/ldif.sample/02-users.ldif +++ b/docker/openldap/ldif.sample/02-users.ldif @@ -7,7 +7,7 @@ cn: msadmin sn: Admin givenName: MapStore Admin mail: msadmin@acme.org -userPassword: admin123 +userPassword: changeme-msadmin-pw-123 dn: uid=msuser,ou=people,dc=acme,dc=org objectClass: inetOrgPerson @@ -18,7 +18,7 @@ cn: msuser sn: User givenName: MapStore User mail: msuser@acme.org -userPassword: user123 +userPassword: changeme-msuser-pw-123 dn: uid=msdev,ou=people,dc=acme,dc=org objectClass: inetOrgPerson @@ -29,4 +29,4 @@ cn: msdev sn: Developer givenName: MapStore Developer mail: msdev@acme.org -userPassword: dev123 \ No newline at end of file +userPassword: changeme-msdev-pw-123 diff --git a/docker/sample-datadir/ldap.properties b/docker/sample-datadir/ldap.properties index 347a8c6cd8..5b4d691d91 100644 --- a/docker/sample-datadir/ldap.properties +++ b/docker/sample-datadir/ldap.properties @@ -1,13 +1,13 @@ ## MapStore LDAP — OpenLDAP (docker service: ldap) ## https://docs.mapstore.geosolutionsgroup.com/en/latest/developer-guide/integrations/users/ldap/ ## -## Passwords must match .env / docker-compose (LDAP_READONLY_PASSWORD). +## Bind DN and password must match .env / docker-compose. ldap.host=ldap ldap.port=389 ldap.root=dc=acme,dc=org -ldap.userDn=cn=readonly,dc=acme,dc=org -ldap.password=readonly +ldap.userDn=cn=svc_mapstore_ldap,dc=acme,dc=org +ldap.password=changeme-ldap-bind-pw-123 ldap.userBase=ou=people ldap.groupBase=ou=groups @@ -15,6 +15,7 @@ ldap.roleBase=ou=groups ldap.userFilter=(uid={0}) ldap.groupFilter=(member={0}) ldap.roleFilter=(member={0}) +ldap.memberPattern=^uid=([^,]+).*$ ldap.hierachicalGroups=false ldap.nestedGroupFilter=(member={0}) diff --git a/docker/sample-datadir/mapstore-ovr.properties b/docker/sample-datadir/mapstore-ovr.properties index 911853e1fd..cf0989c3df 100644 --- a/docker/sample-datadir/mapstore-ovr.properties +++ b/docker/sample-datadir/mapstore-ovr.properties @@ -3,7 +3,7 @@ # MapStore container reaches the same URLs via extra_hosts localhost:host-gateway. keycloakOAuth2Config.enabled=true -keycloakOAuth2Config.jsonConfig={"realm":"mapstore","auth-server-url":"http://localhost/keycloak/","ssl-required":"external","resource":"mapstore-server","credentials":{"secret":"mapstore-server-secret"}} +keycloakOAuth2Config.jsonConfig={"realm":"mapstore","auth-server-url":"http://localhost/keycloak/","ssl-required":"external","resource":"mapstore-server","credentials":{"secret":"changeme-mapstore-oidc-client-secret-123"}} keycloakOAuth2Config.redirectUri=http://localhost/mapstore/rest/geostore/openid/keycloak/callback keycloakOAuth2Config.internalRedirectUri=http://localhost/mapstore/ # Keep false when the WAR is in LDAP-direct mode: auto-create tries to sync Keycloak diff --git a/docs/developer-guide/integrations/auth-docker-setup.md b/docs/developer-guide/integrations/auth-docker-setup.md index 0a3fc8edac..05da30216d 100644 --- a/docs/developer-guide/integrations/auth-docker-setup.md +++ b/docs/developer-guide/integrations/auth-docker-setup.md @@ -5,7 +5,7 @@ This page describes a quick local authentication environment for MapStore develo The stack is intended as a starting point. For the full configuration details, see the dedicated guides for [LDAP](users/ldap.md#ldap-integration-with-mapstore), [OpenID Connect](users/openId.md#integration-with-openid-connect), [Keycloak](users/keycloak.md#keycloak-integrations), [MapStore authentication](auth.md#mapstore-authentication---implementation-details) and the available [infrastructure setups](infrastructure-setups.md#possible-setups). !!! warning - The provided files contain sample credentials, sample users, a sample Keycloak realm and sample LDAP entries. They are useful for local development and testing, but they must be replaced or reviewed before using this setup in production. + The provided files contain public sample credentials, sample users, a sample Keycloak realm and sample LDAP entries. They are useful only for local development and testing. They are not secrets, they are not secure defaults, and they must be changed before using this setup outside a disposable local environment. ## Services @@ -59,7 +59,7 @@ Review the values before starting the stack. The most relevant variables are: - `DATADIR_PATH`: optional path to the MapStore datadir mounted in the container. If omitted, `./datadir` is used. !!! warning - Do not reuse the sample passwords in shared, staging or production environments. Update `.env`, the Keycloak realm, LDIF files and MapStore datadir properties consistently. + Do not reuse the sample passwords or client secret in shared, staging or production environments. The placeholder values in this repository are intentionally guessable. Update `.env`, the Keycloak realm, LDIF files and MapStore datadir properties consistently. ## Prepare The Datadir @@ -78,7 +78,9 @@ The sample datadir contains: - `configs/localConfig.json.patch`: adds Keycloak as an authentication provider in the login UI. !!! note - The sample Keycloak realm and LDAP credentials are intended for local testing only. Keep `docker/keycloak/realm-mapstore.sample.json` and the shipped LDIF data as local test data, and replace client secrets and user passwords before using the setup on another machine or in a shared environment. +The sample Keycloak realm and LDAP credentials are intended for local testing only. Keep `docker/keycloak/realm-mapstore.sample.json` and the shipped LDIF data as local test data, and replace client secrets and user passwords before using the setup on another machine or in a shared environment. + +When you use a MapStore WAR built in LDAP-direct mode, `ldap.properties` must also define `ldap.memberPattern`. The shipped sample uses `^uid=([^,]+).*$`, which matches the sample OpenLDAP `member` values such as `uid=msadmin,ou=people,dc=acme,dc=org`. These files are regular MapStore externalized configuration files. See [configuration files](../configuration-files.md#configuration-files) and [externalized configuration](../externalized-configuration.md#externalized-configuration) for more details about datadir-based overrides. @@ -96,7 +98,7 @@ The sample realm configures: - Realm: `mapstore` - Client: `mapstore-server` -- Client secret: `mapstore-server-secret` +- Client secret: `changeme-mapstore-oidc-client-secret-123` - Redirect URI: `http://localhost/mapstore/*` - Realm roles: `admin`, `user` - Sample users: `kcuser`, `kcadmin` @@ -107,26 +109,20 @@ The client and secret must match the values in `datadir/mapstore-ovr.properties` | Username | Password | Email | Keycloak role | MapStore role | | --- | --- | --- | --- | --- | -| `kcuser` | `kcuser123` | `kcuser@acme.org` | `user` | `USER` | -| `kcadmin` | `kcadmin123` | `kcadmin@acme.org` | `admin` | `ADMIN` | +| `kcuser` | `changeme-kcuser-pw-123` | `kcuser@acme.org` | `user` | `USER` | +| `kcadmin` | `changeme-kcadmin-pw-123` | `kcadmin@acme.org` | `admin` | `ADMIN` | The Keycloak admin console is available at [http://localhost/keycloak/admin/](http://localhost/keycloak/admin/) with the `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` values configured in `.env`. The admin user belongs to the Keycloak `master` realm and is used to manage Keycloak, not to log in to MapStore. ## Prepare OpenLDAP -OpenLDAP is initialized from LDIF files in `docker/openldap/ldif/` when the LDAP data volume is empty. The repository keeps the sample users in `docker/openldap/ldif.sample/02-users.ldif` so you can review or customize them before importing. - -To use the provided test users, copy the sample users LDIF into the active bootstrap directory before the first startup: - -```sh -cp docker/openldap/ldif.sample/02-users.ldif docker/openldap/ldif/02-users.ldif -``` +OpenLDAP is initialized from the LDIF files in `docker/openldap/ldif/` when the LDAP data volume is empty. The repository ships the complete bootstrap set there, so the directory itself is the source of truth for organizational units, users, groups and roles. The LDAP bootstrap files define: - Organizational units, such as `ou=people` and `ou=groups`. -- Sample users, if `02-users.ldif` is copied into `docker/openldap/ldif/`. -- Groups and roles, including `ROLE_ADMIN`, `mapstore-users` and `mapstore-devs`. +- Sample users in `02-users.ldif`. +- Groups and roles, including `ROLE_ADMIN`, `ROLE_USER` and the `everyone` group. The LDIF files are copied into the OpenLDAP image at build time and imported only when the LDAP volumes are empty. If you edit LDIF files after the first startup, rebuild the LDAP image and remove the LDAP volumes before starting again. @@ -143,9 +139,9 @@ docker compose -f docker-compose.yml -f docker/docker-compose.auth.yml up --buil | Login | Password | MapStore role | LDAP groups | | --- | --- | --- | --- | -| `msadmin` | `admin123` | `ADMIN` | `ROLE_ADMIN`, `mapstore-users` | -| `msuser` | `user123` | `USER` | `mapstore-users` | -| `msdev` | `dev123` | `USER` | `mapstore-users`, `mapstore-devs` | +| `msadmin` | `changeme-msadmin-pw-123` | `ADMIN` | `ROLE_ADMIN`, `everyone` | +| `msuser` | `changeme-msuser-pw-123` | `USER` | `ROLE_USER`, `everyone` | +| `msdev` | `changeme-msdev-pw-123` | `USER` | `ROLE_USER`, `everyone` | Log in with the LDAP `uid`, for example `msadmin`, not the full DN. With `ldap.convertToUpperCase=true`, synchronized user and group names may appear uppercase in MapStore. @@ -179,17 +175,17 @@ For Keycloak OpenID login: 1. Open [http://localhost/mapstore](http://localhost/mapstore). 2. Click login and choose **Keycloak**. -3. Log in with `kcuser` / `kcuser123` or `kcadmin` / `kcadmin123`. +3. Log in with `kcuser` / `changeme-kcuser-pw-123` or `kcadmin` / `changeme-kcadmin-pw-123`. 4. Keycloak redirects back to MapStore. -For LDAP login, use the standard username/password form with one of the LDAP users, such as `msadmin` / `admin123`. This requires a MapStore WAR built and configured with LDAP support. See [LDAP integration with MapStore](users/ldap.md#ldap-integration-with-mapstore). +For LDAP login, use the standard username/password form with one of the LDAP users, such as `msadmin` / `changeme-msadmin-pw-123`. This requires a MapStore WAR built and configured with LDAP support. See [LDAP integration with MapStore](users/ldap.md#ldap-integration-with-mapstore). ## Troubleshooting - **Keycloak realm not imported**: Keycloak imports realm files only on first startup. Remove the `keycloak_data` volume and restart the stack. - **LDAP users not found**: confirm `docker/openldap/ldif/02-users.ldif` exists before first startup. If the LDAP volume already exists, remove the LDAP volumes and restart. -- **MapStore waits for LDAP healthcheck**: the LDAP healthcheck searches for `msadmin`, so the sample user LDIF must be active or the healthcheck must be adapted to your custom users. -- **Redirect or callback errors**: check that the Keycloak redirect URI matches `http://localhost/mapstore/*` and that `keycloakOAuth2Config.redirectUri` in the datadir points to `http://localhost/mapstore/rest/geostore/openid/keycloak/callback`. +- **MapStore waits for LDAP healthcheck**: the LDAP container now uses a root DSE healthcheck, so this usually points to an LDAP startup problem rather than missing sample users. +- **Redirect or callback errors**: check that the Keycloak redirect URI matches `http://localhost/mapstore/*` and that `keycloakOAuth2Config.redirectUri` in the datadir points to `http://localhost/mapstore/rest/geostore/openid/keycloak/callback`. In LDAP-direct mode, keep `keycloakOAuth2Config.autoCreateUser=false`; otherwise the Keycloak callback can fail while trying to sync users and groups through the LDAP-backed GeoStore DAOs. - **502 Bad Gateway during login or callback**: the auth proxy config in `docker/mapstore.auth.conf` increases proxy buffers for Keycloak and MapStore callback responses. Restart the proxy if you edit the file. - **LDAP LDIF edits are ignored**: LDIF bootstrap runs only on an empty LDAP volume. Rebuild the LDAP image and remove LDAP volumes before restarting. - **LDAP login does not appear to work**: verify the WAR was built with the LDAP profile and that `ldap.properties` is available in the datadir. From 9cd73196f5ae594d037835d6f6f17a64ffbad481 Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Tue, 9 Jun 2026 19:37:12 +0545 Subject: [PATCH 08/11] tests: Endpoint tests to include ldap properties & service --- .dockerignore | 2 ++ docker-compose-override.yml | 23 ++++++++++++++++++++++- tests/acme-ldap/Dockerfile | 7 +++++++ tests/ldap.properties | 25 +++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 tests/acme-ldap/Dockerfile create mode 100644 tests/ldap.properties diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..85dcc16df6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git +node_modules diff --git a/docker-compose-override.yml b/docker-compose-override.yml index a93f1a6c72..60d04e8a87 100644 --- a/docker-compose-override.yml +++ b/docker-compose-override.yml @@ -2,6 +2,21 @@ version: "3.8" services: + ldap: + build: + context: ./tests/acme-ldap/ + image: mapstore-test/acme-ldap + profiles: + - base + healthcheck: + test: ["CMD-SHELL", "bash -c ' Date: Fri, 19 Jun 2026 21:56:11 +0545 Subject: [PATCH 09/11] fix: updated the sample data-dir & configurations for gs 2.6 related updates --- docker/docker-compose.auth.yml | 2 +- docker/sample-datadir/mapstore-ovr.properties | 26 +++++++++++++++---- .../integrations/auth-docker-setup.md | 14 +++++++++- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/docker/docker-compose.auth.yml b/docker/docker-compose.auth.yml index c87b97d0ae..14f9b5b53d 100644 --- a/docker/docker-compose.auth.yml +++ b/docker/docker-compose.auth.yml @@ -91,7 +91,7 @@ services: keycloak: condition: service_healthy extra_hosts: - - "localhost:host-gateway" + - "host.docker.internal:host-gateway" volumes: - "${DATADIR_PATH:-./datadir}:/usr/local/tomcat/datadir:ro" networks: diff --git a/docker/sample-datadir/mapstore-ovr.properties b/docker/sample-datadir/mapstore-ovr.properties index cf0989c3df..9e9d74c8e5 100644 --- a/docker/sample-datadir/mapstore-ovr.properties +++ b/docker/sample-datadir/mapstore-ovr.properties @@ -1,15 +1,31 @@ -# Keycloak OpenID — https://docs.mapstore.geosolutionsgroup.com/en/latest/developer-guide/integrations/users/openId/#keycloak +# Keycloak OpenID — GeoStore 2.6+ generic OIDC provider configuration. # Browser and redirects use http://localhost (nginx on :80). -# MapStore container reaches the same URLs via extra_hosts localhost:host-gateway. +# Backend-only token, userinfo and key endpoints use the host proxy URL from +# inside Docker, avoiding direct dependency on the Keycloak service network name. + +oidc_providers=keycloak keycloakOAuth2Config.enabled=true -keycloakOAuth2Config.jsonConfig={"realm":"mapstore","auth-server-url":"http://localhost/keycloak/","ssl-required":"external","resource":"mapstore-server","credentials":{"secret":"changeme-mapstore-oidc-client-secret-123"}} +keycloakOAuth2Config.clientId=mapstore-server +keycloakOAuth2Config.clientSecret=changeme-mapstore-oidc-client-secret-123 +keycloakOAuth2Config.sendClientSecret=true +keycloakOAuth2Config.authorizationUri=http://localhost/keycloak/realms/mapstore/protocol/openid-connect/auth +keycloakOAuth2Config.accessTokenUri=http://host.docker.internal/keycloak/realms/mapstore/protocol/openid-connect/token +keycloakOAuth2Config.checkTokenEndpointUrl=http://host.docker.internal/keycloak/realms/mapstore/protocol/openid-connect/userinfo +keycloakOAuth2Config.idTokenUri=http://host.docker.internal/keycloak/realms/mapstore/protocol/openid-connect/certs +keycloakOAuth2Config.logoutUri=http://host.docker.internal/keycloak/realms/mapstore/protocol/openid-connect/logout +keycloakOAuth2Config.revokeEndpoint=http://host.docker.internal/keycloak/realms/mapstore/protocol/openid-connect/revoke +keycloakOAuth2Config.introspectionEndpoint=http://host.docker.internal/keycloak/realms/mapstore/protocol/openid-connect/token/introspect keycloakOAuth2Config.redirectUri=http://localhost/mapstore/rest/geostore/openid/keycloak/callback keycloakOAuth2Config.internalRedirectUri=http://localhost/mapstore/ +keycloakOAuth2Config.globalLogoutEnabled=true +keycloakOAuth2Config.postLogoutRedirectUri=http://localhost/mapstore/ # Keep false when the WAR is in LDAP-direct mode: auto-create tries to sync Keycloak # users/groups through the LDAP-backed GeoStore DAOs and breaks callback login. keycloakOAuth2Config.autoCreateUser=false -keycloakOAuth2Config.forceConfiguredRedirectURI=true +keycloakOAuth2Config.scopes=openid,profile,email +keycloakOAuth2Config.rolesClaim=realm_access.roles keycloakOAuth2Config.authenticatedDefaultRole=USER # Keycloak realm role name -> MapStore role (USER or ADMIN) -keycloakOAuth2Config.roleMappings=admin:ADMIN +keycloakOAuth2Config.roleMappings=admin:ADMIN,user:USER +keycloakOAuth2Config.dropUnmapped=false diff --git a/docs/developer-guide/integrations/auth-docker-setup.md b/docs/developer-guide/integrations/auth-docker-setup.md index 05da30216d..b4a13c2365 100644 --- a/docs/developer-guide/integrations/auth-docker-setup.md +++ b/docs/developer-guide/integrations/auth-docker-setup.md @@ -77,6 +77,15 @@ The sample datadir contains: - `geostore-datasource-ovr.properties`: sample PostgreSQL datasource configuration for GeoStore. - `configs/localConfig.json.patch`: adds Keycloak as an authentication provider in the login UI. +The sample `mapstore-ovr.properties` intentionally separates the internal and public Keycloak URLs: + +- Browser-facing authorization and redirect URLs use `http://localhost/...`, because the local Nginx proxy exposes both MapStore and Keycloak on that host. +- Backend-only token, userinfo, certificate, logout, revocation and introspection URLs use `http://host.docker.internal/keycloak/...`, so MapStore calls the same local proxy from inside Docker without depending on the direct Keycloak container network name. + +Do not point backend-only MapStore OpenID endpoints at `http://localhost/keycloak/...` inside this Docker setup. `localhost` is also the MapStore container itself, so server-side callback operations such as token exchange and key retrieval can fail or depend on host-specific Docker networking behavior. For non-Docker deployments, replace `http://host.docker.internal/keycloak` with a Keycloak/proxy URL reachable from the MapStore backend. + +The sample also enables OpenID global logout and redirects back to `http://localhost/mapstore/`, so a MapStore logout clears the Keycloak SSO session instead of silently logging the user in again on the next Keycloak login attempt. + !!! note The sample Keycloak realm and LDAP credentials are intended for local testing only. Keep `docker/keycloak/realm-mapstore.sample.json` and the shipped LDIF data as local test data, and replace client secrets and user passwords before using the setup on another machine or in a shared environment. @@ -152,7 +161,7 @@ For a full explanation of LDAP synchronized and direct modes, see the [LDAP inte Start MapStore with the auth compose overlay: ```sh -docker compose -f docker-compose.yml -f docker/docker-compose.auth.yml up --build +docker compose -f docker-compose.yml -f docker/docker-compose.auth.yml up -d --build ``` Open [http://localhost/mapstore](http://localhost/mapstore) when the services are ready. @@ -185,6 +194,9 @@ For LDAP login, use the standard username/password form with one of the LDAP use - **Keycloak realm not imported**: Keycloak imports realm files only on first startup. Remove the `keycloak_data` volume and restart the stack. - **LDAP users not found**: confirm `docker/openldap/ldif/02-users.ldif` exists before first startup. If the LDAP volume already exists, remove the LDAP volumes and restart. - **MapStore waits for LDAP healthcheck**: the LDAP container now uses a root DSE healthcheck, so this usually points to an LDAP startup problem rather than missing sample users. +- **OIDC login returns 500 before reaching Keycloak**: check the MapStore logs for `authorizationUri is null`. In this Docker setup, `keycloakOAuth2Config.authorizationUri` must be configured explicitly and should point to the public browser URL `http://localhost/keycloak/realms/mapstore/protocol/openid-connect/auth`. +- **OIDC callback returns 500 after Keycloak login**: make sure backend-only endpoints such as `accessTokenUri`, `checkTokenEndpointUrl`, `idTokenUri`, `revokeEndpoint` and `introspectionEndpoint` point to `http://host.docker.internal/keycloak/...`, not `http://localhost/keycloak/...`. +- **Keycloak login is automatic after MapStore logout**: this means the Keycloak SSO session is still active. The sample sets `keycloakOAuth2Config.globalLogoutEnabled=true` and `keycloakOAuth2Config.postLogoutRedirectUri=http://localhost/mapstore/` so MapStore logout also asks Keycloak to end the realm session. - **Redirect or callback errors**: check that the Keycloak redirect URI matches `http://localhost/mapstore/*` and that `keycloakOAuth2Config.redirectUri` in the datadir points to `http://localhost/mapstore/rest/geostore/openid/keycloak/callback`. In LDAP-direct mode, keep `keycloakOAuth2Config.autoCreateUser=false`; otherwise the Keycloak callback can fail while trying to sync users and groups through the LDAP-backed GeoStore DAOs. - **502 Bad Gateway during login or callback**: the auth proxy config in `docker/mapstore.auth.conf` increases proxy buffers for Keycloak and MapStore callback responses. Restart the proxy if you edit the file. - **LDAP LDIF edits are ignored**: LDIF bootstrap runs only on an empty LDAP volume. Rebuild the LDAP image and remove LDAP volumes before restarting. From 512e57b9e606595ec30d2e0b4e45d8673a13c5a1 Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Mon, 22 Jun 2026 16:09:56 +0545 Subject: [PATCH 10/11] add: sample configs for microsoft entra & updates to group configuration --- docker/keycloak/realm-mapstore.sample.json | 12 +++++-- docker/openldap/ldif.sample/02-users.ldif | 35 +++++++------------ docker/openldap/ldif/03-groups.ldif | 15 ++++++-- docker/sample-datadir/mapstore-ovr.properties | 26 ++++++++++++++ .../integrations/auth-docker-setup.md | 25 +++++++------ 5 files changed, 72 insertions(+), 41 deletions(-) diff --git a/docker/keycloak/realm-mapstore.sample.json b/docker/keycloak/realm-mapstore.sample.json index 6112ccc223..67c262d94b 100644 --- a/docker/keycloak/realm-mapstore.sample.json +++ b/docker/keycloak/realm-mapstore.sample.json @@ -16,6 +16,14 @@ { "name": "user", "description": "Optional; default MapStore role is USER if unmapped" + }, + { + "name": "kc-admins", + "description": "Mapped to the MapStore KC_ADMINS user group" + }, + { + "name": "kc-users", + "description": "Mapped to the MapStore KC_USERS user group" } ] }, @@ -27,7 +35,7 @@ "firstName": "Keycloak", "lastName": "User", "email": "kcuser@acme.org", - "realmRoles": ["user"], + "realmRoles": ["user", "kc-users"], "credentials": [ { "type": "password", @@ -43,7 +51,7 @@ "firstName": "Keycloak", "lastName": "Admin", "email": "kcadmin@acme.org", - "realmRoles": ["admin"], + "realmRoles": ["admin", "kc-admins"], "credentials": [ { "type": "password", diff --git a/docker/openldap/ldif.sample/02-users.ldif b/docker/openldap/ldif.sample/02-users.ldif index 41b3752cf4..76f0c8338b 100644 --- a/docker/openldap/ldif.sample/02-users.ldif +++ b/docker/openldap/ldif.sample/02-users.ldif @@ -1,32 +1,21 @@ -dn: uid=msadmin,ou=people,dc=acme,dc=org +dn: uid=ldapadmin,ou=people,dc=acme,dc=org objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person -uid: msadmin -cn: msadmin +uid: ldapadmin +cn: ldapadmin sn: Admin -givenName: MapStore Admin -mail: msadmin@acme.org -userPassword: changeme-msadmin-pw-123 +givenName: LDAP Admin +mail: ldapadmin@ldap.test +userPassword: changeme-ldapadmin-pw-123 -dn: uid=msuser,ou=people,dc=acme,dc=org +dn: uid=ldapuser,ou=people,dc=acme,dc=org objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person -uid: msuser -cn: msuser +uid: ldapuser +cn: ldapuser sn: User -givenName: MapStore User -mail: msuser@acme.org -userPassword: changeme-msuser-pw-123 - -dn: uid=msdev,ou=people,dc=acme,dc=org -objectClass: inetOrgPerson -objectClass: organizationalPerson -objectClass: person -uid: msdev -cn: msdev -sn: Developer -givenName: MapStore Developer -mail: msdev@acme.org -userPassword: changeme-msdev-pw-123 +givenName: LDAP User +mail: ldapuser@ldap.test +userPassword: changeme-ldapuser-pw-123 diff --git a/docker/openldap/ldif/03-groups.ldif b/docker/openldap/ldif/03-groups.ldif index 4a2379c4d0..49ca1cea38 100644 --- a/docker/openldap/ldif/03-groups.ldif +++ b/docker/openldap/ldif/03-groups.ldif @@ -1,10 +1,19 @@ dn: cn=ROLE_ADMIN,ou=groups,dc=acme,dc=org objectClass: groupOfNames cn: ROLE_ADMIN -member: uid=msadmin,ou=people,dc=acme,dc=org +member: uid=ldapadmin,ou=people,dc=acme,dc=org dn: cn=ROLE_USER,ou=groups,dc=acme,dc=org objectClass: groupOfNames cn: ROLE_USER -member: uid=msuser,ou=people,dc=acme,dc=org -member: uid=msdev,ou=people,dc=acme,dc=org \ No newline at end of file +member: uid=ldapuser,ou=people,dc=acme,dc=org + +dn: cn=LDAP_ADMINS,ou=groups,dc=acme,dc=org +objectClass: groupOfNames +cn: LDAP_ADMINS +member: uid=ldapadmin,ou=people,dc=acme,dc=org + +dn: cn=LDAP_USERS,ou=groups,dc=acme,dc=org +objectClass: groupOfNames +cn: LDAP_USERS +member: uid=ldapuser,ou=people,dc=acme,dc=org diff --git a/docker/sample-datadir/mapstore-ovr.properties b/docker/sample-datadir/mapstore-ovr.properties index 9e9d74c8e5..29854f73a9 100644 --- a/docker/sample-datadir/mapstore-ovr.properties +++ b/docker/sample-datadir/mapstore-ovr.properties @@ -25,7 +25,33 @@ keycloakOAuth2Config.postLogoutRedirectUri=http://localhost/mapstore/ keycloakOAuth2Config.autoCreateUser=false keycloakOAuth2Config.scopes=openid,profile,email keycloakOAuth2Config.rolesClaim=realm_access.roles +keycloakOAuth2Config.groupsClaim=realm_access.roles keycloakOAuth2Config.authenticatedDefaultRole=USER # Keycloak realm role name -> MapStore role (USER or ADMIN) keycloakOAuth2Config.roleMappings=admin:ADMIN,user:USER +# Keycloak realm role name -> MapStore user group +keycloakOAuth2Config.groupMappings=kc-admins:KC_ADMINS,kc-users:KC_USERS keycloakOAuth2Config.dropUnmapped=false + +# Optional Microsoft Entra provider template for the auth test bench. +# Uncomment the provider in oidc_providers and the block below only after +# creating an Entra app registration and adding the Microsoft login entry to +# configs/localConfig.json.patch. Keep real secrets out of committed samples. +# +# oidc_providers=keycloak,microsoft +# microsoftOAuth2Config.enabled=true +# microsoftOAuth2Config.clientId= +# microsoftOAuth2Config.clientSecret= +# microsoftOAuth2Config.sendClientSecret=true +# microsoftOAuth2Config.discoveryUrl=https://login.microsoftonline.com//v2.0/.well-known/openid-configuration +# microsoftOAuth2Config.redirectUri=http://localhost/mapstore/rest/geostore/openid/microsoft/callback +# microsoftOAuth2Config.internalRedirectUri=http://localhost/mapstore/ +# microsoftOAuth2Config.autoCreateUser=true +# microsoftOAuth2Config.scopes=openid,email,profile +# microsoftOAuth2Config.principalKey=preferred_username +# microsoftOAuth2Config.rolesClaim=roles +# microsoftOAuth2Config.groupsClaim=roles +# microsoftOAuth2Config.roleMappings=MapStore.Admin:ADMIN,MapStore.User:USER +# microsoftOAuth2Config.groupMappings=MapStore.Admin:MS_ADMINS,MapStore.User:MS_USERS +# microsoftOAuth2Config.authenticatedDefaultRole=USER +# microsoftOAuth2Config.dropUnmapped=false diff --git a/docs/developer-guide/integrations/auth-docker-setup.md b/docs/developer-guide/integrations/auth-docker-setup.md index b4a13c2365..330bddf84e 100644 --- a/docs/developer-guide/integrations/auth-docker-setup.md +++ b/docs/developer-guide/integrations/auth-docker-setup.md @@ -89,7 +89,7 @@ The sample also enables OpenID global logout and redirects back to `http://local !!! note The sample Keycloak realm and LDAP credentials are intended for local testing only. Keep `docker/keycloak/realm-mapstore.sample.json` and the shipped LDIF data as local test data, and replace client secrets and user passwords before using the setup on another machine or in a shared environment. -When you use a MapStore WAR built in LDAP-direct mode, `ldap.properties` must also define `ldap.memberPattern`. The shipped sample uses `^uid=([^,]+).*$`, which matches the sample OpenLDAP `member` values such as `uid=msadmin,ou=people,dc=acme,dc=org`. +When you use a MapStore WAR built in LDAP-direct mode, `ldap.properties` must also define `ldap.memberPattern`. The shipped sample uses `^uid=([^,]+).*$`, which matches the sample OpenLDAP `member` values such as `uid=ldapadmin,ou=people,dc=acme,dc=org`. These files are regular MapStore externalized configuration files. See [configuration files](../configuration-files.md#configuration-files) and [externalized configuration](../externalized-configuration.md#externalized-configuration) for more details about datadir-based overrides. @@ -109,17 +109,17 @@ The sample realm configures: - Client: `mapstore-server` - Client secret: `changeme-mapstore-oidc-client-secret-123` - Redirect URI: `http://localhost/mapstore/*` -- Realm roles: `admin`, `user` +- Realm roles: `admin`, `user`, `kc-admins`, `kc-users` - Sample users: `kcuser`, `kcadmin` The client and secret must match the values in `datadir/mapstore-ovr.properties`. If you change the realm file, update the MapStore datadir configuration accordingly. For details about Keycloak OpenID configuration, see the [Keycloak section of the OpenID Connect guide](users/openId.md#keycloak). ### Keycloak Test Users -| Username | Password | Email | Keycloak role | MapStore role | -| --- | --- | --- | --- | --- | -| `kcuser` | `changeme-kcuser-pw-123` | `kcuser@acme.org` | `user` | `USER` | -| `kcadmin` | `changeme-kcadmin-pw-123` | `kcadmin@acme.org` | `admin` | `ADMIN` | +| Username | Password | Email | Keycloak roles | MapStore role | MapStore group | +| --- | --- | --- | --- | --- | --- | +| `kcuser` | `changeme-kcuser-pw-123` | `kcuser@acme.org` | `user`, `kc-users` | `USER` | `KC_USERS` | +| `kcadmin` | `changeme-kcadmin-pw-123` | `kcadmin@acme.org` | `admin`, `kc-admins` | `ADMIN` | `KC_ADMINS` | The Keycloak admin console is available at [http://localhost/keycloak/admin/](http://localhost/keycloak/admin/) with the `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` values configured in `.env`. The admin user belongs to the Keycloak `master` realm and is used to manage Keycloak, not to log in to MapStore. @@ -131,7 +131,7 @@ The LDAP bootstrap files define: - Organizational units, such as `ou=people` and `ou=groups`. - Sample users in `02-users.ldif`. -- Groups and roles, including `ROLE_ADMIN`, `ROLE_USER` and the `everyone` group. +- Groups and roles, including `ROLE_ADMIN`, `ROLE_USER`, `LDAP_ADMINS`, `LDAP_USERS`, plus MapStore's default `everyone` group. The LDIF files are copied into the OpenLDAP image at build time and imported only when the LDAP volumes are empty. If you edit LDIF files after the first startup, rebuild the LDAP image and remove the LDAP volumes before starting again. @@ -146,13 +146,12 @@ docker compose -f docker-compose.yml -f docker/docker-compose.auth.yml up --buil ### LDAP Test Users -| Login | Password | MapStore role | LDAP groups | +| Login | Password | MapStore role | LDAP / MapStore groups | | --- | --- | --- | --- | -| `msadmin` | `changeme-msadmin-pw-123` | `ADMIN` | `ROLE_ADMIN`, `everyone` | -| `msuser` | `changeme-msuser-pw-123` | `USER` | `ROLE_USER`, `everyone` | -| `msdev` | `changeme-msdev-pw-123` | `USER` | `ROLE_USER`, `everyone` | +| `ldapadmin` | `changeme-ldapadmin-pw-123` | `ADMIN` | `ROLE_ADMIN`, `LDAP_ADMINS`, `everyone` | +| `ldapuser` | `changeme-ldapuser-pw-123` | `USER` | `ROLE_USER`, `LDAP_USERS`, `everyone` | -Log in with the LDAP `uid`, for example `msadmin`, not the full DN. With `ldap.convertToUpperCase=true`, synchronized user and group names may appear uppercase in MapStore. +Log in with the LDAP `uid`, for example `ldapadmin`, not the full DN. With `ldap.convertToUpperCase=true`, synchronized user and group names may appear uppercase in MapStore. For a full explanation of LDAP synchronized and direct modes, see the [LDAP integration guide](users/ldap.md#ldap-integration-with-mapstore). @@ -187,7 +186,7 @@ For Keycloak OpenID login: 3. Log in with `kcuser` / `changeme-kcuser-pw-123` or `kcadmin` / `changeme-kcadmin-pw-123`. 4. Keycloak redirects back to MapStore. -For LDAP login, use the standard username/password form with one of the LDAP users, such as `msadmin` / `changeme-msadmin-pw-123`. This requires a MapStore WAR built and configured with LDAP support. See [LDAP integration with MapStore](users/ldap.md#ldap-integration-with-mapstore). +For LDAP login, use the standard username/password form with one of the LDAP users, such as `ldapadmin` / `changeme-ldapadmin-pw-123`. This requires a MapStore WAR built and configured with LDAP support. See [LDAP integration with MapStore](users/ldap.md#ldap-integration-with-mapstore). ## Troubleshooting From cdf04bcb2199ff1d5b960416a18011460c0c678c Mon Sep 17 00:00:00 2001 From: mahesh-wor Date: Wed, 24 Jun 2026 12:56:24 +0545 Subject: [PATCH 11/11] add: sample configs for microsoft entra & lint issues --- docker/keycloak/realm-mapstore.sample.json | 46 ++++--- docker/sample-datadir/mapstore-ovr.properties | 23 ++-- .../integrations/auth-docker-setup.md | 130 +++++++++++++++++- 3 files changed, 166 insertions(+), 33 deletions(-) diff --git a/docker/keycloak/realm-mapstore.sample.json b/docker/keycloak/realm-mapstore.sample.json index 67c262d94b..6ce1d47b69 100644 --- a/docker/keycloak/realm-mapstore.sample.json +++ b/docker/keycloak/realm-mapstore.sample.json @@ -16,17 +16,19 @@ { "name": "user", "description": "Optional; default MapStore role is USER if unmapped" - }, - { - "name": "kc-admins", - "description": "Mapped to the MapStore KC_ADMINS user group" - }, - { - "name": "kc-users", - "description": "Mapped to the MapStore KC_USERS user group" } ] }, + "groups": [ + { + "name": "kc-admins", + "path": "/kc-admins" + }, + { + "name": "kc-users", + "path": "/kc-users" + } + ], "users": [ { "username": "kcuser", @@ -35,7 +37,8 @@ "firstName": "Keycloak", "lastName": "User", "email": "kcuser@acme.org", - "realmRoles": ["user", "kc-users"], + "realmRoles": ["user"], + "groups": ["/kc-users"], "credentials": [ { "type": "password", @@ -51,7 +54,8 @@ "firstName": "Keycloak", "lastName": "Admin", "email": "kcadmin@acme.org", - "realmRoles": ["admin", "kc-admins"], + "realmRoles": ["admin"], + "groups": ["/kc-admins"], "credentials": [ { "type": "password", @@ -73,12 +77,22 @@ "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, - "redirectUris": [ - "http://localhost/mapstore/*" - ], - "webOrigins": [ - "http://localhost", - "+" + "redirectUris": ["http://localhost/mapstore/*"], + "webOrigins": ["http://localhost", "+"], + "protocolMappers": [ + { + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-group-membership-mapper", + "consentRequired": false, + "config": { + "claim.name": "groups", + "full.path": "false", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } ], "attributes": { "post.logout.redirect.uris": "http://localhost/mapstore/*" diff --git a/docker/sample-datadir/mapstore-ovr.properties b/docker/sample-datadir/mapstore-ovr.properties index 29854f73a9..d1a7fd9ccf 100644 --- a/docker/sample-datadir/mapstore-ovr.properties +++ b/docker/sample-datadir/mapstore-ovr.properties @@ -20,18 +20,17 @@ keycloakOAuth2Config.redirectUri=http://localhost/mapstore/rest/geostore/openid/ keycloakOAuth2Config.internalRedirectUri=http://localhost/mapstore/ keycloakOAuth2Config.globalLogoutEnabled=true keycloakOAuth2Config.postLogoutRedirectUri=http://localhost/mapstore/ -# Keep false when the WAR is in LDAP-direct mode: auto-create tries to sync Keycloak -# users/groups through the LDAP-backed GeoStore DAOs and breaks callback login. -keycloakOAuth2Config.autoCreateUser=false +# Set to false only when the WAR is in LDAP-direct/read-only user-store mode. +keycloakOAuth2Config.autoCreateUser=true keycloakOAuth2Config.scopes=openid,profile,email keycloakOAuth2Config.rolesClaim=realm_access.roles -keycloakOAuth2Config.groupsClaim=realm_access.roles +keycloakOAuth2Config.groupsClaim=groups keycloakOAuth2Config.authenticatedDefaultRole=USER # Keycloak realm role name -> MapStore role (USER or ADMIN) keycloakOAuth2Config.roleMappings=admin:ADMIN,user:USER -# Keycloak realm role name -> MapStore user group +# Keycloak group name from the OIDC "groups" claim -> MapStore user group keycloakOAuth2Config.groupMappings=kc-admins:KC_ADMINS,kc-users:KC_USERS -keycloakOAuth2Config.dropUnmapped=false +keycloakOAuth2Config.dropUnmapped=true # Optional Microsoft Entra provider template for the auth test bench. # Uncomment the provider in oidc_providers and the block below only after @@ -48,10 +47,14 @@ keycloakOAuth2Config.dropUnmapped=false # microsoftOAuth2Config.internalRedirectUri=http://localhost/mapstore/ # microsoftOAuth2Config.autoCreateUser=true # microsoftOAuth2Config.scopes=openid,email,profile -# microsoftOAuth2Config.principalKey=preferred_username +# microsoftOAuth2Config.principalKey=email +# Microsoft App Roles are recommended for MapStore ADMIN/USER mapping. # microsoftOAuth2Config.rolesClaim=roles -# microsoftOAuth2Config.groupsClaim=roles # microsoftOAuth2Config.roleMappings=MapStore.Admin:ADMIN,MapStore.User:USER -# microsoftOAuth2Config.groupMappings=MapStore.Admin:MS_ADMINS,MapStore.User:MS_USERS # microsoftOAuth2Config.authenticatedDefaultRole=USER -# microsoftOAuth2Config.dropUnmapped=false +# Microsoft Entra groups are recommended for MapStore user-group mapping. +# Prefer stable Entra group Object IDs in groupMappings. If your app registration +# emits group display names instead, use those names in place of the IDs. +# microsoftOAuth2Config.groupsClaim=groups +# microsoftOAuth2Config.groupMappings=:MS_ADMINS,:MS_USERS +# microsoftOAuth2Config.dropUnmapped=true diff --git a/docs/developer-guide/integrations/auth-docker-setup.md b/docs/developer-guide/integrations/auth-docker-setup.md index 330bddf84e..1bf6774121 100644 --- a/docs/developer-guide/integrations/auth-docker-setup.md +++ b/docs/developer-guide/integrations/auth-docker-setup.md @@ -77,6 +77,12 @@ The sample datadir contains: - `geostore-datasource-ovr.properties`: sample PostgreSQL datasource configuration for GeoStore. - `configs/localConfig.json.patch`: adds Keycloak as an authentication provider in the login UI. +The sample follows the production-oriented OpenID mapping model used by GeoStore 2.6+: + +- Identity-provider roles map only to MapStore roles, currently `ADMIN` or `USER`. +- Identity-provider groups map to MapStore user groups, such as `KC_ADMINS`, `KC_USERS`, `MS_ADMINS` or `MS_USERS`. +- `dropUnmapped=true` is used for sample OpenID group mappings, so only groups explicitly listed in `groupMappings` are created and assigned in MapStore. + The sample `mapstore-ovr.properties` intentionally separates the internal and public Keycloak URLs: - Browser-facing authorization and redirect URLs use `http://localhost/...`, because the local Nginx proxy exposes both MapStore and Keycloak on that host. @@ -87,7 +93,10 @@ Do not point backend-only MapStore OpenID endpoints at `http://localhost/keycloa The sample also enables OpenID global logout and redirects back to `http://localhost/mapstore/`, so a MapStore logout clears the Keycloak SSO session instead of silently logging the user in again on the next Keycloak login attempt. !!! note -The sample Keycloak realm and LDAP credentials are intended for local testing only. Keep `docker/keycloak/realm-mapstore.sample.json` and the shipped LDIF data as local test data, and replace client secrets and user passwords before using the setup on another machine or in a shared environment. + The sample Keycloak realm and LDAP credentials are intended for local testing only. Keep `docker/keycloak/realm-mapstore.sample.json` and the shipped LDIF data as local test data, and replace client secrets and user passwords before using the setup on another machine or in a shared environment. + +!!! note + The sample `mapstore-ovr.properties` uses `keycloakOAuth2Config.autoCreateUser=true` so the sample Keycloak users and mapped groups are created and synchronized in a DB-backed GeoStore setup. Set it to `false` only when the WAR is built in LDAP-direct/read-only user-store mode; in that case, make sure the Keycloak usernames are already available in the external user store. When you use a MapStore WAR built in LDAP-direct mode, `ldap.properties` must also define `ldap.memberPattern`. The shipped sample uses `^uid=([^,]+).*$`, which matches the sample OpenLDAP `member` values such as `uid=ldapadmin,ou=people,dc=acme,dc=org`. @@ -109,20 +118,125 @@ The sample realm configures: - Client: `mapstore-server` - Client secret: `changeme-mapstore-oidc-client-secret-123` - Redirect URI: `http://localhost/mapstore/*` -- Realm roles: `admin`, `user`, `kc-admins`, `kc-users` +- Realm roles: `admin`, `user` +- Realm groups: `kc-admins`, `kc-users` +- Client mapper: a Group Membership mapper that emits group names in the OIDC `groups` claim - Sample users: `kcuser`, `kcadmin` The client and secret must match the values in `datadir/mapstore-ovr.properties`. If you change the realm file, update the MapStore datadir configuration accordingly. For details about Keycloak OpenID configuration, see the [Keycloak section of the OpenID Connect guide](users/openId.md#keycloak). +The sample uses Keycloak realm roles for MapStore role mapping: + +```properties +keycloakOAuth2Config.rolesClaim=realm_access.roles +keycloakOAuth2Config.roleMappings=admin:ADMIN,user:USER +``` + +It uses Keycloak groups for MapStore user-group mapping: + +```properties +keycloakOAuth2Config.groupsClaim=groups +keycloakOAuth2Config.groupMappings=kc-admins:KC_ADMINS,kc-users:KC_USERS +keycloakOAuth2Config.dropUnmapped=true +``` + +This keeps the test bench close to a real customer setup: administrative authority and group membership can be changed independently in the identity provider. + ### Keycloak Test Users -| Username | Password | Email | Keycloak roles | MapStore role | MapStore group | -| --- | --- | --- | --- | --- | --- | -| `kcuser` | `changeme-kcuser-pw-123` | `kcuser@acme.org` | `user`, `kc-users` | `USER` | `KC_USERS` | -| `kcadmin` | `changeme-kcadmin-pw-123` | `kcadmin@acme.org` | `admin`, `kc-admins` | `ADMIN` | `KC_ADMINS` | +| Username | Password | Email | Keycloak role | Keycloak group | MapStore role | MapStore group | +| --- | --- | --- | --- | --- | --- | --- | +| `kcuser` | `changeme-kcuser-pw-123` | `kcuser@acme.org` | `user` | `kc-users` | `USER` | `KC_USERS` | +| `kcadmin` | `changeme-kcadmin-pw-123` | `kcadmin@acme.org` | `admin` | `kc-admins` | `ADMIN` | `KC_ADMINS` | The Keycloak admin console is available at [http://localhost/keycloak/admin/](http://localhost/keycloak/admin/) with the `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` values configured in `.env`. The admin user belongs to the Keycloak `master` realm and is used to manage Keycloak, not to log in to MapStore. +## Optional Microsoft Entra Provider + +The sample `mapstore-ovr.properties` includes a commented Microsoft Entra template. It is intentionally disabled because every Entra tenant requires tenant-specific IDs and a real client secret. To enable it in a local copied datadir: + +1. Change `oidc_providers=keycloak` to `oidc_providers=keycloak,microsoft`. +2. Uncomment the `microsoftOAuth2Config.*` block. +3. Fill in `clientId`, `clientSecret` and the tenant-specific `discoveryUrl`. +4. Add a Microsoft entry to `datadir/configs/localConfig.json.patch` with `"provider": "microsoft"`. +5. Register `http://localhost/mapstore/rest/geostore/openid/microsoft/callback` as a Web redirect URI in the Entra app registration. +6. Add readable user claims such as `email`, `preferred_username` and `unique_name`, then set `microsoftOAuth2Config.principalKey` to a claim that is actually present in the token returned by your tenant. + +The Microsoft login entry should use the same provider name as the backend prefix: + +```json +{ + "type": "openID", + "provider": "microsoft", + "title": "Microsoft" +} +``` + +For a realistic client-style setup, use **Entra App Roles** for MapStore `ADMIN` / `USER` role mapping and **Entra security groups** for MapStore user-group mapping. Avoid using the same App Role claim for both roles and groups unless you are deliberately testing a shortcut. + +The sample uses `microsoftOAuth2Config.principalKey=email` because the local Microsoft Entra test token includes a readable `email` claim while `preferred_username` may be absent from the token/userinfo data used by this GeoStore 2.6 flow. For a client tenant, inspect the actual returned claims and prefer a stable readable value such as `email`, `preferred_username` or `unique_name`. + +### Entra Role Mapping + +In the App Registration, create app roles like these: + +| Display name | Value | Allowed member types | MapStore role | +| --- | --- | --- | --- | +| `MapStore Admin` | `MapStore.Admin` | Users/Groups | `ADMIN` | +| `MapStore User` | `MapStore.User` | Users/Groups | `USER` | + +Assign users or groups to these roles from **Enterprise applications** -> your application -> **Users and groups**. The token should then contain a `roles` claim. Configure MapStore with: + +```properties +microsoftOAuth2Config.rolesClaim=roles +microsoftOAuth2Config.roleMappings=MapStore.Admin:ADMIN,MapStore.User:USER +microsoftOAuth2Config.authenticatedDefaultRole=USER +``` + +`MapStore.User` is optional when `authenticatedDefaultRole=USER` is present, but keeping it in the test bench lets you verify both explicit user and admin mappings. + +### Entra Group Mapping + +Create or choose Entra security groups for MapStore groups, for example: + +| Entra group | MapStore group | +| --- | --- | +| `MS_ADMINS` | `MS_ADMINS` | +| `MS_USERS` | `MS_USERS` | + +For the most stable production-like mapping, use the Entra group **Object ID** values in `groupMappings`: + +```properties +microsoftOAuth2Config.groupsClaim=groups +microsoftOAuth2Config.groupMappings=:MS_ADMINS,:MS_USERS +microsoftOAuth2Config.dropUnmapped=true +``` + +To get the `groups` claim in the token, configure the Entra app registration to emit group claims. A practical setup is: + +1. Open **App registrations** -> your app -> **Token configuration**. +2. Add a **Groups claim**. +3. Prefer groups assigned to the application when available, so the token contains only groups relevant to this app. +4. Assign the `MS_ADMINS` and `MS_USERS` groups to the Enterprise Application. + +By default, Entra emits group Object IDs. That is usually the safest value to map because display names can change. If a client explicitly wants display names, configure the app manifest to emit cloud display names for app-assigned groups and use those display names in `groupMappings` instead: + +```json +{ + "groupMembershipClaims": "ApplicationGroup", + "optionalClaims": { + "idToken": [ + { + "name": "groups", + "additionalProperties": ["cloud_displayname"] + } + ] + } +} +``` + +When group claims are enabled broadly, users in many groups can hit Entra's group overage behavior and the token may omit the normal `groups` list. For this test bench, prefer app-assigned groups and `dropUnmapped=true` so the MapStore side stays deterministic. + ## Prepare OpenLDAP OpenLDAP is initialized from the LDIF files in `docker/openldap/ldif/` when the LDAP data volume is empty. The repository ships the complete bootstrap set there, so the directory itself is the source of truth for organizational units, users, groups and roles. @@ -131,7 +245,9 @@ The LDAP bootstrap files define: - Organizational units, such as `ou=people` and `ou=groups`. - Sample users in `02-users.ldif`. -- Groups and roles, including `ROLE_ADMIN`, `ROLE_USER`, `LDAP_ADMINS`, `LDAP_USERS`, plus MapStore's default `everyone` group. +- LDAP groups and roles, including `ROLE_ADMIN`, `ROLE_USER`, `LDAP_ADMINS` and `LDAP_USERS`. + +MapStore/GeoStore also assigns users to its default `everyone` group; that group is not bootstrapped from the LDAP LDIF files. The LDIF files are copied into the OpenLDAP image at build time and imported only when the LDAP volumes are empty. If you edit LDIF files after the first startup, rebuild the LDAP image and remove the LDAP volumes before starting again.