authentik.sources.ldap.sync.membership
Sync LDAP Users and groups into authentik
1"""Sync LDAP Users and groups into authentik""" 2 3from collections.abc import Generator 4from typing import Any 5 6from django.db.models import Q 7from ldap3 import SUBTREE 8from ldap3.utils.conv import escape_filter_chars 9 10from authentik.core.models import Group, User 11from authentik.sources.ldap.models import LDAP_DISTINGUISHED_NAME, LDAP_UNIQUENESS, LDAPSource 12from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer 13from authentik.tasks.models import Task 14 15 16class MembershipLDAPSynchronizer(BaseLDAPSynchronizer): 17 """Sync LDAP Users and groups into authentik""" 18 19 group_cache: dict[str, Group] 20 21 def __init__(self, source: LDAPSource, task: Task): 22 super().__init__(source, task) 23 self.group_cache: dict[str, Group] = {} 24 25 @staticmethod 26 def name() -> str: 27 return "membership" 28 29 def get_objects(self, **kwargs) -> Generator: 30 if not self._source.sync_groups: 31 self._task.info("Group syncing is disabled for this Source") 32 return iter(()) 33 34 # If we are looking up groups from users, we don't need to fetch the group membership field 35 attributes = [self._source.object_uniqueness_field, LDAP_DISTINGUISHED_NAME] 36 if not self._source.lookup_groups_from_user: 37 attributes.append(self._source.group_membership_field) 38 39 return self.search_paginator( 40 search_base=self.base_dn_groups, 41 search_filter=self._source.group_object_filter, 42 search_scope=SUBTREE, 43 attributes=attributes, 44 **kwargs, 45 ) 46 47 def sync(self, page_data: list) -> int: 48 """Iterate over all Users and assign Groups using memberOf Field""" 49 if not self._source.sync_groups: 50 self._task.info("Group syncing is disabled for this Source") 51 return -1 52 membership_count = 0 53 for group_data in page_data: 54 if self._source.lookup_groups_from_user: 55 group_dn = group_data.get("dn", {}) 56 escaped_dn = escape_filter_chars(group_dn) 57 group_filter = f"({self._source.group_membership_field}={escaped_dn})" 58 group_members = self._source.connection().extend.standard.paged_search( 59 search_base=self.base_dn_users, 60 search_filter=group_filter, 61 search_scope=SUBTREE, 62 attributes=[self._source.object_uniqueness_field], 63 ) 64 members = [] 65 for group_member in group_members: 66 group_member_dn = group_member.get("dn", {}) 67 members.append(group_member_dn) 68 else: 69 if (attributes := self.get_attributes(group_data)) is None: 70 continue 71 members = attributes.get(self._source.group_membership_field, []) 72 73 group = self.get_group(group_data) 74 if not group: 75 continue 76 77 users = User.objects.filter( 78 Q(**{f"attributes__{self._source.user_membership_attribute}__in": members}) 79 | Q( 80 **{ 81 f"attributes__{self._source.user_membership_attribute}__isnull": True, 82 "groups__in": [group], 83 } 84 ) 85 ).distinct() 86 membership_count += 1 87 membership_count += users.count() 88 group.users.set(users) 89 group.save() 90 self._logger.debug("Successfully updated group membership") 91 return membership_count 92 93 def get_group(self, group_dict: dict[str, Any]) -> Group | None: 94 """Check if we fetched the group already, and if not cache it for later""" 95 group_dn = group_dict.get("attributes", {}).get(LDAP_DISTINGUISHED_NAME, []) 96 group_uniq = group_dict.get("attributes", {}).get(self._source.object_uniqueness_field, []) 97 # group_uniq might be a single string or an array with (hopefully) a single string 98 if isinstance(group_uniq, list): 99 if len(group_uniq) < 1: 100 self._task.info( 101 f"Group does not have a uniqueness attribute: '{group_dn}'", 102 group=group_dn, 103 ) 104 return None 105 group_uniq = group_uniq[0] 106 if group_uniq not in self.group_cache: 107 groups = Group.objects.filter(**{f"attributes__{LDAP_UNIQUENESS}": group_uniq}) 108 if not groups.exists(): 109 if self._source.sync_groups: 110 self._task.info( 111 f"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'", 112 group=group_dn, 113 ) 114 return None 115 self.group_cache[group_uniq] = groups.first() 116 return self.group_cache[group_uniq]
17class MembershipLDAPSynchronizer(BaseLDAPSynchronizer): 18 """Sync LDAP Users and groups into authentik""" 19 20 group_cache: dict[str, Group] 21 22 def __init__(self, source: LDAPSource, task: Task): 23 super().__init__(source, task) 24 self.group_cache: dict[str, Group] = {} 25 26 @staticmethod 27 def name() -> str: 28 return "membership" 29 30 def get_objects(self, **kwargs) -> Generator: 31 if not self._source.sync_groups: 32 self._task.info("Group syncing is disabled for this Source") 33 return iter(()) 34 35 # If we are looking up groups from users, we don't need to fetch the group membership field 36 attributes = [self._source.object_uniqueness_field, LDAP_DISTINGUISHED_NAME] 37 if not self._source.lookup_groups_from_user: 38 attributes.append(self._source.group_membership_field) 39 40 return self.search_paginator( 41 search_base=self.base_dn_groups, 42 search_filter=self._source.group_object_filter, 43 search_scope=SUBTREE, 44 attributes=attributes, 45 **kwargs, 46 ) 47 48 def sync(self, page_data: list) -> int: 49 """Iterate over all Users and assign Groups using memberOf Field""" 50 if not self._source.sync_groups: 51 self._task.info("Group syncing is disabled for this Source") 52 return -1 53 membership_count = 0 54 for group_data in page_data: 55 if self._source.lookup_groups_from_user: 56 group_dn = group_data.get("dn", {}) 57 escaped_dn = escape_filter_chars(group_dn) 58 group_filter = f"({self._source.group_membership_field}={escaped_dn})" 59 group_members = self._source.connection().extend.standard.paged_search( 60 search_base=self.base_dn_users, 61 search_filter=group_filter, 62 search_scope=SUBTREE, 63 attributes=[self._source.object_uniqueness_field], 64 ) 65 members = [] 66 for group_member in group_members: 67 group_member_dn = group_member.get("dn", {}) 68 members.append(group_member_dn) 69 else: 70 if (attributes := self.get_attributes(group_data)) is None: 71 continue 72 members = attributes.get(self._source.group_membership_field, []) 73 74 group = self.get_group(group_data) 75 if not group: 76 continue 77 78 users = User.objects.filter( 79 Q(**{f"attributes__{self._source.user_membership_attribute}__in": members}) 80 | Q( 81 **{ 82 f"attributes__{self._source.user_membership_attribute}__isnull": True, 83 "groups__in": [group], 84 } 85 ) 86 ).distinct() 87 membership_count += 1 88 membership_count += users.count() 89 group.users.set(users) 90 group.save() 91 self._logger.debug("Successfully updated group membership") 92 return membership_count 93 94 def get_group(self, group_dict: dict[str, Any]) -> Group | None: 95 """Check if we fetched the group already, and if not cache it for later""" 96 group_dn = group_dict.get("attributes", {}).get(LDAP_DISTINGUISHED_NAME, []) 97 group_uniq = group_dict.get("attributes", {}).get(self._source.object_uniqueness_field, []) 98 # group_uniq might be a single string or an array with (hopefully) a single string 99 if isinstance(group_uniq, list): 100 if len(group_uniq) < 1: 101 self._task.info( 102 f"Group does not have a uniqueness attribute: '{group_dn}'", 103 group=group_dn, 104 ) 105 return None 106 group_uniq = group_uniq[0] 107 if group_uniq not in self.group_cache: 108 groups = Group.objects.filter(**{f"attributes__{LDAP_UNIQUENESS}": group_uniq}) 109 if not groups.exists(): 110 if self._source.sync_groups: 111 self._task.info( 112 f"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'", 113 group=group_dn, 114 ) 115 return None 116 self.group_cache[group_uniq] = groups.first() 117 return self.group_cache[group_uniq]
Sync LDAP Users and groups into authentik
MembershipLDAPSynchronizer( source: authentik.sources.ldap.models.LDAPSource, task: authentik.tasks.models.Task)
group_cache: dict[str, authentik.core.models.Group]
def
get_objects(self, **kwargs) -> Generator:
30 def get_objects(self, **kwargs) -> Generator: 31 if not self._source.sync_groups: 32 self._task.info("Group syncing is disabled for this Source") 33 return iter(()) 34 35 # If we are looking up groups from users, we don't need to fetch the group membership field 36 attributes = [self._source.object_uniqueness_field, LDAP_DISTINGUISHED_NAME] 37 if not self._source.lookup_groups_from_user: 38 attributes.append(self._source.group_membership_field) 39 40 return self.search_paginator( 41 search_base=self.base_dn_groups, 42 search_filter=self._source.group_object_filter, 43 search_scope=SUBTREE, 44 attributes=attributes, 45 **kwargs, 46 )
Get objects from LDAP, implemented in subclass
def
sync(self, page_data: list) -> int:
48 def sync(self, page_data: list) -> int: 49 """Iterate over all Users and assign Groups using memberOf Field""" 50 if not self._source.sync_groups: 51 self._task.info("Group syncing is disabled for this Source") 52 return -1 53 membership_count = 0 54 for group_data in page_data: 55 if self._source.lookup_groups_from_user: 56 group_dn = group_data.get("dn", {}) 57 escaped_dn = escape_filter_chars(group_dn) 58 group_filter = f"({self._source.group_membership_field}={escaped_dn})" 59 group_members = self._source.connection().extend.standard.paged_search( 60 search_base=self.base_dn_users, 61 search_filter=group_filter, 62 search_scope=SUBTREE, 63 attributes=[self._source.object_uniqueness_field], 64 ) 65 members = [] 66 for group_member in group_members: 67 group_member_dn = group_member.get("dn", {}) 68 members.append(group_member_dn) 69 else: 70 if (attributes := self.get_attributes(group_data)) is None: 71 continue 72 members = attributes.get(self._source.group_membership_field, []) 73 74 group = self.get_group(group_data) 75 if not group: 76 continue 77 78 users = User.objects.filter( 79 Q(**{f"attributes__{self._source.user_membership_attribute}__in": members}) 80 | Q( 81 **{ 82 f"attributes__{self._source.user_membership_attribute}__isnull": True, 83 "groups__in": [group], 84 } 85 ) 86 ).distinct() 87 membership_count += 1 88 membership_count += users.count() 89 group.users.set(users) 90 group.save() 91 self._logger.debug("Successfully updated group membership") 92 return membership_count
Iterate over all Users and assign Groups using memberOf Field
94 def get_group(self, group_dict: dict[str, Any]) -> Group | None: 95 """Check if we fetched the group already, and if not cache it for later""" 96 group_dn = group_dict.get("attributes", {}).get(LDAP_DISTINGUISHED_NAME, []) 97 group_uniq = group_dict.get("attributes", {}).get(self._source.object_uniqueness_field, []) 98 # group_uniq might be a single string or an array with (hopefully) a single string 99 if isinstance(group_uniq, list): 100 if len(group_uniq) < 1: 101 self._task.info( 102 f"Group does not have a uniqueness attribute: '{group_dn}'", 103 group=group_dn, 104 ) 105 return None 106 group_uniq = group_uniq[0] 107 if group_uniq not in self.group_cache: 108 groups = Group.objects.filter(**{f"attributes__{LDAP_UNIQUENESS}": group_uniq}) 109 if not groups.exists(): 110 if self._source.sync_groups: 111 self._task.info( 112 f"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'", 113 group=group_dn, 114 ) 115 return None 116 self.group_cache[group_uniq] = groups.first() 117 return self.group_cache[group_uniq]
Check if we fetched the group already, and if not cache it for later