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

UI name for the type of object this class synchronizes

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