diff --git a/authentik/sources/ldap/sync/base.py b/authentik/sources/ldap/sync/base.py index 2a769e101..a131d935c 100644 --- a/authentik/sources/ldap/sync/base.py +++ b/authentik/sources/ldap/sync/base.py @@ -133,7 +133,7 @@ class BaseLDAPSynchronizer: def build_user_properties(self, user_dn: str, **kwargs) -> dict[str, Any]: """Build attributes for User object based on property mappings.""" props = self._build_object_properties(user_dn, self._source.property_mappings, **kwargs) - props["path"] = self._source.get_user_path() + props.setdefault("path", self._source.get_user_path()) return props def build_group_properties(self, group_dn: str, **kwargs) -> dict[str, Any]: @@ -151,7 +151,9 @@ class BaseLDAPSynchronizer: continue mapping: LDAPPropertyMapping try: - value = mapping.evaluate(user=None, request=None, ldap=kwargs, dn=object_dn) + value = mapping.evaluate( + user=None, request=None, ldap=kwargs, dn=object_dn, source=self._source + ) if value is None: self._logger.warning("property mapping returned None", mapping=mapping) continue diff --git a/authentik/sources/ldap/tests/mock_ad.py b/authentik/sources/ldap/tests/mock_ad.py index 7a13e92aa..b0914bda4 100644 --- a/authentik/sources/ldap/tests/mock_ad.py +++ b/authentik/sources/ldap/tests/mock_ad.py @@ -55,7 +55,7 @@ def mock_ad_connection(password: str) -> Connection: "revision": 0, "objectSid": "user0", "objectClass": "person", - "distinguishedName": "cn=user0,ou=users,dc=goauthentik,dc=io", + "distinguishedName": "cn=user0,ou=foo,ou=users,dc=goauthentik,dc=io", "userAccountControl": ( UserAccountControl.ACCOUNTDISABLE + UserAccountControl.NORMAL_ACCOUNT ), diff --git a/authentik/sources/ldap/tests/test_sync.py b/authentik/sources/ldap/tests/test_sync.py index 6bf459017..13035ac2b 100644 --- a/authentik/sources/ldap/tests/test_sync.py +++ b/authentik/sources/ldap/tests/test_sync.py @@ -123,6 +123,7 @@ class LDAPSyncTests(TestCase): user = User.objects.filter(username="user0_sn").first() self.assertEqual(user.attributes["foo"], "bar") self.assertFalse(user.is_active) + self.assertEqual(user.path, "goauthentik.io/sources/ldap/users/foo") self.assertFalse(User.objects.filter(username="user1_sn").exists()) def test_sync_users_openldap(self): diff --git a/blueprints/system/sources-ldap.yaml b/blueprints/system/sources-ldap.yaml index fa0056390..dbf8891e7 100644 --- a/blueprints/system/sources-ldap.yaml +++ b/blueprints/system/sources-ldap.yaml @@ -4,6 +4,27 @@ metadata: blueprints.goauthentik.io/system: "true" name: System - LDAP Source - Mappings entries: + - identifiers: + managed: goauthentik.io/sources/ldap/default-dn-path + model: authentik_sources_ldap.ldappropertymapping + attrs: + name: "authentik default LDAP Mapping: DN to User Path" + object_field: "path" + expression: | + dn = ldap.get("distinguishedName") + path_elements = [] + for pair in dn.split(","): + attr, _, value = pair.partition("=") + # Ignore elements from the Root DSE and the canonical name of the object + if attr.lower() in ["cn", "dc"]: + continue + path_elements.append(value) + path_elements.reverse() + + path = source.get_user_path() + if len(path_elements) > 0: + path = f"{path}/{'/'.join(path_elements)}" + return path - identifiers: managed: goauthentik.io/sources/ldap/default-name model: authentik_sources_ldap.ldappropertymapping