authentik.sources.ldap.sync.groups
Sync LDAP Users and groups into authentik
1"""Sync LDAP Users and groups 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 Group 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 GroupLDAPSourceConnection, 22 LDAPSource, 23 flatten, 24) 25from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer 26from authentik.tasks.models import Task 27 28 29class GroupLDAPSynchronizer(BaseLDAPSynchronizer): 30 """Sync LDAP Users and groups into authentik""" 31 32 def __init__(self, source: LDAPSource, task: Task): 33 super().__init__(source, task) 34 self._source = source 35 self.mapper = SourceMapper(source) 36 self.manager = self.mapper.get_manager(Group, ["ldap", "dn"]) 37 38 @staticmethod 39 def name() -> str: 40 return "groups" 41 42 def get_objects(self, **kwargs) -> Generator: 43 if not self._source.sync_groups: 44 self._task.info("Group syncing is disabled for this Source") 45 return iter(()) 46 return self.search_paginator( 47 search_base=self.base_dn_groups, 48 search_filter=self._source.group_object_filter, 49 search_scope=SUBTREE, 50 attributes=[ 51 ALL_ATTRIBUTES, 52 ALL_OPERATIONAL_ATTRIBUTES, 53 self._source.object_uniqueness_field, 54 ], 55 **kwargs, 56 ) 57 58 def sync(self, page_data: list) -> int: 59 """Iterate over all LDAP Groups and create authentik_core.Group instances""" 60 if not self._source.sync_groups: 61 self._task.info("Group syncing is disabled for this Source") 62 return -1 63 group_count = 0 64 for group_data in page_data: 65 if (attributes := self.get_attributes(group_data)) is None: 66 continue 67 group_dn = flatten(flatten(group_data.get("entryDN", group_data.get("dn")))) 68 if not (uniq := self.get_identifier(attributes)): 69 self._task.info( 70 f"Uniqueness field not found/not set in attributes: '{group_dn}'", 71 attributes=list(attributes.keys()), 72 dn=group_dn, 73 ) 74 continue 75 try: 76 defaults = { 77 k: flatten(v) 78 for k, v in self.mapper.build_object_properties( 79 object_type=Group, 80 manager=self.manager, 81 user=None, 82 request=None, 83 dn=group_dn, 84 ldap=attributes, 85 ).items() 86 } 87 if "name" not in defaults: 88 raise IntegrityError("Name was not set by propertymappings") 89 # Special check for `users` field, as this is an M2M relation, and cannot be sync'd 90 if "users" in defaults: 91 del defaults["users"] 92 parent = defaults.pop("parent", None) 93 action, connection = self.matcher.get_group_action(uniq, defaults) 94 95 created = False 96 if action == Action.ENROLL: 97 # Legacy fallback, in case the group only has an `ldap_uniq` attribute set, but 98 # no source connection exists yet 99 legacy_group = Group.objects.filter( 100 **{ 101 f"attributes__{LDAP_UNIQUENESS}": uniq, 102 } 103 ).first() 104 if legacy_group and LDAP_UNIQUENESS in legacy_group.attributes: 105 connection = GroupLDAPSourceConnection( 106 source=self._source, 107 group=legacy_group, 108 identifier=legacy_group.attributes.get(LDAP_UNIQUENESS), 109 ) 110 group = legacy_group 111 # Switch the action to update the attributes 112 action = Action.AUTH 113 else: 114 group = Group.objects.create(**defaults) 115 created = True 116 connection.group = group 117 connection.save() 118 119 if action in (Action.AUTH, Action.LINK): 120 group = connection.group 121 group.update_attributes(defaults) 122 elif action == Action.DENY: 123 continue 124 125 if parent: 126 group.parents.add(parent) 127 self._logger.debug("Created group with attributes", **defaults) 128 except SkipObjectException: 129 continue 130 except PropertyMappingExpressionException as exc: 131 raise StopSync(exc, None, exc.mapping) from exc 132 except (IntegrityError, FieldError, TypeError, AttributeError) as exc: 133 self._logger.debug("failed to create group", exc=exc) 134 Event.new( 135 EventAction.CONFIGURATION_ERROR, 136 message=( 137 "Failed to create group; " 138 "To merge new group with existing group, connect it via the LDAP Source's " 139 "'Synced Groups' tab." 140 ), 141 exception=exception_to_dict(exc), 142 source=self._source, 143 dn=group_dn, 144 ).save() 145 else: 146 self._logger.debug("Synced group", group=group.name, created=created) 147 group_count += 1 148 return group_count
30class GroupLDAPSynchronizer(BaseLDAPSynchronizer): 31 """Sync LDAP Users and groups into authentik""" 32 33 def __init__(self, source: LDAPSource, task: Task): 34 super().__init__(source, task) 35 self._source = source 36 self.mapper = SourceMapper(source) 37 self.manager = self.mapper.get_manager(Group, ["ldap", "dn"]) 38 39 @staticmethod 40 def name() -> str: 41 return "groups" 42 43 def get_objects(self, **kwargs) -> Generator: 44 if not self._source.sync_groups: 45 self._task.info("Group syncing is disabled for this Source") 46 return iter(()) 47 return self.search_paginator( 48 search_base=self.base_dn_groups, 49 search_filter=self._source.group_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 Groups and create authentik_core.Group instances""" 61 if not self._source.sync_groups: 62 self._task.info("Group syncing is disabled for this Source") 63 return -1 64 group_count = 0 65 for group_data in page_data: 66 if (attributes := self.get_attributes(group_data)) is None: 67 continue 68 group_dn = flatten(flatten(group_data.get("entryDN", group_data.get("dn")))) 69 if not (uniq := self.get_identifier(attributes)): 70 self._task.info( 71 f"Uniqueness field not found/not set in attributes: '{group_dn}'", 72 attributes=list(attributes.keys()), 73 dn=group_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=Group, 81 manager=self.manager, 82 user=None, 83 request=None, 84 dn=group_dn, 85 ldap=attributes, 86 ).items() 87 } 88 if "name" not in defaults: 89 raise IntegrityError("Name was not set by propertymappings") 90 # Special check for `users` field, as this is an M2M relation, and cannot be sync'd 91 if "users" in defaults: 92 del defaults["users"] 93 parent = defaults.pop("parent", None) 94 action, connection = self.matcher.get_group_action(uniq, defaults) 95 96 created = False 97 if action == Action.ENROLL: 98 # Legacy fallback, in case the group only has an `ldap_uniq` attribute set, but 99 # no source connection exists yet 100 legacy_group = Group.objects.filter( 101 **{ 102 f"attributes__{LDAP_UNIQUENESS}": uniq, 103 } 104 ).first() 105 if legacy_group and LDAP_UNIQUENESS in legacy_group.attributes: 106 connection = GroupLDAPSourceConnection( 107 source=self._source, 108 group=legacy_group, 109 identifier=legacy_group.attributes.get(LDAP_UNIQUENESS), 110 ) 111 group = legacy_group 112 # Switch the action to update the attributes 113 action = Action.AUTH 114 else: 115 group = Group.objects.create(**defaults) 116 created = True 117 connection.group = group 118 connection.save() 119 120 if action in (Action.AUTH, Action.LINK): 121 group = connection.group 122 group.update_attributes(defaults) 123 elif action == Action.DENY: 124 continue 125 126 if parent: 127 group.parents.add(parent) 128 self._logger.debug("Created group with attributes", **defaults) 129 except SkipObjectException: 130 continue 131 except PropertyMappingExpressionException as exc: 132 raise StopSync(exc, None, exc.mapping) from exc 133 except (IntegrityError, FieldError, TypeError, AttributeError) as exc: 134 self._logger.debug("failed to create group", exc=exc) 135 Event.new( 136 EventAction.CONFIGURATION_ERROR, 137 message=( 138 "Failed to create group; " 139 "To merge new group with existing group, connect it via the LDAP Source's " 140 "'Synced Groups' tab." 141 ), 142 exception=exception_to_dict(exc), 143 source=self._source, 144 dn=group_dn, 145 ).save() 146 else: 147 self._logger.debug("Synced group", group=group.name, created=created) 148 group_count += 1 149 return group_count
Sync LDAP Users and groups into authentik
GroupLDAPSynchronizer( source: authentik.sources.ldap.models.LDAPSource, task: authentik.tasks.models.Task)
def
get_objects(self, **kwargs) -> Generator:
43 def get_objects(self, **kwargs) -> Generator: 44 if not self._source.sync_groups: 45 self._task.info("Group syncing is disabled for this Source") 46 return iter(()) 47 return self.search_paginator( 48 search_base=self.base_dn_groups, 49 search_filter=self._source.group_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 )
Get objects from LDAP, implemented in subclass
def
sync(self, page_data: list) -> int:
59 def sync(self, page_data: list) -> int: 60 """Iterate over all LDAP Groups and create authentik_core.Group instances""" 61 if not self._source.sync_groups: 62 self._task.info("Group syncing is disabled for this Source") 63 return -1 64 group_count = 0 65 for group_data in page_data: 66 if (attributes := self.get_attributes(group_data)) is None: 67 continue 68 group_dn = flatten(flatten(group_data.get("entryDN", group_data.get("dn")))) 69 if not (uniq := self.get_identifier(attributes)): 70 self._task.info( 71 f"Uniqueness field not found/not set in attributes: '{group_dn}'", 72 attributes=list(attributes.keys()), 73 dn=group_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=Group, 81 manager=self.manager, 82 user=None, 83 request=None, 84 dn=group_dn, 85 ldap=attributes, 86 ).items() 87 } 88 if "name" not in defaults: 89 raise IntegrityError("Name was not set by propertymappings") 90 # Special check for `users` field, as this is an M2M relation, and cannot be sync'd 91 if "users" in defaults: 92 del defaults["users"] 93 parent = defaults.pop("parent", None) 94 action, connection = self.matcher.get_group_action(uniq, defaults) 95 96 created = False 97 if action == Action.ENROLL: 98 # Legacy fallback, in case the group only has an `ldap_uniq` attribute set, but 99 # no source connection exists yet 100 legacy_group = Group.objects.filter( 101 **{ 102 f"attributes__{LDAP_UNIQUENESS}": uniq, 103 } 104 ).first() 105 if legacy_group and LDAP_UNIQUENESS in legacy_group.attributes: 106 connection = GroupLDAPSourceConnection( 107 source=self._source, 108 group=legacy_group, 109 identifier=legacy_group.attributes.get(LDAP_UNIQUENESS), 110 ) 111 group = legacy_group 112 # Switch the action to update the attributes 113 action = Action.AUTH 114 else: 115 group = Group.objects.create(**defaults) 116 created = True 117 connection.group = group 118 connection.save() 119 120 if action in (Action.AUTH, Action.LINK): 121 group = connection.group 122 group.update_attributes(defaults) 123 elif action == Action.DENY: 124 continue 125 126 if parent: 127 group.parents.add(parent) 128 self._logger.debug("Created group with attributes", **defaults) 129 except SkipObjectException: 130 continue 131 except PropertyMappingExpressionException as exc: 132 raise StopSync(exc, None, exc.mapping) from exc 133 except (IntegrityError, FieldError, TypeError, AttributeError) as exc: 134 self._logger.debug("failed to create group", exc=exc) 135 Event.new( 136 EventAction.CONFIGURATION_ERROR, 137 message=( 138 "Failed to create group; " 139 "To merge new group with existing group, connect it via the LDAP Source's " 140 "'Synced Groups' tab." 141 ), 142 exception=exception_to_dict(exc), 143 source=self._source, 144 dn=group_dn, 145 ).save() 146 else: 147 self._logger.debug("Synced group", group=group.name, created=created) 148 group_count += 1 149 return group_count
Iterate over all LDAP Groups and create authentik_core.Group instances