authentik.sources.kerberos.sync

Sync Kerberos users into authentik

  1"""Sync Kerberos users into authentik"""
  2
  3from typing import Any
  4
  5from django.core.exceptions import FieldError
  6from django.db import IntegrityError, transaction
  7from kadmin import KAdmin
  8from structlog.stdlib import BoundLogger, get_logger
  9
 10from authentik.core.expression.exceptions import (
 11    PropertyMappingExpressionException,
 12    SkipObjectException,
 13)
 14from authentik.core.models import Group, User, UserTypes
 15from authentik.core.sources.mapper import SourceMapper
 16from authentik.core.sources.matcher import Action, SourceMatcher
 17from authentik.events.models import Event, EventAction
 18from authentik.lib.sync.mapper import PropertyMappingManager
 19from authentik.lib.sync.outgoing.exceptions import StopSync
 20from authentik.sources.kerberos.models import (
 21    GroupKerberosSourceConnection,
 22    KerberosSource,
 23    Krb5ConfContext,
 24    UserKerberosSourceConnection,
 25)
 26from authentik.tasks.models import Task
 27
 28
 29class KerberosSync:
 30    """Sync Kerberos users into authentik"""
 31
 32    _source: KerberosSource
 33    _task: Task
 34    _logger: BoundLogger
 35    _connection: KAdmin
 36    mapper: SourceMapper
 37    user_manager: PropertyMappingManager
 38    group_manager: PropertyMappingManager
 39    matcher: SourceMatcher
 40
 41    def __init__(self, source: KerberosSource, task: Task):
 42        self._source = source
 43        self._task = task
 44        with Krb5ConfContext(self._source):
 45            self._connection = self._source.connection()
 46        self._logger = get_logger().bind(source=self._source, syncer=self.__class__.__name__)
 47        self.mapper = SourceMapper(self._source)
 48        self.user_manager = self.mapper.get_manager(User, ["principal", "principal_obj"])
 49        self.group_manager = self.mapper.get_manager(
 50            Group, ["group_id", "principal", "principal_obj"]
 51        )
 52        self.matcher = SourceMatcher(
 53            self._source, UserKerberosSourceConnection, GroupKerberosSourceConnection
 54        )
 55
 56    @staticmethod
 57    def name() -> str:
 58        """UI name for the type of object this class synchronizes"""
 59        return "users"
 60
 61    def _handle_principal(self, principal: str) -> bool:
 62        try:
 63            # TODO: handle permission error
 64            principal_obj = self._connection.get_principal(principal)
 65
 66            defaults = self.mapper.build_object_properties(
 67                object_type=User,
 68                manager=self.user_manager,
 69                user=None,
 70                request=None,
 71                principal=principal,
 72                principal_obj=principal_obj,
 73            )
 74            self._logger.debug("Writing user with attributes", **defaults)
 75            if "username" not in defaults:
 76                raise IntegrityError("Username was not set by propertymappings")
 77
 78            action, connection = self.matcher.get_user_action(principal, defaults)
 79            self._logger.debug("Action returned", action=action, connection=connection)
 80            if action == Action.DENY:
 81                return False
 82
 83            group_properties = {
 84                group_id: self.mapper.build_object_properties(
 85                    object_type=Group,
 86                    manager=self.group_manager,
 87                    user=None,
 88                    request=None,
 89                    group_id=group_id,
 90                    principal=principal,
 91                    principal_obj=principal_obj,
 92                )
 93                for group_id in defaults.pop("groups", [])
 94            }
 95
 96            if action == Action.ENROLL:
 97                user = User.objects.create(**defaults)
 98                if user.type == UserTypes.INTERNAL_SERVICE_ACCOUNT:
 99                    user.set_unusable_password()
