Refactor LDAP variable schema to use top-level constant LDAP and nested ALL-CAPS keys.

- Converted group_vars/all/13_ldap.yml from lower-case to ALL-CAPS nested keys.
- Updated all roles, tasks, templates, and filter_plugins to reference LDAP.* instead of ldap.*.
- Fixed Keycloak JSON templates to properly quote Jinja variables.
- Adjusted svc-db-openldap filter plugins and unit tests to handle new LDAP structure.
- Updated integration test to only check uniqueness of TOP-LEVEL ALL-CAPS constants, ignoring nested keys.

See: https://chatgpt.com/share/68b01017-efe0-800f-a508-7d7e2f1c8c8d
This commit is contained in:
2025-08-28 10:15:48 +02:00
parent b9da6908ec
commit cb66fb2978
33 changed files with 238 additions and 249 deletions

View File

@@ -14,22 +14,22 @@ _ldap_domain: "{{ PRIMARY_DOMAIN }}" # LDAP is jsut listening to
_ldap_user_id: "uid"
_ldap_filters_users_all: "(|(objectclass=inetOrgPerson))"
ldap:
LDAP:
# Distinguished Names (DN)
dn:
DN:
# -------------------------------------------------------------------------
# Base DN / Suffix
# This is the top-level naming context for your directory, used as the
# default search base for most operations (e.g. adding users, groups).
# Example: “dc=example,dc=com”
root: "{{ LDAP_DN_BASE }}"
administrator:
ROOT: "{{ LDAP_DN_BASE }}"
ADMINISTRATOR:
# -------------------------------------------------------------------------
# Data-Tree Administrator Bind DN
# The DN used to authenticate for regular directory operations under
# the data tree (adding users, modifying attributes, creating OUs, etc.).
# Typically: “cn=admin,dc=example,dc=com”
data: "cn={{ applications['svc-db-openldap'].users.administrator.username }},{{ LDAP_DN_BASE }}"
DATA: "cn={{ applications['svc-db-openldap'].users.administrator.username }},{{ LDAP_DN_BASE }}"
# -------------------------------------------------------------------------
# Config-Tree Administrator Bind DN
@@ -37,9 +37,9 @@ ldap:
# need to load or modify schema, overlays, modules, or other server-
# level settings.
# Typically: “cn=admin,cn=config”
configuration: "cn={{ applications['svc-db-openldap'].users.administrator.username }},cn=config"
CONFIGURATION: "cn={{ applications['svc-db-openldap'].users.administrator.username }},cn=config"
ou:
OU:
# -------------------------------------------------------------------------
# Organizational Units (OUs)
# Pre-created containers in the directory tree to logically separate entries:
@@ -47,9 +47,9 @@ ldap:
# groups: Contains organizational or business groups (e.g., departments, teams).
# roles: Contains application-specific RBAC roles
# (e.g., "cn=app1-user", "cn=yourls-admin").
users: "ou=users,{{ LDAP_DN_BASE }}"
groups: "ou=groups,{{ LDAP_DN_BASE }}"
roles: "ou=roles,{{ LDAP_DN_BASE }}"
USERS: "ou=users,{{ LDAP_DN_BASE }}"
GROUPS: "ou=groups,{{ LDAP_DN_BASE }}"
ROLES: "ou=roles,{{ LDAP_DN_BASE }}"
# -------------------------------------------------------------------------
# Additional Notes
@@ -59,17 +59,17 @@ ldap:
# for ordinary user/group operations, and vice versa.
# Password to access dn.bind
bind_credential: "{{ applications | get_app_conf('svc-db-openldap', 'credentials.administrator_database_password') }}"
server:
domain: "{{ _ldap_name if _ldap_docker_network_enabled else _ldap_domain }}" # Mapping for public or locale access
port: "{{ _ldap_server_port }}"
uri: "{{ _ldap_protocol }}://{{ _ldap_name if _ldap_docker_network_enabled else _ldap_domain }}:{{ _ldap_server_port }}"
security: "" #TLS, SSL - Leave empty for none
network:
local: "{{ _ldap_docker_network_enabled }}" # Uses the application configuration to define if local network should be available or not
user:
objects:
structural:
BIND_CREDENTIAL: "{{ applications | get_app_conf('svc-db-openldap', 'credentials.administrator_database_password') }}"
SERVER:
DOMAIN: "{{ _ldap_name if _ldap_docker_network_enabled else _ldap_domain }}" # Mapping for public or locale access
PORT: "{{ _ldap_server_port }}"
URI: "{{ _ldap_protocol }}://{{ _ldap_name if _ldap_docker_network_enabled else _ldap_domain }}:{{ _ldap_server_port }}"
SECURITY: "" #TLS, SSL - Leave empty for none
NETWORK:
LOCAL: "{{ _ldap_docker_network_enabled }}" # Uses the application configuration to define if local network should be available or not
USER:
OBJECTS:
STRUCTURAL:
- person # Structural Classes define the core identity of an entry:
# • Specify mandatory attributes (e.g. sn, cn)
# • Each entry must have exactly one structural class
@@ -77,26 +77,26 @@ ldap:
# (e.g. mail, employeeNumber)
- posixAccount # Provides UNIX account attributes (uidNumber, gidNumber,
# homeDirectory)
auxiliary:
nextloud_user: "nextcloudUser" # Auxiliary Classes attach optional attributes without
AUXILIARY:
NEXTCLOUD_USER: "nextcloudUser" # Auxiliary Classes attach optional attributes without
# changing the entrys structural role. Here they add
# nextcloudQuota and nextcloudEnabled for Nextcloud.
ssh_public_key: "ldapPublicKey" # Allows storing SSH public keys for services like Gitea.
attributes:
SSH_PUBLIC_KEY: "ldapPublicKey" # Allows storing SSH public keys for services like Gitea.
ATTRIBUTES:
# Attribut to identify the user
id: "{{ _ldap_user_id }}"
mail: "mail"
fullname: "cn"
firstname: "givenname"
surname: "sn"
ssh_public_key: "sshPublicKey"
nextcloud_quota: "nextcloudQuota"
filters:
users:
login: "(&{{ _ldap_filters_users_all }}({{_ldap_user_id}}=%{{_ldap_user_id}}))"
all: "{{ _ldap_filters_users_all }}"
rbac:
flavors:
ID: "{{ _ldap_user_id }}"
MAIL: "mail"
FULLNAME: "cn"
FIRSTNAME: "givenname"
SURNAME: "sn"
SSH_PUBLIC_KEY: "sshPublicKey"
NEXTCLOUD_QUOTA: "nextcloudQuota"
FILTERS:
USERS:
LOGIN: "(&{{ _ldap_filters_users_all }}({{_ldap_user_id}}=%{{_ldap_user_id}}))"
ALL: "{{ _ldap_filters_users_all }}"
RBAC:
FLAVORS:
# Valid values posixGroup, groupOfNames
- groupOfNames
# - posixGroup

View File

@@ -8,12 +8,12 @@ def build_ldap_nested_group_entries(applications, users, ldap):
result = {}
# Base DN components
role_dn_base = ldap["dn"]["ou"]["roles"]
user_dn_base = ldap["dn"]["ou"]["users"]
ldap_user_attr = ldap["user"]["attributes"]["id"]
role_dn_base = ldap["DN"]["OU"]["ROLES"]
user_dn_base = ldap["DN"]["OU"]["USERS"]
ldap_user_attr = ldap["USER"]["ATTRIBUTES"]["ID"]
# Supported objectClass flavors
flavors = ldap.get("rbac", {}).get("flavors", [])
flavors = ldap.get("RBAC").get("FLAVORS")
for application_id, app_config in applications.items():
# Compute the DN for the application-level OU

