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
KerberosSync( source: authentik.sources.kerberos.models.KerberosSource, task: authentik.tasks.models.Task)
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 )
user_manager: authentik.lib.sync.mapper.PropertyMappingManager
group_manager: authentik.lib.sync.mapper.PropertyMappingManager
@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