From 6418a462ecdf531d704492eaecbbeb30478df28d Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Thu, 11 Sep 2025 13:55:53 +0200 Subject: [PATCH] XWiki: LDAP/OIDC admin mapping, config mounts, and REST installs - LDAP: move settings to xwiki.cfg; enable trylocal (1/0), group_mapping to XWiki.XWikiAdminGroup, and mode_group_sync=always. - OIDC: add groups claim request (oidc.userinfoclaims), map provider group to XWiki.XWikiAdminGroup, and use space-separated scopes. - Compose: mount xwiki.cfg and xwiki.properties into /usr/local/xwiki. - Extensions: wait for REST readiness; pre-check OIDC/LDAP extensions (URL-encoded IDs); install via REST job only if missing. - Vars: strict mappings to LDAP.* and OIDC.* (no defaults), add XWIKI_ADMIN_GROUP and derived DNs. - Config: expose ldap.local_enabled; tidy meta tags; README grammar update. Conversation: https://chatgpt.com/share/68c2b8ad-4814-800f-b377-065f967998db --- roles/web-app-xwiki/README.md | 3 +- roles/web-app-xwiki/config/main.yml | 2 + roles/web-app-xwiki/meta/main.yml | 5 +- roles/web-app-xwiki/tasks/01_core.yml | 68 +++++++++++++++ roles/web-app-xwiki/tasks/main.yml | 5 +- .../templates/docker-compose.yml.j2 | 4 +- roles/web-app-xwiki/templates/env.j2 | 24 ------ .../templates/installjobrequest.xml.j2 | 27 ++++++ roles/web-app-xwiki/templates/xwiki.cfg.j2 | 20 +++++ .../templates/xwiki.properties.j2 | 16 ++++ roles/web-app-xwiki/vars/main.yml | 82 +++++++++++++------ 11 files changed, 198 insertions(+), 58 deletions(-) create mode 100644 roles/web-app-xwiki/tasks/01_core.yml create mode 100644 roles/web-app-xwiki/templates/installjobrequest.xml.j2 create mode 100644 roles/web-app-xwiki/templates/xwiki.cfg.j2 create mode 100644 roles/web-app-xwiki/templates/xwiki.properties.j2 diff --git a/roles/web-app-xwiki/README.md b/roles/web-app-xwiki/README.md index eb64511e..8abaac2b 100644 --- a/roles/web-app-xwiki/README.md +++ b/roles/web-app-xwiki/README.md @@ -6,7 +6,7 @@ Empower your organization with **XWiki**, an open-source enterprise wiki and kno ## Overview -This role deploys XWiki using Docker, automating the installation, configuration, and management of your XWiki server. It integrates with an external PostgreSQL database, Redis for caching and sessions, and an Nginx reverse proxy. The role supports advanced features such as global CSS injection, Matomo analytics, OIDC authentication, and centralized logout, making it a powerful and customizable solution within the Infinito.Nexus ecosystem. +This role deploys XWiki using Docker, automating the installation, configuration, and management of your XWiki server. It integrates with an MariaDB database and an Nginx reverse proxy. The role supports advanced features such as global CSS injection, Matomo analytics, OIDC authentication, and centralized logout, making it a powerful and customizable solution within the Infinito.Nexus ecosystem. ## Features @@ -17,7 +17,6 @@ This role deploys XWiki using Docker, automating the installation, configuration - **Office Integration:** Import, export, and collaborate on Office documents (Word, Excel, PDF). - **Customization & Theming:** Adapt the look and feel of your wiki with skins, CSS, and scripting. - **Integration Ready:** Connect with external systems such as Keycloak (OIDC), LDAP, or analytics tools like Matomo. -- **Scalability:** Backend support with PostgreSQL and Redis for performance and session handling. ## Further Resources diff --git a/roles/web-app-xwiki/config/main.yml b/roles/web-app-xwiki/config/main.yml index 99efda55..99208535 100644 --- a/roles/web-app-xwiki/config/main.yml +++ b/roles/web-app-xwiki/config/main.yml @@ -29,3 +29,5 @@ server: - "x.wiki.{{ PRIMARY_DOMAIN }}" rbac: roles: {} +ldap: + local_enabled: true # Allows local login if LDAP is down diff --git a/roles/web-app-xwiki/meta/main.yml b/roles/web-app-xwiki/meta/main.yml index 8c5b632c..66f9b88b 100644 --- a/roles/web-app-xwiki/meta/main.yml +++ b/roles/web-app-xwiki/meta/main.yml @@ -8,7 +8,10 @@ galaxy_info: Kevin Veen-Birkenbach Consulting & Coaching Solutions https://www.veen.world - galaxy_tags: [wiki, collaboration, knowledge, documentation, cms] + galaxy_tags: + - wiki + - documentation + - xwiki repository: "https://s.infinito.nexus/code" issue_tracker_url: "https://s.infinito.nexus/issues" documentation: "https://s.infinito.nexus/code/" diff --git a/roles/web-app-xwiki/tasks/01_core.yml b/roles/web-app-xwiki/tasks/01_core.yml new file mode 100644 index 00000000..33caa3ea --- /dev/null +++ b/roles/web-app-xwiki/tasks/01_core.yml @@ -0,0 +1,68 @@ +- name: "load docker, db and proxy for {{ application_id }}" + include_role: + name: sys-stk-full-stateful + vars: + docker_compose_flush_handlers: false + +- name: "Render xwiki.cfg" + template: + src: "xwiki.cfg.j2" + dest: "{{ XWIKI_HOST_CONF_PATH }}" + notify: docker compose up + +- name: "Render xwiki.properties" + template: + src: "xwiki.properties.j2" + dest: "{{ XWIKI_HOST_PROPERTIES_PATH }}" + notify: docker compose up + +- name: "flush docker compose for '{{ application_id }}'" + meta: flush_handlers + +- name: "Wait until XWiki REST is ready" + uri: + url: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/xwiki/rest/" + status_code: [200, 401] + return_content: no + register: xwiki_rest_up + retries: 60 + delay: 5 + until: xwiki_rest_up is succeeded + +- name: "Check if OIDC extension installed" + uri: + url: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/xwiki/rest/wikis/xwiki/extensions/{{ XWIKI_EXT_OIDC_ID | urlencode }}" + method: GET + user: "{{ XWIKI_ADMIN_USER }}" + password: "{{ XWIKI_ADMIN_PASS }}" + force_basic_auth: yes + status_code: [200,404] + register: xwiki_oidc_ext + when: XWIKI_OIDC_ENABLED | bool + +- name: "Check if LDAP extension installed" + uri: + url: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/xwiki/rest/wikis/xwiki/extensions/{{ XWIKI_EXT_LDAP_ID | urlencode }}" + method: GET + user: "{{ XWIKI_ADMIN_USER }}" + password: "{{ XWIKI_ADMIN_PASS }}" + force_basic_auth: yes + status_code: [200,404] + register: xwiki_ldap_ext + when: XWIKI_LDAP_ENABLED | bool + +- name: "Install LDAP and/or OIDC extensions" + uri: + url: "{{ XWIKI_REST_BASE }}" + method: PUT + user: "{{ XWIKI_ADMIN_USER }}" + password: "{{ XWIKI_ADMIN_PASS }}" + force_basic_auth: yes + headers: { Content-Type: "text/xml" } + body: "{{ lookup('template', 'installjobrequest.xml.j2') }}" + status_code: 200 + when: + - (XWIKI_OIDC_ENABLED | bool and xwiki_oidc_ext.status == 404) or + (XWIKI_LDAP_ENABLED | bool and (xwiki_ldap_ext is not skipped) and xwiki_ldap_ext.status == 404) + +- include_tasks: utils/run_once.yml \ No newline at end of file diff --git a/roles/web-app-xwiki/tasks/main.yml b/roles/web-app-xwiki/tasks/main.yml index 2a4e1343..faa96241 100644 --- a/roles/web-app-xwiki/tasks/main.yml +++ b/roles/web-app-xwiki/tasks/main.yml @@ -1,7 +1,4 @@ --- - block: - - name: "load docker, db and proxy for {{ application_id }}" - include_role: - name: sys-stk-full-stateful - - include_tasks: utils/run_once.yml + - include_tasks: 01_core.yml when: run_once_web_app_xwiki is not defined diff --git a/roles/web-app-xwiki/templates/docker-compose.yml.j2 b/roles/web-app-xwiki/templates/docker-compose.yml.j2 index 47ce7ceb..f069f242 100644 --- a/roles/web-app-xwiki/templates/docker-compose.yml.j2 +++ b/roles/web-app-xwiki/templates/docker-compose.yml.j2 @@ -7,8 +7,10 @@ container_name: "{{ XWIKI_CONTAINER }}" hostname: '{{ XWIKI_HOSTNAME}}' ports: - - "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}" + - "127.0.0.1:{{ XWIKI_HOST_PORT }}:{{ container_port }}" volumes: + - "{{ XWIKI_HOST_CONF_PATH }}:/usr/local/xwiki/xwiki.cfg" + - "{{ XWIKI_HOST_PROPERTIES_PATH }}:/usr/local/xwiki/xwiki.properties" - 'data:/usr/local/xwiki' {% include 'roles/docker-container/templates/healthcheck/curl.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %} diff --git a/roles/web-app-xwiki/templates/env.j2 b/roles/web-app-xwiki/templates/env.j2 index 65d9551a..d43be81a 100644 --- a/roles/web-app-xwiki/templates/env.j2 +++ b/roles/web-app-xwiki/templates/env.j2 @@ -3,27 +3,3 @@ DB_PASSWORD="{{ database_password }}" DB_HOST="{{ database_host }}" DB_PORT="{{ database_port }}" DB_DATABASE="{{ database_name }}" - -### Pretix core -#XWIKI_XWIKI_INSTANCE_NAME="{{ PRIMARY_DOMAIN | upper }} Tickets" -#XWIKI_XWIKI_ALLOWED_HOSTS="{{ XWIKI_HOSTNAME }},127.0.0.1,localhost" -#XWIKI_XWIKI_URL="{{ XWIKI_URL }}" -#XWIKI_XWIKI_AUTH_BACKENDS="xwiki.base.auth.NativeAuthBackend{% if XWIKI_OIDC_ENABLED %},xwiki_oidc.auth.OIDCAuthBackend{% endif %}" -# -### Locale -#XWIKI_LOCALE_TIMEZONE="{{ HOST_TIMEZONE }}" -# -#{% if XWIKI_OIDC_ENABLED %} -### OIDC (plugin) -#XWIKI_OIDC_TITLE="{{ XWIKI_OIDC_LABEL | replace('\"','\\\"') }}" -#XWIKI_OIDC_ISSUER="{{ XWIKI_OIDC_ISSUER }}" -#XWIKI_OIDC_AUTHORIZATION_ENDPOINT="{{ XWIKI_OIDC_AUTH_URL }}" -#XWIKI_OIDC_TOKEN_ENDPOINT="{{ XWIKI_OIDC_TOKEN_URL }}" -#XWIKI_OIDC_USERINFO_ENDPOINT="{{ XWIKI_OIDC_USERINFO_URL }}" -#XWIKI_OIDC_END_SESSION_ENDPOINT="{{ XWIKI_OIDC_LOGOUT_URL }}" -#XWIKI_OIDC_JWKS_URI="{{ XWIKI_OIDC_JWKS_URL }}" -#XWIKI_OIDC_CLIENT_ID="{{ XWIKI_OIDC_CLIENT_ID }}" -#XWIKI_OIDC_CLIENT_SECRET="{{ XWIKI_OIDC_CLIENT_SECRET }}" -#XWIKI_OIDC_SCOPES="{{ XWIKI_OIDC_SCOPES }}" -#XWIKI_OIDC_UNIQUE_ATTRIBUTE="{{ XWIKI_OIDC_UNIQUE_ATTRIBUTE }}" -#{% endif %} diff --git a/roles/web-app-xwiki/templates/installjobrequest.xml.j2 b/roles/web-app-xwiki/templates/installjobrequest.xml.j2 new file mode 100644 index 00000000..498fdb82 --- /dev/null +++ b/roles/web-app-xwiki/templates/installjobrequest.xml.j2 @@ -0,0 +1,27 @@ + + + install-extensions + install + + wiki:xwiki + false + true + true + + {% if XWIKI_LDAP_ENABLED | bool %} + + {{ XWIKI_EXT_LDAP_ID }} + {{ XWIKI_EXT_LDAP_VERSION }} + wiki:xwiki + + {% endif %} + {% if XWIKI_OIDC_ENABLED | bool %} + + {{ XWIKI_EXT_OIDC_ID }} + {{ XWIKI_EXT_OIDC_VERSION }} + wiki:xwiki + + {% endif %} + + + diff --git a/roles/web-app-xwiki/templates/xwiki.cfg.j2 b/roles/web-app-xwiki/templates/xwiki.cfg.j2 new file mode 100644 index 00000000..f2c9a651 --- /dev/null +++ b/roles/web-app-xwiki/templates/xwiki.cfg.j2 @@ -0,0 +1,20 @@ +# ---- Authentication selection +{% if XWIKI_OIDC_ENABLED | bool %} +xwiki.authentication.authclass=org.xwiki.contrib.oidc.auth.OIDCAuthServiceImpl +{% elif XWIKI_LDAP_ENABLED | bool %} +xwiki.authentication.authclass=org.xwiki.contrib.ldap.XWikiLDAPAuthServiceImpl +xwiki.authentication.ldap=1 +xwiki.authentication.ldap.trylocal={{ (XWIKI_LDAP_TRYLOCAL | bool) | ternary(1, 0) }} +xwiki.authentication.ldap.group_mapping=XWiki.XWikiAdminGroup={{ XWIKI_LDAP_ADMIN_GROUP_DN }} +xwiki.authentication.ldap.mode_group_sync=always +xwiki.authentication.ldap.server={{ XWIKI_LDAP_SERVER }} +xwiki.authentication.ldap.port={{ XWIKI_LDAP_PORT }} +xwiki.authentication.ldap.base_DN={{ XWIKI_LDAP_BASE_DN }} +xwiki.authentication.ldap.bind_DN={{ XWIKI_LDAP_BIND_DN }} +xwiki.authentication.ldap.bind_pass={{ XWIKI_LDAP_BIND_PASS }} +xwiki.authentication.ldap.fields_mapping={{ XWIKI_LDAP_FIELDS_MAPPING }} +xwiki.authentication.ldap.update_user=1 +{% else %} +# Fallback: Native XWiki Auth +# xwiki.authentication.authclass=com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl +{% endif %} diff --git a/roles/web-app-xwiki/templates/xwiki.properties.j2 b/roles/web-app-xwiki/templates/xwiki.properties.j2 new file mode 100644 index 00000000..8d00a159 --- /dev/null +++ b/roles/web-app-xwiki/templates/xwiki.properties.j2 @@ -0,0 +1,16 @@ +############################################ +# OIDC +{% if XWIKI_OIDC_ENABLED | bool %} +oidc.provider={{ XWIKI_OIDC_PROVIDER }} +oidc.endpoint.authorization={{ XWIKI_OIDC_AUTHORIZATION }} +oidc.endpoint.token={{ XWIKI_OIDC_TOKEN }} +oidc.endpoint.userinfo={{ XWIKI_OIDC_USERINFO }} +oidc.endpoint.logout={{ XWIKI_OIDC_LOGOUT }} +oidc.clientid={{ XWIKI_OIDC_CLIENT_ID }} +oidc.secret={{ XWIKI_OIDC_CLIENT_SECRET }} +oidc.scope={{ XWIKI_OIDC_SCOPES }} +oidc.enableUser=true +oidc.userinfoclaims={{ XWIKI_OIDC_GROUPS_CLAIM }} +oidc.groups.claim={{ XWIKI_OIDC_GROUPS_CLAIM }} +oidc.groups.mapping=XWiki.XWikiAdminGroup={{ XWIKI_OIDC_ADMIN_PROVIDER_GROUP }} +{% endif %} diff --git a/roles/web-app-xwiki/vars/main.yml b/roles/web-app-xwiki/vars/main.yml index 5c4979d8..a2a28c04 100644 --- a/roles/web-app-xwiki/vars/main.yml +++ b/roles/web-app-xwiki/vars/main.yml @@ -1,33 +1,63 @@ # General -application_id: "web-app-xwiki" -database_type: "mariadb" -container_port: 8080 -container_hostname: "{{ domains | get_domain(application_id) }}" +application_id: "web-app-xwiki" +database_type: "mariadb" +container_port: 8080 +container_hostname: "{{ domains | get_domain(application_id) }}" -# Pretix +# XWiki + +XWIKI_HOST_PORT: "{{ ports.localhost.http[application_id] }}" ## URLs -XWIKI_URL: "{{ domains | get_url(application_id, WEB_PROTOCOL) }}" -XWIKI_HOSTNAME: "{{ container_hostname }}" +XWIKI_HOSTNAME: "{{ container_hostname }}" -## OIDC (mirrors GitLab’s pattern) -XWIKI_OIDC_ENABLED: "{{ applications | get_app_conf(application_id, 'features.oidc') }}" -XWIKI_OIDC_LABEL: "{{ OIDC.BUTTON_TEXT }}" -XWIKI_OIDC_CLIENT_ID: "{{ OIDC.CLIENT.ID }}" -XWIKI_OIDC_CLIENT_SECRET: "{{ OIDC.CLIENT.SECRET }}" -XWIKI_OIDC_ISSUER: "{{ OIDC.CLIENT.ISSUER_URL }}" -XWIKI_OIDC_AUTH_URL: "{{ OIDC.CLIENT.AUTHORIZE_URL }}" -XWIKI_OIDC_TOKEN_URL: "{{ OIDC.CLIENT.TOKEN_URL }}" -XWIKI_OIDC_USERINFO_URL: "{{ OIDC.CLIENT.USER_INFO_URL }}" -XWIKI_OIDC_LOGOUT_URL: "{{ OIDC.CLIENT.LOGOUT_URL }}" -XWIKI_OIDC_JWKS_URL: "{{ OIDC.CLIENT.CERTS }}" -XWIKI_OIDC_SCOPES: "openid,email,profile" -# Use Keycloak username claim by default (plugin default is 'sub') -XWIKI_OIDC_UNIQUE_ATTRIBUTE: "{{ OIDC.ATTRIBUTES.USERNAME }}" +## Paths +XWIKI_HOST_CONF_PATH: "{{ [docker_compose.directories.config, 'xwiki.cfg'] | path_join }}" +XWIKI_HOST_PROPERTIES_PATH: "{{ [docker_compose.directories.config, 'xwiki.properties'] | path_join }}" ## Docker -XWIKI_IMAGE_CUSTOM: "xwiki_custom" -XWIKI_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.xwiki.image') }}" -XWIKI_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.xwiki.version') }}" -XWIKI_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.xwiki.name') }}" -XWIKI_DATA_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}" +XWIKI_IMAGE_CUSTOM: "xwiki_custom" +XWIKI_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.xwiki.image') }}" +XWIKI_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.xwiki.version') }}" +XWIKI_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.xwiki.name') }}" +XWIKI_DATA_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}" + +# Feature toggles (must be set in config/main.yml -> features) +XWIKI_LDAP_ENABLED: "{{ applications | get_app_conf(application_id, 'features.ldap') }}" +XWIKI_OIDC_ENABLED: "{{ applications | get_app_conf(application_id, 'features.oidc') }}" + +# Admin credentials (must be provided via inventory/vault) +XWIKI_ADMIN_USER: "{{ users.administrator.username }}" +XWIKI_ADMIN_PASS: "{{ users.administrator.password }}" +XWIKI_ADMIN_GROUP: "{{ application_id }}-administrator" + +# REST endpoint (local inside container) +XWIKI_REST_BASE: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/xwiki/rest/jobs?jobType=install&async=false" + +# Extension IDs + Versions (pin versions explicitly) +XWIKI_EXT_LDAP_ID: "org.xwiki.contrib.ldap:ldap-authenticator" +XWIKI_EXT_LDAP_VERSION: "9.15.7" +XWIKI_EXT_OIDC_ID: "org.xwiki.contrib.oidc:oidc-authenticator" +XWIKI_EXT_OIDC_VERSION: "2.19.2" + +# LDAP configuration (mapped to LDAP.* context) +XWIKI_LDAP_SERVER: "{{ LDAP.SERVER.DOMAIN }}" +XWIKI_LDAP_PORT: "{{ LDAP.SERVER.PORT }}" +XWIKI_LDAP_BASE_DN: "{{ LDAP.DN.ROOT }}" +XWIKI_LDAP_BIND_DN: "{{ LDAP.DN.ADMINISTRATOR.DATA }}" +XWIKI_LDAP_BIND_PASS: "{{ LDAP.BIND_CREDENTIAL }}" +XWIKI_LDAP_TRYLOCAL: "{{ applications | get_app_conf(application_id, 'ldap.local_enabled') }}" +XWIKI_LDAP_FIELDS_MAPPING: "last_name={{ LDAP.USER.ATTRIBUTES.SURNAME }},first_name={{ LDAP.USER.ATTRIBUTES.FIRSTNAME }},email={{ LDAP.USER.ATTRIBUTES.MAIL }}" +XWIKI_LDAP_ADMIN_GROUP_DN: "cn={{ XWIKI_ADMIN_GROUP ~ ',' ~ LDAP.DN.OU.GROUPS }}" + +# OIDC configuration (must exist in OIDC.* context) +XWIKI_OIDC_PROVIDER: "{{ OIDC.CLIENT.ISSUER_URL }}" +XWIKI_OIDC_AUTHORIZATION: "{{ OIDC.CLIENT.AUTHORIZE_URL }}" +XWIKI_OIDC_TOKEN: "{{ OIDC.CLIENT.TOKEN_URL }}" +XWIKI_OIDC_USERINFO: "{{ OIDC.CLIENT.USER_INFO_URL }}" +XWIKI_OIDC_LOGOUT: "{{ OIDC.CLIENT.LOGOUT_URL }}" +XWIKI_OIDC_CLIENT_ID: "{{ OIDC.CLIENT.ID }}" +XWIKI_OIDC_CLIENT_SECRET: "{{ OIDC.CLIENT.SECRET }}" +XWIKI_OIDC_SCOPES: "openid email profile {{ RBAC.GROUP.CLAIM }}" +XWIKI_OIDC_GROUPS_CLAIM: "{{ RBAC.GROUP.CLAIM }}" +XWIKI_OIDC_ADMIN_PROVIDER_GROUP: "{{ [RBAC.GROUP.NAME, XWIKI_ADMIN_GROUP] | path_join }}"