diff --git a/dejacode/settings.py b/dejacode/settings.py index eb1040cf..d5c246d9 100644 --- a/dejacode/settings.py +++ b/dejacode/settings.py @@ -767,7 +767,72 @@ def get_fake_redis_connection(config, use_strict_redis): # AUTH_LDAP_USER_FILTERSTR="(uid=%(user)s)" AUTH_LDAP_USER_FILTERSTR = env.str("AUTH_LDAP_USER_FILTERSTR", default="") -AUTH_LDAP_USER_SEARCH = LDAPSearch(AUTH_LDAP_USER_DN, ldap.SCOPE_SUBTREE, AUTH_LDAP_USER_FILTERSTR) +# Optional: Define multiple LDAP user searches using a JSON list. +# When provided, this setting overrides AUTH_LDAP_USER_DN and AUTH_LDAP_USER_FILTERSTR. +# +# Example: +# AUTH_LDAP_USER_SEARCHES = """ +# [ +# { +# "base": "ou=users,dc=example,dc=com", +# "filter": "(uid=%(user)s)" +# }, +# { +# "base": "ou=otherusers,dc=example,dc=com", +# "filter": "(uid=%(user)s)" +# } +# ] +# """ +# +# Hint: use as a single line string within docker env +# +# Each entry must define: +# - "base": The base DN to search +# - "filter": The LDAP filter (must include %(user)s) +# +# All searches are combined using LDAPSearchUnion. +AUTH_LDAP_USER_SEARCHES = env.str("AUTH_LDAP_USER_SEARCHES", default="") + +if AUTH_LDAP_USER_SEARCHES: + import json + from django_auth_ldap.config import LDAPSearchUnion + + try: + ldap_search_definitions = json.loads(AUTH_LDAP_USER_SEARCHES) + except json.JSONDecodeError as e: + raise ValueError("Invalid JSON in AUTH_LDAP_USER_SEARCHES") from e + + if not isinstance(ldap_search_definitions, list): + raise ValueError("AUTH_LDAP_USER_SEARCHES must be a JSON list") + + ldap_searches = [] + for search_definition in ldap_search_definitions: + if not isinstance(search_definition, dict): + raise ValueError("Each entry must be an object") + + base_dn = search_definition.get("base") + filterstr = search_definition.get("filter") + + if not base_dn or not filterstr: + raise ValueError("Each LDAP search entry must define 'base' and 'filter'") + + ldap_searches.append( + LDAPSearch(base_dn, ldap.SCOPE_SUBTREE, filterstr) + ) + + if not ldap_searches: + raise ValueError("AUTH_LDAP_USER_SEARCHES cannot be empty") + + # Always use LDAPSearchUnion, even for a single search entry. + AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*ldap_searches) + +else: + # Fallback to single LDAP search configuration. + AUTH_LDAP_USER_SEARCH = LDAPSearch( + AUTH_LDAP_USER_DN, + ldap.SCOPE_SUBTREE, + AUTH_LDAP_USER_FILTERSTR, + ) # When AUTH_LDAP_AUTOCREATE_USER is True (default), a new DejaCode user will be # created in the database with the minimum permission (a read-only user). diff --git a/docs/application-settings.rst b/docs/application-settings.rst index d7e1903f..9a55b36e 100644 --- a/docs/application-settings.rst +++ b/docs/application-settings.rst @@ -383,6 +383,42 @@ It must return exactly one result for authentication to succeed. AUTH_LDAP_USER_DN="ou=users,dc=example,dc=com" AUTH_LDAP_USER_FILTERSTR="(uid=%(user)s)" +USER_SEARCHES +------------- + +To search across multiple LDAP locations, use +``AUTH_LDAP_USER_SEARCHES``. + +When this setting is provided, it overrides both +``AUTH_LDAP_USER_DN`` and ``AUTH_LDAP_USER_FILTERSTR``. + +The setting must contain a JSON list of search definitions. Each entry +must define: + +- ``base``: The base DN to search +- ``filter``: The LDAP filter (must include ``%(user)s``) + +All configured searches are combined using ``LDAPSearchUnion``. + +Example: + +.. code-block:: python + + AUTH_LDAP_USER_SEARCHES = """ + [ + { + "base": "ou=users,dc=example,dc=com", + "filter": "(uid=%(user)s)" + }, + { + "base": "ou=otherusers,dc=example,dc=com", + "filter": "(uid=%(user)s)" + } + ] + """ + +For usage in a ``docker.env`` file only a single line string is accepted. + AUTOCREATE_USER ---------------