100                    user.save()
101                connection.user = user
102                connection.save()
103            elif action in (Action.AUTH, Action.LINK):
104                user = connection.user
105                user.update_attributes(defaults)
106            else:
107                return False
108
109            groups: list[Group] = []
110            for group_id, properties in group_properties.items():
111                group = self._handle_group(group_id, properties)
112                if group:
113                    groups.append(group)
114
115            with transaction.atomic():
116                user.groups.remove(*user.groups.filter(groupsourceconnection__source=self._source))
117                user.groups.add(*groups)
118
119        except PropertyMappingExpressionException as exc:
120            raise StopSync(exc, None, exc.mapping) from exc
121        except SkipObjectException:
122            return False
123        except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
124            Event.new(
125                EventAction.CONFIGURATION_ERROR,
126                message=(f"Failed to create user: {str(exc)} "),
127                source=self._source,
128                principal=principal,
129            ).save()
130            return False
131        self._logger.debug("Synced User", user=user.username)
132        return True
133
134    def _handle_group(
135        self, group_id: str, defaults: dict[str, Any | dict[str, Any]]
136    ) -> Group | None:
137        action, connection = self.matcher.get_group_action(group_id, defaults)
138        if action == Action.DENY:
139            return None
140        if action == Action.ENROLL:
141            group = Group.objects.create(**defaults)
142            connection.group = group
143            connection.save()
144            return group
145        if action in (Action.AUTH, Action.LINK):
146            group = connection.group
147            group.update_attributes(defaults)
148            connection.save()
149            return group
150        return None
151
152    def sync(self) -> int:
153        """Iterate over all Kerberos users and create authentik_core.User instances"""
154        if not self._source.enabled or not self._source.sync_users:
155            self._task.info("Source is disabled or user syncing is disabled for this Source")
156            return -1
157
158        user_count = 0
159        with Krb5ConfContext(self._source):
160            for principal in self._connection.list_principals(None):
161                if self._handle_principal(principal):
162                    user_count += 1
163        return user_count
class KerberosSync:
 30class KerberosSync:
 31    """Sync Kerberos users into authentik"""
 32
 33    _source: KerberosSource
 34    _task: Task
 35    _logger: BoundLogger
 36    _connection: KAdmin
 37    mapper: SourceMapper
 38    user_manager: PropertyMappingManager
 39    group_manager: PropertyMappingManager
 40    matcher: SourceMatcher
 41
 42    def __init__(self, source: KerberosSource, task: Task):
 43        self._source = source
 44        self._task = task
 45        with Krb5ConfContext(self._source):
 46            self._connection = self._source.connection()
 47        self._logger = get_logger().bind(source=self._source, syncer=self.__class__.__name__)
 48        self.mapper = SourceMapper(self._source)
 49        self.user_manager = self.mapper.get_manager(User, ["principal", "principal_obj"])
 50        self.group_manager = self.mapper.get_manager(
 51            Group, ["group_id", "principal", "principal_obj"]
 52        )
 53        self.matcher = SourceMatcher(
 54            self._source, UserKerberosSourceConnection, GroupKerberosSourceConnection
 55        )
 56
 57    @staticmethod
 58    def name() -> str:
 59        """UI name for the type of object this class synchronizes"""
 60        return "users"
 61
 62    def _handle_principal(self, principal: str) -> bool:
 63        try:
 64            # TODO: handle permission error
 65            principal_obj = self._connection.get_principal(principal)
 66
 67            defaults = self.mapper.build_object_properties(
 68                object_type=User,
 69                manager=self.user_manager,
 70                user=None,
 71                request=None,
 72                principal=principal,
 73                principal_obj=principal_obj,
 74            )
 75            self._logger.debug("Writing user with attributes", **defaults)
 76            if "username" not in defaults:
 77                raise IntegrityError("Username was not set by propertymappings")
 78
 79            action, connection = self.matcher.get_user_action(principal, defaults)
 80            self._logger.debug("Action returned", action=action, connection=connection)
 81            if action == Action.DENY:
 82                return False
 83
 84            group_properties = {
 85                group_id: self.mapper.build_object_properties(
 86                    object_type=Group,
 87                    manager=self.group_manager,
 88                    user=None,
 89                    request=None,
 90                    group_id=group_id,
 91                    principal=principal,
 92                    principal_obj=principal_obj,
 93                )
 94                for group_id in defaults.pop("groups", [])
 95            }
 96
 97            if action == Action.ENROLL:
 98                user = User.objects.create(**defaults)
 99                if user.type == UserTypes.INTERNAL_SERVICE_ACCOUNT:
