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.events.models import Event, EventAction
 16from authentik.lib.sync.outgoing.exceptions import StopSync
 17from authentik.sources.ldap.models import (
 18    LDAP_UNIQUENESS,
 19    GroupLDAPSourceConnection,
 20    LDAPSource,
 21    flatten,
 22)
 23from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
 24from authentik.tasks.models import Task
 25
 26
 27class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
 28    """Sync LDAP Users and groups into authentik"""
 29
 30    def __init__(self, source: LDAPSource, task: Task):
 31        super().__init__(source, task)
 32        self._source = source
 33        self.mapper = SourceMapper(source)
 34        self.manager = self.mapper.get_manager(Group, ["ldap", "dn"])
 35
 36    @staticmethod
 37    def name() -> str:
 38        return "groups"
 39
 40    def get_objects(self, **kwargs) -> Generator:
 41        if not self._source.sync_groups:
 42            self._task.info("Group syncing is disabled for this Source")
 43            return iter(())
 44        return self.search_paginator(
 45            search_base=self.base_dn_groups,
 46            search_filter=self._source.group_object_filter,
 47            search_scope=SUBTREE,
 48            attributes=[
 49                ALL_ATTRIBUTES,
 50                ALL_OPERATIONAL_ATTRIBUTES,
 51                self._source.object_uniqueness_field,
 52            ],
 53            **kwargs,
 54        )
 55
 56    def sync(self, page_data: list) -> int:
 57        """Iterate over all LDAP Groups and create authentik_core.Group instances"""
 58        if not self._source.sync_groups:
 59            self._task.info("Group syncing is disabled for this Source")
 60            return -1
 61        group_count = 0
 62        for group_data in page_data:
 63            if (attributes := self.get_attributes(group_data)) is None:
 64                continue
 65            group_dn = flatten(flatten(group_data.get("entryDN", group_data.get("dn"))))
 66            if not (uniq := self.get_identifier(attributes)):
 67                self._task.info(
 68                    f"Uniqueness field not found/not set in attributes: '{group_dn}'",
 69                    attributes=list(attributes.keys()),
 70                    dn=group_dn,
 71                )
 72                continue
 73            try:
 74                defaults = {
 75                    k: flatten(v)
 76                    for k, v in self.mapper.build_object_properties(
 77                        object_type=Group,
 78                        manager=self.manager,
 79                        user=None,
 80                        request=None,
 81                        dn=group_dn,
 82                        ldap=attributes,
 83                    ).items()
 84                }
 85                if "name" not in defaults:
 86                    raise IntegrityError("Name was not set by propertymappings")
 87                # Special check for `users` field, as this is an M2M relation, and cannot be sync'd
 88                if "users" in defaults:
 89                    del defaults["users"]
 90                parent = defaults.pop("parent", None)
 91                group, created = Group.update_or_create_attributes(
 92                    {
 93                        f"attributes__{LDAP_UNIQUENESS}": uniq,
 94                    },
 95                    defaults,
 96                )
 97                if parent:
 98                    group.parents.add(parent)
 99                self._logger.debug("Created group with attributes", **defaults)
100                if not GroupLDAPSourceConnection.objects.filter(
101                    source=self._source, identifier=uniq
102                ):
103                    GroupLDAPSourceConnection.objects.create(
104                        source=self._source, group=group, identifier=uniq
105                    )
106            except SkipObjectException:
107                continue
108            except PropertyMappingExpressionException as exc:
109                raise StopSync(exc, None, exc.mapping) from exc
110            except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
111                Event.new(
112                    EventAction.CONFIGURATION_ERROR,
113                    message=(
114                        f"Failed to create group: {str(exc)} "
115                        "To merge new group with existing group, set the groups's "
116                        f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'"
117                    ),
118                    source=self._source,
119                    dn=group_dn,
120                ).save()
121            else:
122                self._logger.debug("Synced group", group=group.name, created=created)
123                group_count += 1
124        return group_count
class GroupLDAPSynchronizer(authentik.sources.ldap.sync.base.BaseLDAPSynchronizer):
 28class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
 29    """Sync LDAP Users and groups into authentik"""
 30
 31    def __init__(self, source: LDAPSource, task: Task):
 32        super().__init__(source, task)
 33        self._source = source
 34        self.mapper = SourceMapper(source)
 35        self.manager = self.mapper.get_manager(Group, ["ldap", "dn"])
 36
 37    @staticmethod
 38    def name() -> str:
 39        return "groups"
 40
 41    def get_objects(self, **kwargs) -> Generator:
 42        if not self._source.sync_groups:
 43            self._task.info("Group syncing is disabled for this Source")
 44            return iter(())
 45        return self.search_paginator(
 46            search_base=self.base_dn_groups,
 47            search_filter=self._source.group_object_filter,
 48            search_scope=SUBTREE,
 49            attributes=[
 50                ALL_ATTRIBUTES,
 51                ALL_OPERATIONAL_ATTRIBUTES,
 52                self._source.object_uniqueness_field,
 53            ],
 54            **kwargs,
 55        )
 56
 57    def sync(self, page_data: list) -> int:
 58        """Iterate over all LDAP Groups and create authentik_core.Group instances"""
 59        if not self._source.sync_groups:
 60            self._task.info("Group syncing is disabled for this Source")
 61            return -1
 62        group_count = 0
 63        for group_data in page_data:
 64            if (attributes := self.get_attributes(group_data)) is None:
 65                continue
 66            group_dn = flatten(flatten(group_data.get("entryDN", group_data.get("dn"))))
 67            if not (uniq := self.get_identifier(attributes)):
 68                self._task.info(
 69                    f"Uniqueness field not found/not set in attributes: '{group_dn}'",
 70                    attributes=list(attributes.keys()),
 71                    dn=group_dn,
 72                )
 73                continue
 74            try:
 75                defaults = {
 76                    k: flatten(v)
 77                    for k, v in self.mapper.build_object_properties(
 78                        object_type=Group,
 79                        manager=self.manager,
 80                        user=None,
 81                        request=None,
 82                        dn=group_dn,
 83                        ldap=attributes,
 84                    ).items()
 85                }
 86                if "name" not in defaults:
 87                    raise IntegrityError("Name was not set by propertymappings")
 88                # Special check for `users` field, as this is an M2M relation, and cannot be sync'd
 89                if "users" in defaults:
 90                    del defaults["users"]
 91                parent = defaults.pop("parent", None)
 92                group, created = Group.update_or_create_attributes(
 93                    {
 94                        f"attributes__{LDAP_UNIQUENESS}": uniq,
 95                    },
 96                    defaults,
 97                )
 98                if parent:
 99                    group.parents.add(parent)