View File

@@ -16,10 +16,10 @@ def build_ldap_role_entries(applications, users, ldap):
}
group_id = application_config.get("group_id")
user_dn_base = ldap["dn"]["ou"]["users"]
ldap_user_attr = ldap["user"]["attributes"]["id"]
role_dn_base = ldap["dn"]["ou"]["roles"]
flavors = ldap.get("rbac", {}).get("flavors", [])
user_dn_base = ldap["DN"]["OU"]["USERS"]
ldap_user_attr = ldap["USER"]["ATTRIBUTES"]["ID"]
role_dn_base = ldap["DN"]["OU"]["ROLES"]
flavors = ldap.get("RBAC").get("FLAVORS")
for role_name, role_conf in roles.items():
group_cn = f"{application_id}-{role_name}"

View File

@@ -1,6 +1,6 @@
- name: Load memberof module from file in OpenLDAP container
shell: >
docker exec -i {{ openldap_name }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{openldap_ldif_docker_path}}configuration/01_member_of_configuration.ldif
docker exec -i {{ openldap_name }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ openldap_ldif_docker_path }}configuration/01_member_of_configuration.ldif
listen:
- "Import configuration LDIF files"
- "Import all LDIF files"
@@ -10,7 +10,7 @@
- name: Refint Module Activation for OpenLDAP
shell: >
docker exec -i {{ openldap_name }} ldapadd -Y EXTERNAL -H ldapi:/// -f {{openldap_ldif_docker_path}}configuration/02_member_of_configuration.ldif
docker exec -i {{ openldap_name }} ldapadd -Y EXTERNAL -H ldapi:/// -f {{ openldap_ldif_docker_path }}configuration/02_member_of_configuration.ldif
listen:
- "Import configuration LDIF files"
- "Import all LDIF files"
@@ -22,7 +22,7 @@
- name: "Import schemas"
shell: >
docker exec -i {{ openldap_name }} ldapadd -Y EXTERNAL -H ldapi:/// -f "{{openldap_ldif_docker_path}}schema/{{ item | basename | regex_replace('\.j2$', '') }}"
docker exec -i {{ openldap_name }} ldapadd -Y EXTERNAL -H ldapi:/// -f "{{ openldap_ldif_docker_path }}schema/{{ item | basename | regex_replace('\.j2$', '') }}"
register: ldapadd_result
changed_when: "'adding new entry' in ldapadd_result.stdout"
failed_when: ldapadd_result.rc not in [0, 80]
@@ -33,7 +33,7 @@
- name: Refint Overlay Configuration for OpenLDAP
shell: >
docker exec -i {{ openldap_name }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{openldap_ldif_docker_path}}configuration/03_member_of_configuration.ldif
docker exec -i {{ openldap_name }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ openldap_ldif_docker_path }}configuration/03_member_of_configuration.ldif
listen:
- "Import configuration LDIF files"
- "Import all LDIF files"
@@ -45,7 +45,7 @@
- name: "Import users, groups, etc. to LDAP"
shell: >
docker exec -i {{ openldap_name }} ldapadd -x -D "{{ldap.dn.administrator.data}}" -w "{{ldap.bind_credential}}" -c -f "{{openldap_ldif_docker_path}}groups/{{ item | basename | regex_replace('\.j2$', '') }}"
docker exec -i {{ openldap_name }} ldapadd -x -D "{{LDAP.DN.ADMINISTRATOR.DATA}}" -w "{{ LDAP.BIND_CREDENTIAL }}" -c -f "{{ openldap_ldif_docker_path }}groups/{{ item | basename | regex_replace('\.j2$', '') }}"
register: ldapadd_result
changed_when: "'adding new entry' in ldapadd_result.stdout"
failed_when: ldapadd_result.rc not in [0, 20, 68, 65]

View File

@@ -28,7 +28,7 @@
- name: "Generate hash for Database Admin password"
shell: |
docker exec {{ openldap_name }} \
slappasswd -s "{{ ldap.bind_credential }}"
slappasswd -s "{{ LDAP.BIND_CREDENTIAL }}"
register: database_admin_pw_hash
- name: "Reset Database Admin password in LDAP (olcRootPW)"

View File

@@ -3,11 +3,11 @@
###############################################################################
- name: Ensure LDAP users exist
community.general.ldap_entry:
dn: "{{ ldap.user.attributes.id }}={{ item.key }},{{ ldap.dn.ou.users }}"
dn: "{{ LDAP.USER.ATTRIBUTES.ID }}={{ item.key }},{{ LDAP.DN.OU.USERS }}"
server_uri: "{{ openldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
objectClass: "{{ ldap.user.objects.structural }}"
bind_dn: "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
bind_pw: "{{ LDAP.BIND_CREDENTIAL }}"
objectClass: "{{ LDAP.USER.OBJECTS.STRUCTURAL }}"
attributes:
uid: "{{ item.value.username }}"
sn: "{{ item.value.sn | default(item.key) }}"
@@ -29,12 +29,12 @@
###############################################################################
- name: Ensure required objectClass values and mail address are present
community.general.ldap_attrs:
dn: "{{ ldap.user.attributes.id }}={{ item.key }},{{ ldap.dn.ou.users }}"
dn: "{{ LDAP.USER.ATTRIBUTES.ID }}={{ item.key }},{{ LDAP.DN.OU.USERS }}"
server_uri: "{{ openldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
bind_dn: "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
bind_pw: "{{ LDAP.BIND_CREDENTIAL }}"
attributes:
objectClass: "{{ ldap.user.objects.structural }}"
objectClass: "{{ LDAP.USER.OBJECTS.STRUCTURAL }}"
mail: "{{ item.value.email }}"
state: exact
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
@@ -45,10 +45,10 @@
- name: "Ensure container for application roles exists"
community.general.ldap_entry:
dn: "{{ ldap.dn.ou.roles }}"
dn: "{{ LDAP.DN.OU.ROLES }}"
server_uri: "{{ openldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
bind_dn: "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
bind_pw: "{{ LDAP.BIND_CREDENTIAL }}"
objectClass: organizationalUnit
attributes:
ou: roles

View File

