Compare commits
	
		
			5 Commits
		
	
	
		
			web/use-oa
			...
			sources/ld
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 57a38c93fc | |||
| 93b9dae178 | |||
| 589c123dc1 | |||
| a4d9f08095 | |||
| d856e403f8 | 
| @ -153,7 +153,7 @@ class Migration(migrations.Migration): | ||||
|                 ( | ||||
|                     "object_uniqueness_field", | ||||
|                     models.TextField( | ||||
|                         default="objectSid", help_text="Field which contains a unique Identifier." | ||||
|                         default="entryDN", help_text="Field which contains a unique Identifier." | ||||
|                     ), | ||||
|                 ), | ||||
|                 ("sync_groups", models.BooleanField(default=True)), | ||||
|  | ||||
| @ -88,7 +88,7 @@ class LDAPSource(Source): | ||||
|         help_text=_("Consider Objects matching this filter to be Groups."), | ||||
|     ) | ||||
|     object_uniqueness_field = models.TextField( | ||||
|         default="objectSid", help_text=_("Field which contains a unique Identifier.") | ||||
|         default="entryDN", help_text=_("Field which contains a unique Identifier.") | ||||
|     ) | ||||
|  | ||||
|     property_mappings_group = models.ManyToManyField( | ||||
|  | ||||
| @ -47,6 +47,15 @@ class BaseLDAPSynchronizer: | ||||
|         """UI name for the type of object this class synchronizes""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def get_unique_identifier(self, ldap_object: dict) -> str | None: | ||||
|         """Get unique identifier""" | ||||
|         attributes = ldap_object.get("attributes", {}) | ||||
|         if self._source.object_uniqueness_field in attributes: | ||||
|             return flatten(attributes[self._source.object_uniqueness_field]) | ||||
|         if self._source.object_uniqueness_field in ldap_object: | ||||
|             return flatten(ldap_object.get(self._source.object_uniqueness_field)) | ||||
|         return None | ||||
|  | ||||
|     def sync_full(self): | ||||
|         """Run full sync, this function should only be used in tests""" | ||||
|         if not settings.TEST:  # noqa | ||||
| @ -134,20 +143,22 @@ class BaseLDAPSynchronizer: | ||||
|                 cookie = None | ||||
|             yield self._connection.response | ||||
|  | ||||
|     def build_user_properties(self, user_dn: str, **kwargs) -> dict[str, Any]: | ||||
|     def build_user_properties(self, user_dn: str, uniq: 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 = self._build_object_properties( | ||||
|             user_dn, self._source.property_mappings, uniq, **kwargs | ||||
|         ) | ||||
|         props.setdefault("path", self._source.get_user_path()) | ||||
|         return props | ||||
|  | ||||
|     def build_group_properties(self, group_dn: str, **kwargs) -> dict[str, Any]: | ||||
|     def build_group_properties(self, group_dn: str, uniq: str, **kwargs) -> dict[str, Any]: | ||||
|         """Build attributes for Group object based on property mappings.""" | ||||
|         return self._build_object_properties( | ||||
|             group_dn, self._source.property_mappings_group, **kwargs | ||||
|             group_dn, self._source.property_mappings_group, uniq, **kwargs | ||||
|         ) | ||||
|  | ||||
|     def _build_object_properties( | ||||
|         self, object_dn: str, mappings: QuerySet, **kwargs | ||||
|         self, object_dn: str, mappings: QuerySet, uniq: str, **kwargs | ||||
|     ) -> dict[str, dict[Any, Any]]: | ||||
|         properties = {"attributes": {}} | ||||
|         for mapping in mappings.all().select_subclasses(): | ||||
| @ -180,10 +191,7 @@ class BaseLDAPSynchronizer: | ||||
|                 ).save() | ||||
|                 self._logger.warning("Mapping failed to evaluate", exc=exc, mapping=mapping) | ||||
|                 continue | ||||
|         if self._source.object_uniqueness_field in kwargs: | ||||
|             properties["attributes"][LDAP_UNIQUENESS] = flatten( | ||||
|                 kwargs.get(self._source.object_uniqueness_field) | ||||
|             ) | ||||
|         properties["attributes"][LDAP_UNIQUENESS] = uniq | ||||
|         properties["attributes"][LDAP_DISTINGUISHED_NAME] = object_dn | ||||
|         return properties | ||||
|  | ||||
|  | ||||
| @ -41,16 +41,16 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|                 continue | ||||
|             attributes = group.get("attributes", {}) | ||||
|             group_dn = flatten(flatten(group.get("entryDN", group.get("dn")))) | ||||
|             if self._source.object_uniqueness_field not in attributes: | ||||
|             uniq = self.get_unique_identifier(group) | ||||
|             if not uniq: | ||||
|                 self.message( | ||||
|                     f"Cannot find uniqueness field in attributes: '{group_dn}'", | ||||
|                     attributes=attributes.keys(), | ||||
|                     dn=group_dn, | ||||
|                 ) | ||||
|                 continue | ||||
|             uniq = flatten(attributes[self._source.object_uniqueness_field]) | ||||
|             try: | ||||
|                 defaults = self.build_group_properties(group_dn, **attributes) | ||||
|                 defaults = self.build_group_properties(group_dn, uniq, **attributes) | ||||
|                 defaults["parent"] = self._source.sync_parent_group | ||||
|                 if "name" not in defaults: | ||||
|                     raise IntegrityError("Name was not set by propertymappings") | ||||
|  | ||||
| @ -4,7 +4,7 @@ from collections.abc import Generator | ||||
| from typing import Any | ||||
|  | ||||
| from django.db.models import Q | ||||
| from ldap3 import SUBTREE | ||||
| from ldap3 import ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE | ||||
|  | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME | ||||
| @ -33,11 +33,7 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|             search_base=self.base_dn_groups, | ||||
|             search_filter=self._source.group_object_filter, | ||||
|             search_scope=SUBTREE, | ||||
|             attributes=[ | ||||
|                 self._source.group_membership_field, | ||||
|                 self._source.object_uniqueness_field, | ||||
|                 LDAP_DISTINGUISHED_NAME, | ||||
|             ], | ||||
|             attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES], | ||||
|             **kwargs, | ||||
|         ) | ||||
|  | ||||
| @ -80,7 +76,7 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|     def get_group(self, group_dict: dict[str, Any]) -> Group | None: | ||||
|         """Check if we fetched the group already, and if not cache it for later""" | ||||
|         group_dn = group_dict.get("attributes", {}).get(LDAP_DISTINGUISHED_NAME, []) | ||||
|         group_uniq = group_dict.get("attributes", {}).get(self._source.object_uniqueness_field, []) | ||||
|         group_uniq = self.get_unique_identifier(group_dict) | ||||
|         # group_uniq might be a single string or an array with (hopefully) a single string | ||||
|         if isinstance(group_uniq, list): | ||||
|             if len(group_uniq) < 1: | ||||
|  | ||||
| @ -43,16 +43,16 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|                 continue | ||||
|             attributes = user.get("attributes", {}) | ||||
|             user_dn = flatten(user.get("entryDN", user.get("dn"))) | ||||
|             if self._source.object_uniqueness_field not in attributes: | ||||
|             uniq = self.get_unique_identifier(user) | ||||
|             if not uniq: | ||||
|                 self.message( | ||||
|                     f"Cannot find uniqueness field in attributes: '{user_dn}'", | ||||
|                     attributes=attributes.keys(), | ||||
|                     dn=user_dn, | ||||
|                 ) | ||||
|                 continue | ||||
|             uniq = flatten(attributes[self._source.object_uniqueness_field]) | ||||
|             try: | ||||
|                 defaults = self.build_user_properties(user_dn, **attributes) | ||||
|                 defaults = self.build_user_properties(user_dn, uniq, **attributes) | ||||
|                 self._logger.debug("Writing user with attributes", **defaults) | ||||
|                 if "username" not in defaults: | ||||
|                     raise IntegrityError("Username was not set by propertymappings") | ||||
|  | ||||
| @ -41,7 +41,7 @@ def mock_ad_connection(password: str) -> Connection: | ||||
|     connection.strategy.add_entry( | ||||
|         "cn=group2,ou=groups,dc=goauthentik,dc=io", | ||||
|         { | ||||
|             "name": "test-group", | ||||
|             "name": "test-group2", | ||||
|             "objectClass": "group", | ||||
|             "distinguishedName": "cn=group2,ou=groups,dc=goauthentik,dc=io", | ||||
|         }, | ||||
| @ -61,18 +61,6 @@ def mock_ad_connection(password: str) -> Connection: | ||||
|             ), | ||||
|         }, | ||||
|     ) | ||||
|     # User without SID | ||||
|     connection.strategy.add_entry( | ||||
|         "cn=user1,ou=users,dc=goauthentik,dc=io", | ||||
|         { | ||||
|             "userPassword": "test1111", | ||||
|             "sAMAccountName": "user2_sn", | ||||
|             "name": "user1_sn", | ||||
|             "revision": 0, | ||||
|             "objectClass": "person", | ||||
|             "distinguishedName": "cn=user1,ou=users,dc=goauthentik,dc=io", | ||||
|         }, | ||||
|     ) | ||||
|     # Duplicate users | ||||
|     connection.strategy.add_entry( | ||||
|         "cn=user2,ou=users,dc=goauthentik,dc=io", | ||||
| @ -87,7 +75,7 @@ def mock_ad_connection(password: str) -> Connection: | ||||
|         }, | ||||
|     ) | ||||
|     connection.strategy.add_entry( | ||||
|         "cn=user3,ou=users,dc=goauthentik,dc=io", | ||||
|         "cn=user2,ou=users,dc=goauthentik,dc=io", | ||||
|         { | ||||
|             "userPassword": "test2222", | ||||
|             "sAMAccountName": "user2_sn", | ||||
| @ -95,7 +83,7 @@ def mock_ad_connection(password: str) -> Connection: | ||||
|             "revision": 0, | ||||
|             "objectSid": "unique-test2222", | ||||
|             "objectClass": "person", | ||||
|             "distinguishedName": "cn=user3,ou=users,dc=goauthentik,dc=io", | ||||
|             "distinguishedName": "cn=user2,ou=users,dc=goauthentik,dc=io", | ||||
|         }, | ||||
|     ) | ||||
|     connection.bind() | ||||
|  | ||||
| @ -108,12 +108,7 @@ class LDAPSyncTests(TestCase): | ||||
|         user = User.objects.create( | ||||
|             username="user0_sn", | ||||
|             attributes={ | ||||
|                 "ldap_uniq": ( | ||||
|                     "S-117-6648368-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-" | ||||
|                     "0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-" | ||||
|                     "0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-" | ||||
|                     "0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0" | ||||
|                 ), | ||||
|                 "ldap_uniq": "cn=user0,ou=foo,ou=users,dc=goauthentik,dc=io", | ||||
|                 "foo": "bar", | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
| @ -470,7 +470,7 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> { | ||||
|                     > | ||||
|                         <input | ||||
|                             type="text" | ||||
|                             value="${this.instance?.objectUniquenessField || "objectSid"}" | ||||
|                             value="${this.instance?.objectUniquenessField || "entryDN"}" | ||||
|                             class="pf-c-form-control" | ||||
|                             required | ||||
|                         /> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	