100                    user.set_unusable_password()
101                    user.save()
102                connection.user = user
103                connection.save()
104            elif action in (Action.AUTH, Action.LINK):
105                user = connection.user
106                user.update_attributes(defaults)
107            else:
108                return False
109
110            groups: list[Group] = []
111            for group_id, properties in group_properties.items():
112                group = self._handle_group(group_id, properties)
113                if group:
114                    groups.append(group)
115
116            with transaction.atomic():
117                user.groups.remove(*user.groups.filter(groupsourceconnection__source=self._source))
118                user.groups.add(*groups)
119
120        except PropertyMappingExpressionException as exc:
121            raise StopSync(exc, None, exc.mapping) from exc
122        except SkipObjectException:
123            return False
124        except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
125            Event.new(
126                EventAction.CONFIGURATION_ERROR,
127                message=(f"Failed to create user: {str(exc)} "),
128                source=self._source,
129                principal=principal,
130            ).save()
131            return False
132        self._logger.debug("Synced User", user=user.username)
133        return True
134
135    def _handle_group(
136        self, group_id: str, defaults: dict[str, Any | dict[str, Any]]
137    ) -> Group | None:
138        action, connection = self.matcher.get_group_action(group_id, defaults)
139        if action == Action.DENY:
140            return None
141        if action == Action.ENROLL:
142            group = Group.objects.create(**defaults)
143            connection.group = group
144            connection.save()
145            return group
146        if action in (Action.AUTH, Action.LINK):
147            group = connection.group
148            group.update_attributes(defaults)
149            connection.save()
150            return group
151        return None
152
153    def sync(self) -> int:
154        """Iterate over all Kerberos users and create authentik_core.User instances"""
155        if not self._source.enabled or not self._source.sync_users:
156            self._task.info("Source is disabled or user syncing is disabled for this Source")
157            return -1
158
159        user_count = 0
160        with Krb5ConfContext(self._source):
161            for principal in self._connection.list_principals(None):
162                if self._handle_principal(principal):
163                    user_count += 1
164        return user_count

Sync Kerberos users into authentik

42    def __init__(self, source: KerberosSource, task: Task):
43        self._source = source
44        self._task = task
45        with Krb5ConfContext(self._source):
46            self._connection = self._source.connection()
47        self._logger = get_logger().bind(source=self._source, syncer=self.__class__.__name__)
48        self.mapper = SourceMapper(self._source)
49        self.user_manager = self.mapper.get_manager(User, ["principal", "principal_obj"])
50        self.group_manager = self.mapper.get_manager(
51            Group, ["group_id", "principal", "principal_obj"]
52        )
53        self.matcher = SourceMatcher(
54            self._source, UserKerberosSourceConnection, GroupKerberosSourceConnection
55        )
@staticmethod
def name() -> str:
57    @staticmethod
58    def name() -> str:
59        """UI name for the type of object this class synchronizes"""
60        return "users"

UI name for the type of object this class synchronizes

def sync(self) -> int:
153    def sync(self) -> int:
154        """Iterate over all Kerberos users and create authentik_core.User instances"""
155        if not self._source.enabled or not self._source.sync_users:
156            self._task.info("Source is disabled or user syncing is disabled for this Source")
157            return -1
158
159        user_count = 0
160        with Krb5ConfContext(self._source):
161            for principal in self._connection.list_principals(None):
162                if self._handle_principal(principal):
163                    user_count += 1
164        return user_count

Iterate over all Kerberos users and create authentik_core.User instances