@@ -1,22 +1,22 @@
- name: Gather all users with their current objectClass list
community.general.ldap_search:
server_uri: "{{ openldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
dn: "{{ ldap.dn.ou.users }}"
bind_dn: "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
bind_pw: "{{ LDAP.BIND_CREDENTIAL }}"
dn: "{{ LDAP.DN.OU.USERS }}"
scope: subordinate
filter: "{{ ldap.filters.users.all }}"
filter: "{{ LDAP.FILTERS.USERS.ALL }}"
attrs:
- dn
- objectClass
- "{{ ldap.user.attributes.id }}"
- "{{ LDAP.USER.ATTRIBUTES.ID }}"
register: ldap_users_with_classes
- name: Add only missing auxiliary classes
community.general.ldap_attrs:
server_uri: "{{ openldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
bind_dn: "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
bind_pw: "{{ LDAP.BIND_CREDENTIAL }}"
dn: "{{ item.dn }}"
attributes:
objectClass: "{{ missing_auxiliary }}"
@@ -28,7 +28,7 @@
label: "{{ item.dn }}"
vars:
missing_auxiliary: >-
{{ (ldap.user.objects.auxiliary.values() | list)
{{ (LDAP.USER.OBJECTS.AUXILIARY.values() | list)
| difference(item.objectClass | default([]))
}}
when: missing_auxiliary | length > 0

View File

@@ -8,9 +8,9 @@
vars:
schema_name: "nextcloud"
attribute_defs:
- "( 1.3.6.1.4.1.99999.1 NAME '{{ ldap.user.attributes.nextcloud_quota }}' DESC 'Quota for Nextcloud' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )"
- "( 1.3.6.1.4.1.99999.1 NAME '{{ LDAP.USER.ATTRIBUTES.NEXTCLOUD_QUOTA }}' DESC 'Quota for Nextcloud' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )"
objectclass_defs:
- "( 1.3.6.1.4.1.99999.2 NAME 'nextcloudUser' DESC 'Auxiliary class for Nextcloud attributes' AUXILIARY MAY ( {{ ldap.user.attributes.nextcloud_quota }} ) )"
- "( 1.3.6.1.4.1.99999.2 NAME '{{ LDAP.USER.OBJECTS.AUXILIARY.NEXTCLOUD_USER }}' DESC 'Auxiliary class for Nextcloud attributes' AUXILIARY MAY ( {{ LDAP.USER.ATTRIBUTES.NEXTCLOUD_QUOTA }} ) )"
command: >
ldapsm
-s {{ openldap_server_uri }}

View File

@@ -8,16 +8,16 @@
vars:
schema_name: "openssh-lpk"
attribute_defs:
- "( 1.3.6.1.4.1.24552.1.1 NAME '{{ ldap.user.attributes.ssh_public_key }}' DESC 'OpenSSH Public Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )"
- "( 1.3.6.1.4.1.24552.1.1 NAME '{{ LDAP.USER.ATTRIBUTES.SSH_PUBLIC_KEY }}' DESC 'OpenSSH Public Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )"
- "( 1.3.6.1.4.1.24552.1.2 NAME 'sshFingerprint' DESC 'OpenSSH Public Key Fingerprint' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )"
objectclass_defs:
- >-
( 1.3.6.1.4.1.24552.2.1
NAME '{{ ldap.user.objects.auxiliary.ssh_public_key }}'
NAME '{{ LDAP.USER.OBJECTS.AUXILIARY.SSH_PUBLIC_KEY }}'
DESC 'Auxiliary class for OpenSSH public keys'
SUP top
AUXILIARY
MAY ( {{ ldap.user.attributes.ssh_public_key }} $ sshFingerprint ) )
MAY ( {{ LDAP.USER.ATTRIBUTES.SSH_PUBLIC_KEY }} $ sshFingerprint ) )
command: >
ldapsm

View File

@@ -10,12 +10,12 @@
{% endif %}
volumes:
- 'data:/bitnami/openldap'
- '{{openldap_ldif_host_path}}:{{openldap_ldif_docker_path}}:ro'
- '{{openldap_ldif_host_path}}:{{ openldap_ldif_docker_path }}:ro'
healthcheck:
test: >
bash -c '
ldapsearch -x -H ldap://localhost:{{ openldap_docker_port_open }} \
-D "{{ ldap.dn.administrator.data }}" -w "{{ ldap.bind_credential }}" -b "{{ ldap.dn.root }}" > /dev/null \
-D "{{ LDAP.DN.ADMINISTRATOR.DATA }}" -w "{{ LDAP.BIND_CREDENTIAL }}" -b "{{ LDAP.DN.ROOT }}" > /dev/null \
&& ldapsearch -Y EXTERNAL -H ldapi:/// \
-b cn=config "(&(objectClass=olcOverlayConfig)(olcOverlay=memberof))" \
| grep "olcOverlay:" | grep -q "memberof"

View File

@@ -4,15 +4,15 @@
# GENERAL
## Admin (Data)
LDAP_ADMIN_USERNAME= {{ applications | get_app_conf(application_id, 'users.administrator.username') }} # LDAP database admin user.
LDAP_ADMIN_PASSWORD= {{ldap.bind_credential}} # LDAP database admin password.
LDAP_ADMIN_PASSWORD= {{ LDAP.BIND_CREDENTIAL }} # LDAP database admin password.
## Users
LDAP_USERS= ' ' # Comma separated list of LDAP users to create in the default LDAP tree. Default: user01,user02
LDAP_PASSWORDS= ' ' # Comma separated list of passwords to use for LDAP users. Default: bitnami1,bitnami2
LDAP_ROOT= {{ldap.dn.root}} # LDAP baseDN (or suffix) of the LDAP tree. Default: dc=example,dc=org
LDAP_ROOT= {{ LDAP.DN.ROOT }} # LDAP baseDN (or suffix) of the LDAP tree. Default: dc=example,dc=org
## Admin (Config)
LDAP_ADMIN_DN= {{ldap.dn.administrator.data}}
LDAP_ADMIN_DN= {{LDAP.DN.ADMINISTRATOR.DATA}}
LDAP_CONFIG_ADMIN_ENABLED= yes
LDAP_CONFIG_ADMIN_USERNAME= {{ applications | get_app_conf(application_id, 'users.administrator.username') }}
LDAP_CONFIG_ADMIN_PASSWORD= {{ applications | get_app_conf(application_id, 'credentials.administrator_password') }}

View File

@@ -4,7 +4,7 @@ application_id: "svc-db-openldap"
openldap_docker_port_secure: 636
openldap_docker_port_open: 389
openldap_server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap[application_id] }}"
openldap_bind_dn: "{{ ldap.dn.administrator.configuration }}"
openldap_bind_dn: "{{ LDAP.DN.ADMINISTRATOR.CONFIGURATION }}"
openldap_bind_pw: "{{ applications | get_app_conf(application_id, 'credentials.administrator_password', True) }}"
# LDIF Variables

View File

@@ -170,21 +170,21 @@ OAUTH2_REDIRECT=
# LDAP_SERVER=ldap.example.com
# LDAP_PORT=389
# LDAP_METHOD=plain
# LDAP_UID={{ ldap.user.attributes.id }}
# LDAP_UID={{ LDAP.USER.ATTRIBUTES.ID }}
# LDAP_BASE=dc=example,dc=com
# LDAP_AUTH=simple
# LDAP_BIND_DN=cn=admin,dc=example,dc=com
# LDAP_PASSWORD=password
# LDAP_ROLE_FIELD=ou
# LDAP_FILTER=(&(attr1=value1)(attr2=value2))
LDAP_SERVER="{{ ldap.server.domain }}"
LDAP_PORT="{{ ldap.server.port }}"
LDAP_SERVER="{{ LDAP.SERVER.DOMAIN }}"
LDAP_PORT="{{ LDAP.SERVER.PORT }}"
LDAP_METHOD=
LDAP_UID={{ ldap.user.attributes.id }}
LDAP_BASE="{{ ldap.dn.root }}"
LDAP_BIND_DN="{{ ldap.dn.administrator.data }}"
LDAP_UID={{ LDAP.USER.ATTRIBUTES.ID }}
LDAP_BASE="{{ LDAP.DN.ROOT }}"
LDAP_BIND_DN="{{ LDAP.DN.ADMINISTRATOR.DATA }}"
LDAP_AUTH=password
LDAP_PASSWORD="{{ ldap.bind_credential }}"
LDAP_PASSWORD="{{ LDAP.BIND_CREDENTIAL }}"
LDAP_ROLE_FIELD=
LDAP_FILTER=
{% endif %}

View File

