authentik.sources.ldap.sync.users
Sync LDAP Users into authentik
1"""Sync LDAP Users into authentik""" 2 3from collections.abc import Generator 4 5from django.core.exceptions import FieldError 6from django.db.utils import IntegrityError 7from ldap3 import ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE 8 9from authentik.core.expression.exceptions import ( 10 PropertyMappingExpressionException, 11 SkipObjectException, 12) 13from authentik.core.models import User 14from authentik.core.sources.mapper import SourceMapper 15from authentik.core.sources.matcher import Action 16from authentik.events.models import Event, EventAction 17from authentik.lib.sync.outgoing.exceptions import StopSync 18from authentik.lib.utils.errors import exception_to_dict 19from authentik.sources.ldap.models import ( 20 LDAP_UNIQUENESS, 21 LDAPSource, 22 UserLDAPSourceConnection, 23 flatten, 24) 25from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer 26from authentik.sources.ldap.sync.vendor.freeipa import FreeIPA 27from authentik.sources.ldap.sync.vendor.ms_ad import MicrosoftActiveDirectory 28from authentik.tasks.models import Task 29 30 31class UserLDAPSynchronizer(BaseLDAPSynchronizer): 32 """Sync LDAP Users into authentik""" 33 34 def __init__(self, source: LDAPSource, task: Task): 35 super().__init__(source, task) 36 self.mapper = SourceMapper(source) 37 self.manager = self.mapper.get_manager(User, ["ldap", "dn"]) 38 39 @staticmethod 40 def name() -> str: 41 return "users" 42 43 def get_objects(self, **kwargs) -> Generator: 44 if not self._source.sync_users: 45 self._task.info("User syncing is disabled for this Source") 46 return iter(()) 47 return self.search_paginator( 48 search_base=self.base_dn_users, 49 search_filter=self._source.user_object_filter, 50 search_scope=SUBTREE, 51 attributes=[ 52 ALL_ATTRIBUTES, 53 ALL_OPERATIONAL_ATTRIBUTES, 54 self._source.object_uniqueness_field, 55 ], 56 **kwargs, 57 ) 58 59 def sync(self, page_data: list) -> int: 60 """Iterate over all LDAP Users and create authentik_core.User instances""" 61 if not self._source.sync_users: 62 self._task.info("User syncing is disabled for this Source") 63 return -1 64 user_count = 0 65 for user in page_data: 66 if (attributes := self.get_attributes(user)) is None: 67 continue 68 user_dn = flatten(user.get("entryDN", user.get("dn"))) 69 if not (uniq := self.get_identifier(attributes)): 70 self._task.info( 71 f"Uniqueness field not found/not set in attributes: '{user_dn}'", 72 attributes=list(attributes.keys()), 73 dn=user_dn, 74 ) 75 continue 76 try: 77 defaults = { 78 k: flatten(v) 79 for k, v in self.mapper.build_object_properties( 80 object_type=User, 81 manager=self.manager, 82 user=None, 83 request=None, 84 dn=user_dn, 85 ldap=attributes, 86 ).items() 87 } 88 self._logger.debug("Writing user with attributes", **defaults) 89 if "username" not in defaults: 90 raise IntegrityError("Username was not set by propertymappings") 91 action, connection = self.matcher.get_user_action(uniq, defaults) 92 created = False 93 if action == Action.ENROLL: 94 # Legacy fallback, in case the user only has an `ldap_uniq` attribute set, but 95 # no source connection exists yet 96 legacy_user = User.objects.filter( 97 **{ 98 f"attributes__{LDAP_UNIQUENESS}": uniq, 99 } 100 ).first() 101 if legacy_user and LDAP_UNIQUENESS in legacy_user.attributes: 102 connection = UserLDAPSourceConnection( 103 source=self._source, 104 user=legacy_user, 105 identifier=legacy_user.attributes.get(LDAP_UNIQUENESS), 106 ) 107 ak_user = legacy_user 108 # Switch the action to update the attributes 109 action = Action.AUTH 110 else: 111 ak_user = User.objects.create(**defaults) 112 created = True 113 connection.user = ak_user 114 connection.save() 115 116 if action in (Action.AUTH, Action.LINK): 117 ak_user = connection.user 118 ak_user.update_attributes(defaults) 119 elif action == Action.DENY: 120 continue 121 except PropertyMappingExpressionException as exc: 122 raise StopSync(exc, None, exc.mapping) from exc 123 except SkipObjectException: 124 continue 125 except (IntegrityError, FieldError, TypeError, AttributeError) as exc: 126 self._logger.debug("failed to create user", exc=exc) 127 Event.new( 128 EventAction.CONFIGURATION_ERROR, 129 message=( 130 "Failed to create user; " 131 "To merge new user with existing user, connect it via the LDAP Source's " 132 "'Synced Users' tab." 133 ), 134 exception=exception_to_dict(exc), 135 source=self._source, 136 dn=user_dn, 137 ).save() 138 else: 139 self._logger.debug("Synced User", user=ak_user.username, created=created) 140 user_count += 1 141 MicrosoftActiveDirectory(self._source, self._task).sync( 142 attributes, ak_user, created 143 ) 144 FreeIPA(self._source, self._task).sync(attributes, ak_user, created) 145 return user_count
32class UserLDAPSynchronizer(BaseLDAPSynchronizer): 33 """Sync LDAP Users into authentik""" 34 35 def __init__(self, source: LDAPSource, task: Task): 36 super().__init__(source, task) 37 self.mapper = SourceMapper(source) 38 self.manager = self.mapper.get_manager(User, ["ldap", "dn"]) 39 40 @staticmethod 41 def name() -> str: 42 return "users" 43 44 def get_objects(self, **kwargs) -> Generator: 45 if not self._source.sync_users: 46 self._task.info("User syncing is disabled for this Source") 47 return iter(()) 48 return self.search_paginator( 49 search_base=self.base_dn_users, 50 search_filter=self._source.user_object_filter, 51 search_scope=SUBTREE, 52 attributes=[ 53 ALL_ATTRIBUTES, 54 ALL_OPERATIONAL_ATTRIBUTES, 55 self._source.object_uniqueness_field, 56 ], 57 **kwargs, 58 ) 59 60 def sync(self, page_data: list) -> int: 61 """Iterate over all LDAP Users and create authentik_core.User instances""" 62 if not self._source.sync_users: 63 self._task.info("User syncing is disabled for this Source") 64 return -1 65 user_count = 0 66 for user in page_data: 67 if (attributes := self.get_attributes(user)) is None: 68 continue 69 user_dn = flatten(user.get("entryDN", user.get("dn"))) 70 if not (uniq := self.get_identifier(attributes)): 71 self._task.info( 72 f"Uniqueness field not found/not set in attributes: '{user_dn}'", 73 attributes=list(attributes.keys()), 74 dn=user_dn, 75 ) 76 continue 77 try: 78 defaults = { 79 k: flatten(v) 80 for k, v in self.mapper.build_object_properties( 81 object_type=User, 82 manager=self.manager, 83 user=None, 84 request=None, 85 dn=user_dn, 86 ldap=attributes, 87 ).items() 88 } 89 self._logger.debug("Writing user with attributes", **defaults) 90 if "username" not in defaults: 91 raise IntegrityError("Username was not set by propertymappings") 92 action, connection = self.matcher.get_user_action(uniq, defaults) 93 created = False 94 if action == Action.ENROLL: 95 # Legacy fallback, in case the user only has an `ldap_uniq` attribute set, but 96 # no source connection exists yet 97 legacy_user = User.objects.filter( 98 **{ 99 f"attributes__{LDAP_UNIQUENESS}": uniq, 100 } 101 ).first() 102 if legacy_user and LDAP_UNIQUENESS in legacy_user.attributes: 103 connection = UserLDAPSourceConnection( 104 source=self._source, 105 user=legacy_user, 106 identifier=legacy_user.attributes.get(LDAP_UNIQUENESS), 107 ) 108 ak_user = legacy_user 109 # Switch the action to update the attributes 110 action = Action.AUTH 111 else: 112 ak_user = User.objects.create(**defaults) 113 created = True 114 connection.user = ak_user 115 connection.save() 116 117 if action in (Action.AUTH, Action.LINK): 118 ak_user = connection.user 119 ak_user.update_attributes(defaults) 120 elif action == Action.DENY: 121 continue 122 except PropertyMappingExpressionException as exc: 123 raise StopSync(exc, None, exc.mapping) from exc 124 except SkipObjectException: 125 continue 126 except (IntegrityError, FieldError, TypeError, AttributeError) as exc: 127 self._logger.debug("failed to create user", exc=exc) 128 Event.new( 129 EventAction.CONFIGURATION_ERROR, 130 message=( 131 "Failed to create user; " 132 "To merge new user with existing user, connect it via the LDAP Source's " 133 "'Synced Users' tab." 134 ), 135 exception=exception_to_dict(exc), 136 source=self._source, 137 dn=user_dn, 138 ).save() 139 else: 140 self._logger.debug("Synced User", user=ak_user.username, created=created) 141 user_count += 1 142 MicrosoftActiveDirectory(self._source, self._task).sync( 143 attributes, ak_user, created 144 ) 145 FreeIPA(self._source, self._task).sync(attributes, ak_user, created) 146 return user_count
Sync LDAP Users into authentik
UserLDAPSynchronizer( source: authentik.sources.ldap.models.LDAPSource, task: authentik.tasks.models.Task)
def
get_objects(self, **kwargs) -> Generator:
44 def get_objects(self, **kwargs) -> Generator: 45 if not self._source.sync_users: 46 self._task.info("User syncing is disabled for this Source") 47 return iter(()) 48 return self.search_paginator( 49 search_base=self.base_dn_users, 50 search_filter=self._source.user_object_filter, 51 search_scope=SUBTREE, 52 attributes=[ 53 ALL_ATTRIBUTES, 54 ALL_OPERATIONAL_ATTRIBUTES, 55 self._source.object_uniqueness_field, 56 ], 57 **kwargs, 58 )
Get objects from LDAP, implemented in subclass
def
sync(self, page_data: list) -> int:
60 def sync(self, page_data: list) -> int: 61 """Iterate over all LDAP Users and create authentik_core.User instances""" 62 if not self._source.sync_users: 63 self._task.info("User syncing is disabled for this Source") 64 return -1 65 user_count = 0 66 for user in page_data: 67 if (attributes := self.get_attributes(user)) is None: 68 continue 69 user_dn = flatten(user.get("entryDN", user.get("dn"))) 70 if not (uniq := self.get_identifier(attributes)): 71 self._task.info( 72 f"Uniqueness field not found/not set in attributes: '{user_dn}'", 73 attributes=list(attributes.keys()), 74 dn=user_dn, 75 ) 76 continue 77 try: 78 defaults = { 79 k: flatten(v) 80 for k, v in self.mapper.build_object_properties( 81 object_type=User, 82 manager=self.manager, 83 user=None, 84 request=None, 85 dn=user_dn, 86 ldap=attributes, 87 ).items() 88 } 89 self._logger.debug("Writing user with attributes", **defaults) 90 if "username" not in defaults: 91 raise IntegrityError("Username was not set by propertymappings") 92 action, connection = self.matcher.get_user_action(uniq, defaults) 93 created = False 94 if action == Action.ENROLL: 95 # Legacy fallback, in case the user only has an `ldap_uniq` attribute set, but 96 # no source connection exists yet 97 legacy_user = User.objects.filter( 98 **{ 99 f"attributes__{LDAP_UNIQUENESS}": uniq, 100 } 101 ).first() 102 if legacy_user and LDAP_UNIQUENESS in legacy_user.attributes: 103 connection = UserLDAPSourceConnection( 104 source=self._source, 105 user=legacy_user, 106 identifier=legacy_user.attributes.get(LDAP_UNIQUENESS), 107 ) 108 ak_user = legacy_user 109 # Switch the action to update the attributes 110 action = Action.AUTH 111 else: 112 ak_user = User.objects.create(**defaults) 113 created = True 114 connection.user = ak_user 115 connection.save() 116 117 if action in (Action.AUTH, Action.LINK): 118 ak_user = connection.user 119 ak_user.update_attributes(defaults) 120 elif action == Action.DENY: 121 continue 122 except PropertyMappingExpressionException as exc: 123 raise StopSync(exc, None, exc.mapping) from exc 124 except SkipObjectException: 125 continue 126 except (IntegrityError, FieldError, TypeError, AttributeError) as exc: 127 self._logger.debug("failed to create user", exc=exc) 128 Event.new( 129 EventAction.CONFIGURATION_ERROR, 130 message=( 131 "Failed to create user; " 132 "To merge new user with existing user, connect it via the LDAP Source's " 133 "'Synced Users' tab." 134 ), 135 exception=exception_to_dict(exc), 136 source=self._source, 137 dn=user_dn, 138 ).save() 139 else: 140 self._logger.debug("Synced User", user=ak_user.username, created=created) 141 user_count += 1 142 MicrosoftActiveDirectory(self._source, self._task).sync( 143 attributes, ak_user, created 144 ) 145 FreeIPA(self._source, self._task).sync(attributes, ak_user, created) 146 return user_count
Iterate over all LDAP Users and create authentik_core.User instances