authentik.enterprise.providers.ssf.signals
1from hashlib import sha256 2 3from django.db.models import Model 4from django.db.models.signals import post_delete, post_save, pre_delete 5from django.dispatch import receiver 6 7from authentik.core.models import ( 8 USER_PATH_SYSTEM_PREFIX, 9 AuthenticatedSession, 10 Token, 11 TokenIntents, 12 User, 13 UserTypes, 14) 15from authentik.core.signals import password_changed, password_hash_changed 16from authentik.enterprise.providers.ssf.models import ( 17 EventTypes, 18 SSFProvider, 19) 20from authentik.enterprise.providers.ssf.tasks import send_ssf_events 21from authentik.events.middleware import audit_ignore 22from authentik.stages.authenticator.models import Device 23from authentik.stages.authenticator_duo.models import DuoDevice 24from authentik.stages.authenticator_static.models import StaticDevice 25from authentik.stages.authenticator_totp.models import TOTPDevice 26from authentik.stages.authenticator_webauthn.models import ( 27 UNKNOWN_DEVICE_TYPE_AAGUID, 28 WebAuthnDevice, 29) 30 31USER_PATH_PROVIDERS_SSF = USER_PATH_SYSTEM_PREFIX + "/providers/ssf" 32 33 34@receiver(post_save, sender=SSFProvider) 35def ssf_providers_post_save(sender: type[Model], instance: SSFProvider, created: bool, **_): 36 """Create service account before provider is saved""" 37 identifier = instance.service_account_identifier 38 user, _ = User.objects.update_or_create( 39 username=identifier, 40 defaults={ 41 "name": f"SSF Provider {instance.name} Service-Account", 42 "type": UserTypes.INTERNAL_SERVICE_ACCOUNT, 43 "path": USER_PATH_PROVIDERS_SSF, 44 }, 45 ) 46 user.assign_perms_to_managed_role("authentik_providers_ssf.add_stream", instance) 47 token, token_created = Token.objects.update_or_create( 48 identifier=identifier, 49 defaults={ 50 "user": user, 51 "intent": TokenIntents.INTENT_API, 52 "expiring": False, 53 "managed": f"goauthentik.io/providers/ssf/{instance.pk}", 54 }, 55 ) 56 if created or token_created: 57 with audit_ignore(): 58 instance.token = token 59 instance.save() 60 61 62@receiver(pre_delete, sender=AuthenticatedSession) 63def ssf_user_session_delete_session_revoked(sender, instance: AuthenticatedSession, **_): 64 """Session revoked trigger (users' session has been deleted) 65 66 As this signal is also triggered with a regular logout, we can't be sure 67 if the session has been deleted by an admin or by the user themselves.""" 68 send_ssf_events( 69 EventTypes.CAEP_SESSION_REVOKED, 70 { 71 "initiating_entity": "user", 72 }, 73 sub_id={ 74 "format": "complex", 75 "session": { 76 "format": "opaque", 77 "id": sha256(instance.session.session_key.encode("ascii")).hexdigest(), 78 }, 79 "user": { 80 "format": "email", 81 "email": instance.user.email, 82 }, 83 }, 84 ) 85 86 87def _send_password_credential_change(user: User, change_type: str): 88 """Credential change trigger (password changed)""" 89 send_ssf_events( 90 EventTypes.CAEP_CREDENTIAL_CHANGE, 91 { 92 "credential_type": "password", 93 "change_type": change_type, 94 }, 95 sub_id={ 96 "format": "complex", 97 "user": { 98 "format": "email", 99 "email": user.email, 100 }, 101 }, 102 ) 103 104 105@receiver(password_hash_changed) 106@receiver(password_changed) 107def ssf_password_changed_cred_change(signal, sender, user: User, password: str | None = None, **_): 108 """Credential change trigger (password changed)""" 109 if signal is password_hash_changed: 110 _send_password_credential_change(user, "update") 111 return 112 _send_password_credential_change(user, "revoke" if password is None else "update") 113 114 115device_type_map = { 116 StaticDevice: "pin", 117 TOTPDevice: "pin", 118 WebAuthnDevice: "fido-u2f", 119 DuoDevice: "app", 120} 121 122 123@receiver(post_save) 124def ssf_device_post_save(sender: type[Model], instance: Device, created: bool, **_): 125 if not isinstance(instance, Device): 126 return 127 if not instance.confirmed: 128 return 129 device_type = device_type_map.get(instance.__class__) 130 data = { 131 "credential_type": device_type, 132 "change_type": "create" if created else "update", 133 "friendly_name": instance.name, 134 } 135 if isinstance(instance, WebAuthnDevice) and instance.aaguid != UNKNOWN_DEVICE_TYPE_AAGUID: 136 data["fido2_aaguid"] = instance.aaguid 137 send_ssf_events( 138 EventTypes.CAEP_CREDENTIAL_CHANGE, 139 data, 140 sub_id={ 141 "format": "complex", 142 "user": { 143 "format": "email", 144 "email": instance.user.email, 145 }, 146 }, 147 ) 148 149 150@receiver(post_delete) 151def ssf_device_post_delete(sender: type[Model], instance: Device, **_): 152 if not isinstance(instance, Device): 153 return 154 if not instance.confirmed: 155 return 156 device_type = device_type_map.get(instance.__class__) 157 data = { 158 "credential_type": device_type, 159 "change_type": "delete", 160 "friendly_name": instance.name, 161 } 162 if isinstance(instance, WebAuthnDevice) and instance.aaguid != UNKNOWN_DEVICE_TYPE_AAGUID: 163 data["fido2_aaguid"] = instance.aaguid 164 send_ssf_events( 165 EventTypes.CAEP_CREDENTIAL_CHANGE, 166 data, 167 sub_id={ 168 "format": "complex", 169 "user": { 170 "format": "email", 171 "email": instance.user.email, 172 }, 173 }, 174 )
USER_PATH_PROVIDERS_SSF =
'goauthentik.io/providers/ssf'
@receiver(post_save, sender=SSFProvider)
def
ssf_providers_post_save( sender: type[django.db.models.base.Model], instance: authentik.enterprise.providers.ssf.models.SSFProvider, created: bool, **_):
35@receiver(post_save, sender=SSFProvider) 36def ssf_providers_post_save(sender: type[Model], instance: SSFProvider, created: bool, **_): 37 """Create service account before provider is saved""" 38 identifier = instance.service_account_identifier 39 user, _ = User.objects.update_or_create( 40 username=identifier, 41 defaults={ 42 "name": f"SSF Provider {instance.name} Service-Account", 43 "type": UserTypes.INTERNAL_SERVICE_ACCOUNT, 44 "path": USER_PATH_PROVIDERS_SSF, 45 }, 46 ) 47 user.assign_perms_to_managed_role("authentik_providers_ssf.add_stream", instance) 48 token, token_created = Token.objects.update_or_create( 49 identifier=identifier, 50 defaults={ 51 "user": user, 52 "intent": TokenIntents.INTENT_API, 53 "expiring": False, 54 "managed": f"goauthentik.io/providers/ssf/{instance.pk}", 55 }, 56 ) 57 if created or token_created: 58 with audit_ignore(): 59 instance.token = token 60 instance.save()
Create service account before provider is saved
@receiver(pre_delete, sender=AuthenticatedSession)
def
ssf_user_session_delete_session_revoked(sender, instance: authentik.core.models.AuthenticatedSession, **_):
63@receiver(pre_delete, sender=AuthenticatedSession) 64def ssf_user_session_delete_session_revoked(sender, instance: AuthenticatedSession, **_): 65 """Session revoked trigger (users' session has been deleted) 66 67 As this signal is also triggered with a regular logout, we can't be sure 68 if the session has been deleted by an admin or by the user themselves.""" 69 send_ssf_events( 70 EventTypes.CAEP_SESSION_REVOKED, 71 { 72 "initiating_entity": "user", 73 }, 74 sub_id={ 75 "format": "complex", 76 "session": { 77 "format": "opaque", 78 "id": sha256(instance.session.session_key.encode("ascii")).hexdigest(), 79 }, 80 "user": { 81 "format": "email", 82 "email": instance.user.email, 83 }, 84 }, 85 )
Session revoked trigger (users' session has been deleted)
As this signal is also triggered with a regular logout, we can't be sure if the session has been deleted by an admin or by the user themselves.
@receiver(password_hash_changed)
@receiver(password_changed)
def
ssf_password_changed_cred_change( signal, sender, user: authentik.core.models.User, password: str | None = None, **_):
106@receiver(password_hash_changed) 107@receiver(password_changed) 108def ssf_password_changed_cred_change(signal, sender, user: User, password: str | None = None, **_): 109 """Credential change trigger (password changed)""" 110 if signal is password_hash_changed: 111 _send_password_credential_change(user, "update") 112 return 113 _send_password_credential_change(user, "revoke" if password is None else "update")
Credential change trigger (password changed)
device_type_map =
{<class 'authentik.stages.authenticator_static.models.StaticDevice'>: 'pin', <class 'authentik.stages.authenticator_totp.models.TOTPDevice'>: 'pin', <class 'authentik.stages.authenticator_webauthn.models.WebAuthnDevice'>: 'fido-u2f', <class 'authentik.stages.authenticator_duo.models.DuoDevice'>: 'app'}
@receiver(post_save)
def
ssf_device_post_save( sender: type[django.db.models.base.Model], instance: authentik.stages.authenticator.models.Device, created: bool, **_):
124@receiver(post_save) 125def ssf_device_post_save(sender: type[Model], instance: Device, created: bool, **_): 126 if not isinstance(instance, Device): 127 return 128 if not instance.confirmed: 129 return 130 device_type = device_type_map.get(instance.__class__) 131 data = { 132 "credential_type": device_type, 133 "change_type": "create" if created else "update", 134 "friendly_name": instance.name, 135 } 136 if isinstance(instance, WebAuthnDevice) and instance.aaguid != UNKNOWN_DEVICE_TYPE_AAGUID: 137 data["fido2_aaguid"] = instance.aaguid 138 send_ssf_events( 139 EventTypes.CAEP_CREDENTIAL_CHANGE, 140 data, 141 sub_id={ 142 "format": "complex", 143 "user": { 144 "format": "email", 145 "email": instance.user.email, 146 }, 147 }, 148 )
@receiver(post_delete)
def
ssf_device_post_delete( sender: type[django.db.models.base.Model], instance: authentik.stages.authenticator.models.Device, **_):
151@receiver(post_delete) 152def ssf_device_post_delete(sender: type[Model], instance: Device, **_): 153 if not isinstance(instance, Device): 154 return 155 if not instance.confirmed: 156 return 157 device_type = device_type_map.get(instance.__class__) 158 data = { 159 "credential_type": device_type, 160 "change_type": "delete", 161 "friendly_name": instance.name, 162 } 163 if isinstance(instance, WebAuthnDevice) and instance.aaguid != UNKNOWN_DEVICE_TYPE_AAGUID: 164 data["fido2_aaguid"] = instance.aaguid 165 send_ssf_events( 166 EventTypes.CAEP_CREDENTIAL_CHANGE, 167 data, 168 sub_id={ 169 "format": "complex", 170 "user": { 171 "format": "email", 172 "email": instance.user.email, 173 }, 174 }, 175 )