@@ -150,16 +150,16 @@ run:
- exec: rails r "SiteSetting.ldap_sync_enabled = true"
# LDAP connection settings
- exec: rails r "SiteSetting.ldap_sync_host = '{{ ldap.server.domain }}'"
- exec: rails r "SiteSetting.ldap_sync_port = {{ ldap.server.port }}"
- exec: rails r "SiteSetting.ldap_sync_host = '{{ LDAP.SERVER.DOMAIN }}'"
- exec: rails r "SiteSetting.ldap_sync_port = {{ LDAP.SERVER.PORT }}"
- exec: rails r "SiteSetting.ldap_encryption = 'simple_tls'"
- exec: rails r "SiteSetting.ldap_base_dn = '{{ ldap.dn.root }}'"
- exec: rails r "SiteSetting.ldap_bind_dn = '{{ ldap.dn.administrator.data }}'"
- exec: rails r "SiteSetting.ldap_bind_password = '{{ ldap.bind_credential }}'"
- exec: rails r "SiteSetting.ldap_base_dn = '{{ LDAP.DN.ROOT }}'"
- exec: rails r "SiteSetting.ldap_bind_dn = '{{ LDAP.DN.ADMINISTRATOR.DATA }}'"
- exec: rails r "SiteSetting.ldap_bind_password = '{{ LDAP.BIND_CREDENTIAL }}'"
# LDAP additional configuration
- exec: rails r "SiteSetting.ldap_user_filter = '{{ ldap.filters.users.login }}'"
- exec: rails r "SiteSetting.ldap_group_base_dn = '{{ ldap.dn.ou.groups }}'"
- exec: rails r "SiteSetting.ldap_user_filter = '{{ LDAP.FILTERS.USERS.LOGIN }}'"
- exec: rails r "SiteSetting.ldap_group_base_dn = '{{ LDAP.DN.OU.GROUPS }}'"
- exec: rails r "SiteSetting.ldap_group_member_check = 'memberUid'"
- exec: rails r "SiteSetting.ldap_sync_period = 1"

View File

@@ -63,13 +63,13 @@ ESPOCRM_CONFIG_OUTBOUND_EMAIL_FROM_ADDRESS={{ users['contact'].email }}
# ------------------------------------------------
{% if applications | get_app_conf(application_id, 'features.ldap', False) %}
ESPOCRM_CONFIG_AUTHENTICATION_METHOD=Ldap
ESPOCRM_CONFIG_LDAP_HOST={{ ldap.server.domain }}
ESPOCRM_CONFIG_LDAP_PORT={{ ldap.server.port }}
ESPOCRM_CONFIG_LDAP_HOST={{ LDAP.SERVER.DOMAIN }}
ESPOCRM_CONFIG_LDAP_PORT={{ LDAP.SERVER.PORT }}
# ESPOCRM_CONFIG_LDAP_SECURITY: "", SSL or TLS
ESPOCRM_CONFIG_LDAP_SECURITY={{ ldap.server.security }}
ESPOCRM_CONFIG_LDAP_USERNAME={{ ldap.dn.administrator.data }}
ESPOCRM_CONFIG_LDAP_PASSWORD={{ ldap.bind_credential }}
ESPOCRM_CONFIG_LDAP_BASE_DN={{ ldap.dn.ou.users }}
ESPOCRM_CONFIG_LDAP_SECURITY={{ LDAP.SERVER.SECURITY }}
ESPOCRM_CONFIG_LDAP_USERNAME={{ LDAP.DN.ADMINISTRATOR.DATA }}
ESPOCRM_CONFIG_LDAP_PASSWORD={{ LDAP.BIND_CREDENTIAL }}
ESPOCRM_CONFIG_LDAP_BASE_DN={{ LDAP.DN.OU.USERS }}
ESPOCRM_CONFIG_LDAP_USER_LOGIN_FILTER=(sAMAccountName=%USERNAME%)
{% endif %}

View File

@@ -9,26 +9,26 @@ return [
// ldap_server (String)
// ldap hostname server - required
// Example: ldap_server = host.example.com
'ldap_server' => '{{ ldap.server.uri }}',
'ldap_server' => '{{ LDAP.SERVER.URI }}',
// ldap_binddn (String)
// admin dn - optional - only if ldap server dont have anonymous access
// Example: ldap_binddn = cn=admin,dc=example,dc=com
'ldap_binddn' => '{{ ldap.dn.administrator.data }}',
'ldap_binddn' => '{{ LDAP.DN.ADMINISTRATOR.DATA }}',
// ldap_bindpw (String)
// admin password - optional - only if ldap server dont have anonymous access
'ldap_bindpw' => '{{ ldap.bind_credential }}',
'ldap_bindpw' => '{{ LDAP.BIND_CREDENTIAL }}',
// ldap_searchdn (String)
// dn to search users - required
// Example: ldap_searchdn = ou=users,dc=example,dc=com
'ldap_searchdn' => '{{ ldap.dn.ou.users }}',
'ldap_searchdn' => '{{ LDAP.DN.OU.USERS }}',
// ldap_userattr (String)
// attribute to find username - required
// Example: ldap_userattr = uid
'ldap_userattr' => '{{ ldap.user.attributes.id }}',
'ldap_userattr' => '{{ LDAP.USER.ATTRIBUTES.ID }}',
// ldap_group (String)
// DN of the group whose member can auth on Friendica - optional
@@ -42,10 +42,10 @@ return [
// ldap_autocreateaccount_emailattribute (String)
// attribute to get email - optional - default : 'mail'
'ldap_autocreateaccount_emailattribute' => '{{ ldap.user.attributes.mail }}',
'ldap_autocreateaccount_emailattribute' => '{{ LDAP.USER.ATTRIBUTES.MAIL }}',
// ldap_autocreateaccount_nameattribute (String)
// attribute to get nickname - optional - default : 'givenName'
'ldap_autocreateaccount_nameattribute' => '{{ ldap.user.attributes.firstname }}',
'ldap_autocreateaccount_nameattribute' => '{{ LDAP.USER.ATTRIBUTES.FIRSTNAME }}',
],
];

View File

