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
class GroupLDAPSynchronizer(authentik.sources.ldap.sync.base.BaseLDAPSynchronizer):
 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)
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"])
mapper
manager
@staticmethod
def name() -> str:
39    @staticmethod
40    def name() -> str:
41        return "groups"

UI name for the type of object this class synchronizes

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