100                self._logger.debug("Created group with attributes", **defaults)
101                if not GroupLDAPSourceConnection.objects.filter(
102                    source=self._source, identifier=uniq
103                ):
104                    GroupLDAPSourceConnection.objects.create(
105                        source=self._source, group=group, identifier=uniq
106                    )
107            except SkipObjectException:
108                continue
109            except PropertyMappingExpressionException as exc:
110                raise StopSync(exc, None, exc.mapping) from exc
111            except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
112                Event.new(
113                    EventAction.CONFIGURATION_ERROR,
114                    message=(
115                        f"Failed to create group: {str(exc)} "
116                        "To merge new group with existing group, set the groups's "
117                        f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'"
118                    ),
119                    source=self._source,
120                    dn=group_dn,
121                ).save()
122            else:
123                self._logger.debug("Synced group", group=group.name, created=created)
124                group_count += 1
125        return group_count

Sync LDAP Users and groups into authentik

GroupLDAPSynchronizer( source: authentik.sources.ldap.models.LDAPSource, task: authentik.tasks.models.Task)
31    def __init__(self, source: LDAPSource, task: Task):
32        super().__init__(source, task)
33        self._source = source
34        self.mapper = SourceMapper(source)
35        self.manager = self.mapper.get_manager(Group, ["ldap", "dn"])
mapper
manager
@staticmethod
def name() -> str:
37    @staticmethod
38    def name() -> str:
39        return "groups"

UI name for the type of object this class synchronizes

def get_objects(self, **kwargs) -> Generator:
41    def get_objects(self, **kwargs) -> Generator:
42        if not self._source.sync_groups:
43            self._task.info("Group syncing is disabled for this Source")
44            return iter(())
45        return self.search_paginator(
46            search_base=self.base_dn_groups,
47            search_filter=self._source.group_object_filter,
48            search_scope=SUBTREE,
49            attributes=[
50                ALL_ATTRIBUTES,
51                ALL_OPERATIONAL_ATTRIBUTES,
52                self._source.object_uniqueness_field,
53            ],
54            **kwargs,
55        )

Get objects from LDAP, implemented in subclass

def sync(self, page_data: list) -> int:
 57    def sync(self, page_data: list) -> int:
 58        """Iterate over all LDAP Groups and create authentik_core.Group instances"""
 59        if not self._source.sync_groups:
 60            self._task.info("Group syncing is disabled for this Source")
 61            return -1
 62        group_count = 0
 63        for group_data in page_data:
 64            if (attributes := self.get_attributes(group_data)) is None:
 65                continue
 66            group_dn = flatten(flatten(group_data.get("entryDN", group_data.get("dn"))))
 67            if not (uniq := self.get_identifier(attributes)):
 68                self._task.info(
 69                    f"Uniqueness field not found/not set in attributes: '{group_dn}'",
 70                    attributes=list(attributes.keys()),
 71                    dn=group_dn,
 72                )
 73                continue
 74            try:
 75                defaults = {
 76                    k: flatten(v)
 77                    for k, v in self.mapper.build_object_properties(
 78                        object_type=Group,
 79                        manager=self.manager,
 80                        user=None,
 81                        request=None,
 82                        dn=group_dn,
 83                        ldap=attributes,
 84                    ).items()
 85                }
 86                if "name" not in defaults:
 87                    raise IntegrityError("Name was not set by propertymappings")
 88                # Special check for `users` field, as this is an M2M relation, and cannot be sync'd
 89                if "users" in defaults:
 90                    del defaults["users"]
 91                parent = defaults.pop("parent", None)
 92                group, created = Group.update_or_create_attributes(
 93                    {
 94                        f"attributes__{LDAP_UNIQUENESS}": uniq,
 95                    },
 96                    defaults,
 97                )
 98                if parent:
 99                    group.parents.add(parent)
100                self._logger.debug("Created group with attributes", **defaults)
101                if not GroupLDAPSourceConnection.objects.filter(
102                    source=self._source, identifier=uniq
103                ):
104                    GroupLDAPSourceConnection.objects.create(
105                        source=self._source, group=group, identifier=uniq
106                    )
107            except SkipObjectException:
108                continue
109            except PropertyMappingExpressionException as exc:
110                raise StopSync(exc, None, exc.mapping) from exc
111            except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
112                Event.new(
113                    EventAction.CONFIGURATION_ERROR,
114                    message=(
115                        f"Failed to create group: {str(exc)} "
116                        "To merge new group with existing group, set the groups's "
117                        f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'"
118                    ),
119                    source=self._source,
120                    dn=group_dn,
121                ).save()
122            else:
123                self._logger.debug("Synced group", group=group.name, created=created)
124                group_count += 1
125        return group_count

Iterate over all LDAP Groups and create authentik_core.Group instances