@@ -108,12 +108,12 @@ DJANGO_SECRET_KEY={{applications | get_app_conf(application_id, 'credentials.dja
# Commit: https://gitea.fudaoyuan.icu/Github/funkwhale/commit/4ce46ff2a000646a3dbab80f0ca9fd8d7f8ae24c
LDAP_ENABLED = True
LDAP_SERVER_URI = "{{ ldap.server.uri }}"
LDAP_BIND_DN = "{{ ldap.dn.administrator.data }}"
LDAP_BIND_PASSWORD = "{{ ldap.bind_credential }}"
LDAP_SEARCH_FILTER = "{{ ldap.filters.users.login | replace('%' ~ ldap.user.attributes.id, '{0}') }}"
LDAP_SERVER_URI = "{{ LDAP.SERVER.URI }}"
LDAP_BIND_DN = "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
LDAP_BIND_PASSWORD = "{{ LDAP.BIND_CREDENTIAL }}"
LDAP_SEARCH_FILTER = "{{ LDAP.FILTERS.USERS.LOGIN | replace('%' ~ LDAP.USER.ATTRIBUTES.ID, '{0}') }}"
LDAP_START_TLS = False
LDAP_ROOT_DN = "{{ldap.dn.root}}"
LDAP_ROOT_DN = "{{ LDAP.DN.ROOT }}"
LDAP_USER_ATTR_MAP='{"username":"uid","first_name":"givenName","last_name":"sn","email":"mail"}'
{% endif %}

View File

@@ -8,7 +8,7 @@ LAM_PASSWORD= {{ applications | get_app_conf(application_id, 'cred
LAM_CONFIGURATION_DATABASE= files # configuration database (files or mysql) @todo implement mariadb
# LDAP Configuration
LDAP_SERVER= {{ldap.server.domain}} # domain of LDAP database root entry
LDAP_BASE_DN= {{ldap.dn.root}} # LDAP base DN to overwrite value generated by LDAP_DOMAIN
LDAP_USER= {{ldap.dn.administrator.data}} # LDAP admin user (set as login user for LAM)
LDAP_ADMIN_PASSWORD= {{ldap.bind_credential}} # LDAP admin password
LDAP_SERVER= {{ LDAP.SERVER.DOMAIN }} # domain of LDAP database root entry
LDAP_BASE_DN= {{ LDAP.DN.ROOT }} # LDAP base DN to overwrite value generated by LDAP_DOMAIN
LDAP_USER= {{ LDAP.DN.ADMINISTRATOR.DATA }} # LDAP admin user (set as login user for LAM)
LDAP_ADMIN_PASSWORD= {{ LDAP.BIND_CREDENTIAL }} # LDAP admin password

View File

@@ -9,18 +9,18 @@ docker_compose_flush_handlers: true
# Gitea
GITEA_LDAP_AUTH_ARGS:
- '--name "LDAP ({{ PRIMARY_DOMAIN }})"'
- '--host "{{ ldap.server.domain }}"'
- '--port {{ ldap.server.port }}'
- '--security-protocol "{{ ldap.server.security | trim or "unencrypted" }}"'
- '--bind-dn "{{ ldap.dn.administrator.data }}"'
- '--bind-password "{{ ldap.bind_credential }}"'
- '--user-search-base "{{ ldap.dn.ou.users }}"'
- '--host "{{ LDAP.SERVER.DOMAIN }}"'
- '--port {{ LDAP.SERVER.PORT }}'
- '--security-protocol "{{ LDAP.SERVER.SECURITY | trim or "unencrypted" }}"'
- '--bind-dn "{{ LDAP.DN.ADMINISTRATOR.DATA }}"'
- '--bind-password "{{ LDAP.BIND_CREDENTIAL }}"'
- '--user-search-base "{{ LDAP.DN.OU.USERS }}"'
- '--user-filter "(&(objectClass=inetOrgPerson)(uid=%s))"'
- '--username-attribute "{{ ldap.user.attributes.id }}"'
- '--firstname-attribute "{{ ldap.user.attributes.firstname }}"'
- '--surname-attribute "{{ ldap.user.attributes.surname }}"'
- '--email-attribute "{{ ldap.user.attributes.mail }}"'
- '--public-ssh-key-attribute "{{ ldap.user.attributes.ssh_public_key }}"'
- '--username-attribute "{{ LDAP.USER.ATTRIBUTES.ID }}"'
- '--firstname-attribute "{{ LDAP.USER.ATTRIBUTES.FIRSTNAME }}"'
- '--surname-attribute "{{ LDAP.USER.ATTRIBUTES.SURNAME }}"'
- '--email-attribute "{{ LDAP.USER.ATTRIBUTES.MAIL }}"'
- '--public-ssh-key-attribute "{{ LDAP.USER.ATTRIBUTES.SSH_PUBLIC_KEY }}"'
- '--synchronize-users'
GITEA_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.gitea.version') }}"
GITEA_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.gitea.image') }}"

View File

@@ -11,7 +11,7 @@
"providerId": "user-attribute-ldap-mapper",
"subComponents": {},
"config": {
"ldap.attribute": [ "{{ ldap.user.attributes.firstname }}" ],
"ldap.attribute": [ "{{ LDAP.USER.ATTRIBUTES.FIRSTNAME }}" ],
"attribute.force.default": [ "true" ],
"is.mandatory.in.ldap": [ "true" ],
"is.binary.attribute": [ "false" ],
@@ -27,7 +27,7 @@
"providerId": "user-attribute-ldap-mapper",
"subComponents": {},
"config": {
"ldap.attribute": [ "{{ ldap.user.attributes.surname }}" ],
"ldap.attribute": [ "{{ LDAP.USER.ATTRIBUTES.SURNAME }}" ],
"is.mandatory.in.ldap": [ "true" ],
"always.read.value.from.ldap": [ "true" ],
"read.only": [ "false" ],
@@ -43,7 +43,7 @@
"config": {
"read.only": [ "false" ],
"write.only": [ "true" ],
"ldap.full.name.attribute": [ "{{ ldap.user.attributes.fullname }}" ]
"ldap.full.name.attribute": [ "{{ LDAP.USER.ATTRIBUTES.FULLNAME }}" ]
}
},
@@ -53,7 +53,7 @@
"providerId": "user-attribute-ldap-mapper",
"subComponents": {},
"config": {
"ldap.attribute": [ "{{ ldap.user.attributes.id }}" ],
"ldap.attribute": [ "{{ LDAP.USER.ATTRIBUTES.ID }}" ],
"is.mandatory.in.ldap": [ "true" ],
"attribute.force.default": [ "false" ],
"is.binary.attribute": [ "false" ],
@@ -69,7 +69,7 @@
"providerId": "user-attribute-ldap-mapper",
"subComponents": {},
"config": {
"ldap.attribute": [ "{{ ldap.user.attributes.mail }}" ],
"ldap.attribute": [ "{{ LDAP.USER.ATTRIBUTES.MAIL }}" ],
"is.mandatory.in.ldap": [ "false" ],
"read.only": [ "false" ],
"always.read.value.from.ldap": [ "false" ],
@@ -83,29 +83,29 @@
"providerId": "user-attribute-ldap-mapper",
"subComponents": {},
"config": {
"ldap.attribute": [ "{{ ldap.user.attributes.ssh_public_key }}" ],
"ldap.attribute": [ "{{ LDAP.USER.ATTRIBUTES.SSH_PUBLIC_KEY }}" ],
"is.mandatory.in.ldap": [ "false" ],
"attribute.force.default": [ "false" ],
"is.binary.attribute": [ "false" ],
"read.only": [ "false" ],
"always.read.value.from.ldap": [ "true" ],
"user.model.attribute": [ "{{ ldap.user.attributes.ssh_public_key }}" ]
"user.model.attribute": [ "{{ LDAP.USER.ATTRIBUTES.SSH_PUBLIC_KEY }}" ]
}
},
{# ---------------------- Nextcloud Quota ----------------- #}
{
"name": "{{ ldap.user.attributes.nextcloud_quota }}",
"name": "{{ LDAP.USER.ATTRIBUTES.NEXTCLOUD_QUOTA }}",
"providerId": "user-attribute-ldap-mapper",
"subComponents": {},
"config": {
"ldap.attribute": [ "{{ ldap.user.attributes.nextcloud_quota }}" ],
"ldap.attribute": [ "{{ LDAP.USER.ATTRIBUTES.NEXTCLOUD_QUOTA }}" ],
"is.mandatory.in.ldap": [ "false" ],
"attribute.force.default": [ "false" ],
"is.binary.attribute": [ "false" ],
"always.read.value.from.ldap": [ "false" ],
"read.only": [ "false" ],
"user.model.attribute": [ "{{ ldap.user.attributes.nextcloud_quota }}" ]
"user.model.attribute": [ "{{ LDAP.USER.ATTRIBUTES.NEXTCLOUD_QUOTA }}" ]
}
},
@@ -145,12 +145,12 @@
"config": {
"membership.attribute.type": [ "DN" ],
"group.name.ldap.attribute": [ "cn" ],
"membership.user.ldap.attribute": [ "{{ ldap.user.attributes.id }}" ],
"membership.user.ldap.attribute": [ "{{ LDAP.USER.ATTRIBUTES.ID }}" ],
"preserve.group.inheritance": [ "true" ],
"groups.dn": [ "{{ ldap.dn.ou.roles }}" ],
"groups.dn": [ "{{ LDAP.DN.OU.ROLES }}" ],
"mode": [ "LDAP_ONLY" ],
"user.roles.retrieve.strategy": [ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" ],
"groups.ldap.filter": ["{{ ldap.rbac.flavors | ldap_groups_filter }}"],
"groups.ldap.filter": ["{{ LDAP.RBAC.FLAVORS | ldap_groups_filter }}"],
"membership.ldap.attribute": [ "member" ],
"ignore.missing.groups": [ "true" ],
"group.object.classes": [ "groupOfNames" ],
@@ -205,9 +205,9 @@
"mode": [ "LDAP_ONLY" ],
"membership.attribute.type": [ "DN" ],
"user.roles.retrieve.strategy": [ "LOAD_ROLES_BY_MEMBER_ATTRIBUTE" ],
"roles.dn": [ "{{ ldap.dn.ou.roles }}" ],
"roles.dn": [ "{{ LDAP.DN.OU.ROLES }}" ],
"membership.ldap.attribute": [ "member" ],
"membership.user.ldap.attribute": [ "{{ ldap.user.attributes.id }}" ],
"membership.user.ldap.attribute": [ "{{ LDAP.USER.ATTRIBUTES.ID }}" ],
"memberof.ldap.attribute": [ "memberOf" ],
"role.name.ldap.attribute": [ "cn" ],
"use.realm.roles.mapping": [ "true" ],
@@ -221,7 +221,7 @@
"pagination": [ "true" ],
"connectionTrace": [ "false" ],
"startTls": [ "false" ],
"usersDn": [ "{{ ldap.dn.ou.users }}" ],
"usersDn": [ "{{ LDAP.DN.OU.USERS }}" ],
"connectionPooling": [ "true" ],
"cachePolicy": [ "DEFAULT" ],
"useKerberosForPasswordAuthentication": [ "false" ],
@@ -229,10 +229,10 @@
"enabled": [ "true" ],
"bindCredential": [ "{{ KEYCLOAK_LDAP_BIND_PW }}" ],
"changedSyncPeriod": [ "-1" ],
"usernameLDAPAttribute": [ "{{ ldap.user.attributes.id }}" ],
"usernameLDAPAttribute": [ "{{ LDAP.USER.ATTRIBUTES.ID }}" ],
"bindDn": [ "{{ KEYCLOAK_LDAP_BIND_DN }}" ],
"vendor": [ "other" ],
"uuidLDAPAttribute": [ "{{ ldap.user.attributes.id }}" ],
"uuidLDAPAttribute": [ "{{ LDAP.USER.ATTRIBUTES.ID }}" ],
"allowKerberosAuthentication": [ "false" ],
"connectionUrl": [ "{{ KEYCLOAK_LDAP_URL }}" ],
"syncRegistrations": [ "true" ],
@@ -248,7 +248,7 @@
{{ KEYCLOAK_LDAP_USER_OBJECT_CLASSES | trim | tojson }}
],
"rdnLDAPAttribute": [ "{{ ldap.user.attributes.id }}" ],
"rdnLDAPAttribute": [ "{{ LDAP.USER.ATTRIBUTES.ID }}" ],
"editMode": [ "WRITABLE" ],
"validatePasswordPolicy": [ "false" ],

View File

@@ -33,7 +33,7 @@
"multivalued": false
},
{
"name": ldap.user.attributes.ssh_public_key,
"name": "{{ LDAP.USER.ATTRIBUTES.SSH_PUBLIC_KEY }}",
"displayName": "SSH Public Key",
"validations": {},
"annotations": {},

View File

@@ -10,7 +10,7 @@
},
"protocolMappers": [
{
"name": "{{ ldap.user.attributes.nextcloud_quota }}",
"name": "{{ LDAP.USER.ATTRIBUTES.NEXTCLOUD_QUOTA }}",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
@@ -19,11 +19,11 @@
"introspection.token.claim": "true",
"multivalued": "false",
"userinfo.token.claim": "true",
"user.attribute": "{{ ldap.user.attributes.nextcloud_quota }}",
"user.attribute": "{{ LDAP.USER.ATTRIBUTES.NEXTCLOUD_QUOTA }}",
"id.token.claim": "true",
"lightweight.claim": "false",
"access.token.claim": "true",
"claim.name": "{{ ldap.user.attributes.nextcloud_quota }}",
"claim.name": "{{ LDAP.USER.ATTRIBUTES.NEXTCLOUD_QUOTA }}",
"jsonType.label": "int"
}
},
@@ -41,7 +41,7 @@
"id.token.claim": "true",
"lightweight.claim": "false",
"access.token.claim": "true",
"claim.name": "{{ldap.user.attributes.id}}",
"claim.name": "{{LDAP.USER.ATTRIBUTES.ID}}",
"jsonType.label": "String"
}
}

View File

@@ -55,17 +55,17 @@ KEYCLOAK_POST_LOGOUT_URIS: "+"
## LDAP
KEYCLOAK_LDAP_ENABLED: "{{ applications | get_app_conf(application_id, 'features.ldap', False) }}"
KEYCLOAK_LDAP_CMP_NAME: "{{ ldap.server.domain }}" # Name of the LDAP User Federation component in Keycloak (as shown in UI)
KEYCLOAK_LDAP_BIND_DN: "{{ ldap.dn.administrator.data }}"
KEYCLOAK_LDAP_BIND_PW: "{{ ldap.bind_credential }}"
KEYCLOAK_LDAP_URL: "{{ ldap.server.uri }}"
KEYCLOAK_LDAP_CMP_NAME: "{{ LDAP.SERVER.DOMAIN }}" # Name of the LDAP User Federation component in Keycloak (as shown in UI)
KEYCLOAK_LDAP_BIND_DN: "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
KEYCLOAK_LDAP_BIND_PW: "{{ LDAP.BIND_CREDENTIAL }}"
KEYCLOAK_LDAP_URL: "{{ LDAP.SERVER.URI }}"
# It's important to filter the posixAccount class out, because it is just used by ansible
KEYCLOAK_LDAP_USER_OBJECT_CLASSES: >
{{
(
(ldap.user.objects.structural | reject('equalto','posixAccount') | list)
+ (ldap.user.objects.auxiliary | dict2items | map(attribute='value') | list)
(LDAP.USER.OBJECTS.STRUCTURAL | reject('equalto','posixAccount') | list)
+ (LDAP.USER.OBJECTS.AUXILIARY | dict2items | map(attribute='value') | list)
) | join(', ')
}}

View File

@@ -8,7 +8,7 @@ LAM_PASSWORD= {{ applications | get_app_conf(application_id, 'cred
LAM_CONFIGURATION_DATABASE= files # configuration database (files or mysql) @todo implement mariadb
# LDAP Configuration
LDAP_SERVER= {{ldap.server.domain}} # domain of LDAP database root entry
LDAP_BASE_DN= {{ldap.dn.root}} # LDAP base DN to overwrite value generated by LDAP_DOMAIN
LDAP_USER= {{ldap.dn.administrator.data}} # LDAP admin user (set as login user for LAM)
LDAP_ADMIN_PASSWORD= {{ldap.bind_credential}} # LDAP admin password
LDAP_SERVER= {{ LDAP.SERVER.DOMAIN }} # domain of LDAP database root entry
LDAP_BASE_DN= {{ LDAP.DN.ROOT }} # LDAP base DN to overwrite value generated by LDAP_DOMAIN
LDAP_USER= {{ LDAP.DN.ADMINISTRATOR.DATA }} # LDAP admin user (set as login user for LAM)
LDAP_ADMIN_PASSWORD= {{ LDAP.BIND_CREDENTIAL }} # LDAP admin password

View File

@@ -3,7 +3,7 @@
- name: Set Nextcloud LDAP bind password
command: >
{{ NEXTCLOUD_DOCKER_EXEC_OCC }} ldap:set-config s01 ldapAgentPassword "{{ ldap.bind_credential }}"
{{ NEXTCLOUD_DOCKER_EXEC_OCC }} ldap:set-config s01 ldapAgentPassword "{{ LDAP.BIND_CREDENTIAL }}"
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

View File

@@ -97,10 +97,10 @@ return array (
// note: on Keycloak, OIDC name claim = "${given_name} ${family_name}" or one of them if any is missing
//
'oidc_login_attributes' => array (
'id' => '{{ldap.user.attributes.id}}',
'id' => '{{LDAP.USER.ATTRIBUTES.ID}}',
'name' => 'name',
'mail' => 'email',
'quota' => '{{ ldap.user.attributes.nextcloud_quota }}',
'quota' => '{{ LDAP.USER.ATTRIBUTES.NEXTCLOUD_QUOTA }}',
# 'home' => 'homeDirectory', # Not implemented yet
'ldap_uid' => '{{ OIDC.ATTRIBUTES.USERNAME }}',
# 'groups' => 'ownCloudGroups', # Not implemented yet

View File

@@ -32,17 +32,17 @@ plugin_configuration:
-
appid: "user_ldap"
configkey: "s01ldap_base"
configvalue: "{{ldap.dn.root}}"
configvalue: "{{ LDAP.DN.ROOT }}"
-
appid: "user_ldap"
configkey: "s01ldap_base_groups"
configvalue: "{{ldap.dn.root}}"
configvalue: "{{ LDAP.DN.ROOT }}"
-
appid: "user_ldap"
configkey: "s01ldap_base_users"
configvalue: "{{ldap.dn.ou.users}}"
configvalue: "{{LDAP.DN.OU.USERS}}"
-
appid: "user_ldap"
@@ -67,7 +67,7 @@ plugin_configuration:
-
appid: "user_ldap"
configkey: "s01ldap_dn"
configvalue: "{{ldap.dn.administrator.data}}"
configvalue: "{{LDAP.DN.ADMINISTRATOR.DATA}}"
-
appid: "user_ldap"
configkey: "s01ldap_email_attr"
@@ -103,11 +103,11 @@ plugin_configuration:
-
appid: "user_ldap"
configkey: "s01ldap_host"
configvalue: "{{ldap.server.domain}}"
configvalue: "{{ LDAP.SERVER.DOMAIN }}"
-
appid: "user_ldap"
configkey: "s01ldap_login_filter"
configvalue: "{{ ldap.filters.users.login }}"
configvalue: "{{ LDAP.FILTERS.USERS.LOGIN }}"
-
appid: "user_ldap"
configkey: "s01ldap_login_filter_mode"
@@ -165,7 +165,7 @@ plugin_configuration:
configkey: "s01ldap_userlist_filter"
configvalue: |-
{% if applications | get_app_conf(application_id, 'plugins.user_ldap.user_directory.enabled', True) %}
{{ ldap.filters.users.all }}
{{ LDAP.FILTERS.USERS.ALL }}
{% else %}
()
{% endif %}
@@ -181,4 +181,4 @@ plugin_configuration:
-
appid: "user_ldap"
configkey: "s01ldap_expert_username_attr"
configvalue: "{{ldap.user.attributes.id}}"
configvalue: "{{LDAP.USER.ATTRIBUTES.ID}}"

View File

@@ -1,14 +1,14 @@
openproject_ldap:
name: "{{ PRIMARY_DOMAIN }}" # Display name for the LDAP connection in OpenProject
host: "{{ ldap.server.domain }}" # LDAP server address
port: "{{ ldap.server.port }}" # LDAP server port (typically 389 or 636)
account: "{{ ldap.dn.administrator.data }}" # Bind DN (used for authentication)
account_password: "{{ ldap.bind_credential }}" # Bind password
base_dn: "{{ ldap.dn.ou.users }}" # Base DN for user search
attr_login: "{{ ldap.user.attributes.id }}" # LDAP attribute used for login
host: "{{ LDAP.SERVER.DOMAIN }}" # LDAP server address
port: "{{ LDAP.SERVER.PORT }}" # LDAP server port (typically 389 or 636)
account: "{{ LDAP.DN.ADMINISTRATOR.DATA }}" # Bind DN (used for authentication)
account_password: "{{ LDAP.BIND_CREDENTIAL }}" # Bind password
base_dn: "{{ LDAP.DN.OU.USERS }}" # Base DN for user search
attr_login: "{{ LDAP.USER.ATTRIBUTES.ID }}" # LDAP attribute used for login
attr_firstname: "givenName" # LDAP attribute for first name
attr_lastname: "{{ ldap.user.attributes.surname }}" # LDAP attribute for last name
attr_mail: "{{ ldap.user.attributes.mail }}" # LDAP attribute for email
attr_lastname: "{{ LDAP.USER.ATTRIBUTES.SURNAME }}" # LDAP attribute for last name
attr_mail: "{{ LDAP.USER.ATTRIBUTES.MAIL }}" # LDAP attribute for email
attr_admin: "{{ openproject_filters.administrators }}" # Optional: LDAP attribute for admin group (leave empty if unused)
onthefly_register: true # Automatically create users on first login
tls_mode: 0 # 0 = No TLS, 1 = TLS, 2 = STARTTLS

View File

@@ -5,17 +5,17 @@ application_id: "web-app-openproject"
database_type: "postgres"
# Open Project Specific
openproject_version: "{{ applications | get_app_conf(application_id, 'docker.services.web.version', True) }}"
openproject_image: "{{ applications | get_app_conf(application_id, 'docker.services.web.image', True) }}"
openproject_volume: "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}"
openproject_web_name: "{{ applications | get_app_conf(application_id, 'docker.services.web.name', True) }}"
openproject_seeder_name: "{{ applications | get_app_conf(application_id, 'docker.services.seeder.name', True) }}"
openproject_cron_name: "{{ applications | get_app_conf(application_id, 'docker.services.cron.name', True) }}"
openproject_proxy_name: "{{ applications | get_app_conf(application_id, 'docker.services.proxy.name', True) }}"
openproject_worker_name: "{{ applications | get_app_conf(application_id, 'docker.services.worker.name', True) }}"
openproject_version: "{{ applications | get_app_conf(application_id, 'docker.services.web.version') }}"
openproject_image: "{{ applications | get_app_conf(application_id, 'docker.services.web.image') }}"
openproject_volume: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}"
openproject_web_name: "{{ applications | get_app_conf(application_id, 'docker.services.web.name') }}"
openproject_seeder_name: "{{ applications | get_app_conf(application_id, 'docker.services.seeder.name') }}"
openproject_cron_name: "{{ applications | get_app_conf(application_id, 'docker.services.cron.name') }}"
openproject_proxy_name: "{{ applications | get_app_conf(application_id, 'docker.services.proxy.name') }}"
openproject_worker_name: "{{ applications | get_app_conf(application_id, 'docker.services.worker.name') }}"
# Open Project Cache
openproject_cache_name: "{{ applications | get_app_conf(application_id, 'docker.services.cache.name', True) }}"
openproject_cache_name: "{{ applications | get_app_conf(application_id, 'docker.services.cache.name') }}"
openproject_cache_image: "{{ applications
| get_app_conf(application_id, 'docker.services.cache.image')
or applications
@@ -45,11 +45,11 @@ openproject_rails_settings:
smtp_ssl: false
openproject_filters:
administrators: "{{ '(memberOf=cn=openproject-admins,' ~ ldap.dn.ou.roles ~ ')'
if applications | get_app_conf(application_id, 'ldap.filters.administrators', True) else '' }}"
administrators: "{{ '(memberOf=cn=openproject-admins,' ~ LDAP.DN.OU.ROLES ~ ')'
if applications | get_app_conf(application_id, 'ldap.filters.administrators') else '' }}"
users: "{{ '(memberOf=cn=openproject-users,' ~ ldap.dn.ou.roles ~ ')'
if applications | get_app_conf(application_id, 'ldap.filters.users', True) else '' }}"
users: "{{ '(memberOf=cn=openproject-users,' ~ LDAP.DN.OU.ROLES ~ ')'
if applications | get_app_conf(application_id, 'ldap.filters.users') else '' }}"
# Docker
docker_repository_branch: "stable/{{ openproject_version }}"

View File

@@ -1,3 +1,3 @@
# @See https://github.com/leenooks/phpLDAPadmin/wiki/Docker-Container
APP_URL= {{ domains | get_url(application_id, WEB_PROTOCOL) }}
LDAP_HOST= {{ldap.server.domain}}
LDAP_HOST= {{ LDAP.SERVER.DOMAIN }}

View File

@@ -21,21 +21,21 @@
sh -c 'php artisan tinker << "EOF"
$s = \App\Models\Setting::getSettings();
$s->ldap_enabled = 1;
$s->ldap_server = "{{ ldap.server.uri }}";
$s->ldap_port = {{ ldap.server.port }};
$s->ldap_uname = "{{ ldap.dn.administrator.data }}";
$s->ldap_basedn = "{{ ldap.dn.ou.users }}";
$s->ldap_server = "{{ LDAP.SERVER.URI }}";
$s->ldap_port = {{ LDAP.SERVER.PORT }};
$s->ldap_uname = "{{ LDAP.DN.ADMINISTRATOR.DATA }}";
$s->ldap_basedn = "{{ LDAP.DN.OU.USERS }}";
$s->ldap_filter = "&(objectClass=inetOrgPerson)";
$s->ldap_username_field = "{{ ldap.user.attributes.id }}";
$s->ldap_fname_field = "{{ ldap.user.attributes.firstname }}";
$s->ldap_lname_field = "{{ ldap.user.attributes.surname }}";
$s->ldap_username_field = "{{ LDAP.USER.ATTRIBUTES.ID }}";
$s->ldap_fname_field = "{{ LDAP.USER.ATTRIBUTES.FIRSTNAME }}";
$s->ldap_lname_field = "{{ LDAP.USER.ATTRIBUTES.SURNAME }}";
$s->ldap_auth_filter_query = "uid=";
$s->ldap_version = 3;
$s->ldap_pw_sync = 0;
$s->is_ad = 0;
$s->ad_domain = "";
$s->ldap_default_group = "";
$s->ldap_email = "{{ ldap.user.attributes.mail }}";
$s->ldap_email = "{{ LDAP.USER.ATTRIBUTES.MAIL }}";
$s->custom_forgot_pass_url = "{{ OIDC.CLIENT.RESET_CREDENTIALS }}";
$s->save();
EOF'
@@ -62,7 +62,7 @@
use Illuminate\Support\Facades\DB;
/* encrypt the clear-text password */
\$encrypted = Crypt::encrypt('{{ ldap.bind_credential }}');
\$encrypted = Crypt::encrypt('{{ LDAP.BIND_CREDENTIAL }}');
/* write it straight into settings.ldap_pword */
/* update the one and only row in `settings` */

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
Integration test: ensure every ALL-CAPS variable is defined only once project-wide.
Integration test: ensure every TOP-LEVEL ALL-CAPS variable is defined only once project-wide.
Scope (by design):
- group_vars/**/*.yml
@@ -8,9 +8,11 @@ Scope (by design):
- roles/*/defaults/*.yml
- roles/*/defauls/*.yml # included on purpose in case of folder typos
A variable is considered a “constant” if its key matches: ^[A-Z0-9_]+$
If a constant is declared more than once across the scanned files, the test fails
with a clear message explaining that such constants must be defined only once.
A variable is considered a “constant” if its KEY (at the top level of a YAML document)
matches: ^[A-Z0-9_]+$
Only TOP-LEVEL keys are checked for uniqueness. Nested keys are ignored to allow
namespacing like DICTIONARYA.ENTRY and DICTIONARYB.ENTRY without conflicts.
"""
import os
@@ -41,42 +43,32 @@ def _iter_yaml_files():
seen = set()
for pattern in patterns:
for path in glob.glob(pattern, recursive=True):
# Normalize and deduplicate
norm = os.path.normpath(path)
if norm not in seen and os.path.isfile(norm):
seen.add(norm)
yield norm
def _extract_uppercase_keys_from_mapping(mapping):
def _extract_top_level_uppercase_keys(docs):
"""
Recursively extract ALL-CAPS keys from any YAML mapping.
Returns a set of keys found in this mapping (deduplicated for the file).
Return a set of TOP-LEVEL ALL-CAPS keys found across all mapping documents in a file.
Nested keys are intentionally ignored.
"""
found = set()
def walk(node):
if isinstance(node, dict):
for k, v in node.items():
# Only consider string keys
for doc in docs:
if isinstance(doc, dict):
for k in doc.keys():
if isinstance(k, str) and UPPER_CONST_RE.match(k):
found.add(k)
# Recurse into values to catch nested mappings too
walk(v)
elif isinstance(node, list):
for item in node:
walk(item)
walk(mapping)
return found
class TestUppercaseConstantVarsUnique(unittest.TestCase):
def test_uppercase_constants_unique(self):
# Track where each constant is defined
# Track where each TOP-LEVEL constant is defined
constant_to_files = defaultdict(set)
# Track YAML parse errors to fail fast with a helpful message
# Track YAML parse errors to fail with a helpful message
parse_errors = []
yaml_files = list(_iter_yaml_files())
@@ -88,35 +80,32 @@ class TestUppercaseConstantVarsUnique(unittest.TestCase):
parse_errors.append(f"{yml}: {e}")
continue
# Some files may be empty or contain only comments
if not docs:
continue
# Collect ALL-CAPS keys for this file (dedup per file)
file_constants = set()
for doc in docs:
if isinstance(doc, dict):
file_constants |= _extract_uppercase_keys_from_mapping(doc)
# Non-mapping documents (e.g., lists/None) are ignored
file_constants = _extract_top_level_uppercase_keys(docs)
for const in file_constants:
constant_to_files[const].add(yml)
# Fail if YAML parsing had errors
if parse_errors:
self.fail(
"YAML parsing failed for one or more files:\n"
+ "\n".join(f"- {err}" for err in parse_errors)
)
# Find duplicates (same constant in more than one file)
duplicates = {c: sorted(files) for c, files in constant_to_files.items() if len(files) > 1}
# Duplicates are same TOP-LEVEL constant appearing in >1 files
duplicates = {
c: sorted(files)
for c, files in constant_to_files.items()
if len(files) > 1
}
if duplicates:
msg_lines = [
"Found constants defined more than once. "
"ALL-CAPS variables are treated as constants and must be defined only once project-wide.\n"
"Please consolidate each duplicated constant into a single authoritative location (e.g., one vars/defaults file).",
"Found TOP-LEVEL constants defined more than once. "
"ALL-CAPS top-level variables are treated as constants and must be defined only once project-wide.\n"
"Nested ALL-CAPS keys are allowed and ignored by this test.",
"",
]
for const, files in sorted(duplicates.items()):

View File

@@ -42,19 +42,19 @@ class TestBuildLdapRoleEntries(unittest.TestCase):
}
self.ldap = {
"dn": {
"ou": {
"users": "ou=users,dc=example,dc=org",
"roles": "ou=roles,dc=example,dc=org"
"DN": {
"OU": {
"USERS": "ou=users,dc=example,dc=org",
"ROLES": "ou=roles,dc=example,dc=org"
}
},
"user":{
"attributes": {
"id": "uid"
"USER":{
"ATTRIBUTES": {
"ID": "uid"
}
},
"rbac": {
"flavors": ["posixGroup", "groupOfNames"]
"RBAC": {
"FLAVORS": ["posixGroup", "groupOfNames"]
}
}