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
 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
 87@receiver(password_changed)
 88def ssf_password_changed_cred_change(sender, user: User, password: str | None, **_):
 89    """Credential change trigger (password changed)"""
 90    send_ssf_events(
 91        EventTypes.CAEP_CREDENTIAL_CHANGE,
 92        {
 93            "credential_type": "password",
 94            "change_type": "revoke" if password is None else "update",
 95        },
 96        sub_id={
 97            "format": "complex",
 98            "user": {
 99                "format": "email",
100                "email": user.email,
101            },
102        },
103    )
104
105
106device_type_map = {
107    StaticDevice: "pin",
108    TOTPDevice: "pin",
109    WebAuthnDevice: "fido-u2f",
110    DuoDevice: "app",
111}
112
113
114@receiver(post_save)
115def ssf_device_post_save(sender: type[Model], instance: Device, created: bool, **_):
116    if not isinstance(instance, Device):
117        return
118    if not instance.confirmed:
119        return
120    device_type = device_type_map.get(instance.__class__)
121    data = {
122        "credential_type": device_type,
123        "change_type": "create" if created else "update",
124        "friendly_name": instance.name,
125    }
126    if isinstance(instance, WebAuthnDevice) and instance.aaguid != UNKNOWN_DEVICE_TYPE_AAGUID:
127        data["fido2_aaguid"] = instance.aaguid
128    send_ssf_events(
129        EventTypes.CAEP_CREDENTIAL_CHANGE,
130        data,
131        sub_id={
132            "format": "complex",
133            "user": {
134                "format": "email",
135                "email": instance.user.email,
136            },
137        },
138    )
139
140
141@receiver(post_delete)
142def ssf_device_post_delete(sender: type[Model], instance: Device, **_):
143    if not isinstance(instance, Device):
144        return
145    if not instance.confirmed:
146        return
147    device_type = device_type_map.get(instance.__class__)
148    data = {
149        "credential_type": device_type,
150        "change_type": "delete",
151        "friendly_name": instance.name,
152    }
153    if isinstance(instance, WebAuthnDevice) and instance.aaguid != UNKNOWN_DEVICE_TYPE_AAGUID:
154        data["fido2_aaguid"] = instance.aaguid
155    send_ssf_events(
156        EventTypes.CAEP_CREDENTIAL_CHANGE,
157        data,
158        sub_id={
159            "format": "complex",
160            "user": {
161                "format": "email",
162                "email": instance.user.email,
163            },
164        },
165    )
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_changed)
def ssf_password_changed_cred_change(sender, user: authentik.core.models.User, password: str | None, **_):
 88@receiver(password_changed)
 89def ssf_password_changed_cred_change(sender, user: User, password: str | None, **_):
 90    """Credential change trigger (password changed)"""
 91    send_ssf_events(
 92        EventTypes.CAEP_CREDENTIAL_CHANGE,
 93        {
 94            "credential_type": "password",
 95            "change_type": "revoke" if password is None else "update",
 96        },
 97        sub_id={
 98            "format": "complex",
 99            "user": {
100                "format": "email",
101                "email": user.email,
102            },
103        },
104    )

Credential change trigger (password changed)

@receiver(post_save)
def ssf_device_post_save( sender: type[django.db.models.base.Model], instance: authentik.stages.authenticator.models.Device, created: bool, **_):
115@receiver(post_save)
116def ssf_device_post_save(sender: type[Model], instance: Device, created: bool, **_):
117    if not isinstance(instance, Device):
118        return
119    if not instance.confirmed:
120        return
121    device_type = device_type_map.get(instance.__class__)
122    data = {
123        "credential_type": device_type,
124        "change_type": "create" if created else "update",
125        "friendly_name": instance.name,
126    }
127    if isinstance(instance, WebAuthnDevice) and instance.aaguid != UNKNOWN_DEVICE_TYPE_AAGUID:
128        data["fido2_aaguid"] = instance.aaguid
129    send_ssf_events(
130        EventTypes.CAEP_CREDENTIAL_CHANGE,
131        data,
132        sub_id={
133            "format": "complex",
134            "user": {
135                "format": "email",
136                "email": instance.user.email,
137            },
138        },
139    )
@receiver(post_delete)
def ssf_device_post_delete( sender: type[django.db.models.base.Model], instance: authentik.stages.authenticator.models.Device, **_):
142@receiver(post_delete)
143def ssf_device_post_delete(sender: type[Model], instance: Device, **_):
144    if not isinstance(instance, Device):
145        return
146    if not instance.confirmed:
147        return
148    device_type = device_type_map.get(instance.__class__)
149    data = {
150        "credential_type": device_type,
151        "change_type": "delete",
152        "friendly_name": instance.name,
153    }
154    if isinstance(instance, WebAuthnDevice) and instance.aaguid != UNKNOWN_DEVICE_TYPE_AAGUID:
155        data["fido2_aaguid"] = instance.aaguid
156    send_ssf_events(
157        EventTypes.CAEP_CREDENTIAL_CHANGE,
158        data,
159        sub_id={
160            "format": "complex",
161            "user": {
162                "format": "email",
163                "email": instance.user.email,
164            },
165        },
166    )