authentik.enterprise.providers.ssf.models

  1from datetime import datetime
  2from functools import cached_property
  3from uuid import uuid4
  4
  5from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
  6from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
  7from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
  8from django.contrib.postgres.fields import ArrayField
  9from django.db import models
 10from django.templatetags.static import static
 11from django.utils.timezone import now
 12from django.utils.translation import gettext_lazy as _
 13from jwt import encode
 14
 15from authentik.core.models import BackchannelProvider, ExpiringModel, Token
 16from authentik.crypto.models import CertificateKeyPair
 17from authentik.lib.models import CreatedUpdatedModel, InternallyManagedMixin
 18from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
 19from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider
 20from authentik.tasks.models import TasksModel
 21
 22
 23class EventTypes(models.TextChoices):
 24    """SSF Event types supported by authentik"""
 25
 26    CAEP_SESSION_REVOKED = "https://schemas.openid.net/secevent/caep/event-type/session-revoked"
 27    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.1"""
 28    CAEP_TOKEN_CLAIMS_CHANGE = (
 29        "https://schemas.openid.net/secevent/caep/event-type/token-claims-change"
 30    )
 31    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.2"""
 32    CAEP_CREDENTIAL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/credential-change"
 33    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.3"""
 34    CAEP_ASSURANCE_LEVEL_CHANGE = (
 35        "https://schemas.openid.net/secevent/caep/event-type/assurance-level-change"
 36    )
 37    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.4"""
 38    CAEP_DEVICE_COMPLIANCE_CHANGE = (
 39        "https://schemas.openid.net/secevent/caep/event-type/device-compliance-change"
 40    )
 41    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.5"""
 42    CAEP_SESSION_ESTABLISHED = (
 43        "https://schemas.openid.net/secevent/caep/event-type/session-established"
 44    )
 45    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.6"""
 46    CAEP_SESSION_PRESENTED = "https://schemas.openid.net/secevent/caep/event-type/session-presented"
 47    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.7"""
 48    CAEP_RISK_LEVEL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/risk-level-change"
 49    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.8"""
 50    SET_VERIFICATION = "https://schemas.openid.net/secevent/ssf/event-type/verification"
 51    """https://openid.net/specs/openid-sharedsignals-framework-1_0.html#section-8.1.4.1"""
 52
 53
 54class DeliveryMethods(models.TextChoices):
 55    """SSF Delivery methods"""
 56
 57    RISC_PUSH = "https://schemas.openid.net/secevent/risc/delivery-method/push"
 58    RISC_POLL = "https://schemas.openid.net/secevent/risc/delivery-method/poll"
 59    RFC_PUSH = "urn:ietf:rfc:8935", _("SSF RFC Push")
 60    RFC_PULL = "urn:ietf:rfc:8936", _("SSF RFC Pull")
 61
 62
 63class SSFEventStatus(models.TextChoices):
 64    """SSF Event status"""
 65
 66    PENDING_NEW = "pending_new"
 67    PENDING_FAILED = "pending_failed"
 68    SENT = "sent"
 69
 70
 71class StreamStatus(models.TextChoices):
 72    """SSF Stream status"""
 73
 74    ENABLED = "enabled"
 75    PAUSED = "paused"
 76    DISABLED = "disabled"
 77    DISABLED_DELETED = "disabled_deleted"
 78
 79
 80class SSFProvider(TasksModel, BackchannelProvider):
 81    """Shared Signals Framework provider to allow applications to
 82    receive user events from authentik."""
 83
 84    signing_key = models.ForeignKey(
 85        CertificateKeyPair,
 86        verbose_name=_("Signing Key"),
 87        on_delete=models.CASCADE,
 88        help_text=_("Key used to sign the SSF Events."),
 89    )
 90
 91    push_verify_certificates = models.BooleanField(default=True)
 92
 93    oidc_auth_providers = models.ManyToManyField(OAuth2Provider, blank=True, default=None)
 94
 95    token = models.ForeignKey(Token, on_delete=models.CASCADE, null=True, default=None)
 96
 97    event_retention = models.TextField(
 98        default="days=30",
 99        validators=[timedelta_string_validator],
100    )
101
102    @cached_property
103    def jwt_key(self) -> tuple[PrivateKeyTypes, str]:
104        """Get either the configured certificate or the client secret"""
105        key: CertificateKeyPair = self.signing_key
106        private_key = key.private_key
107        if isinstance(private_key, RSAPrivateKey):
108            return private_key, JWTAlgorithms.RS256
109        if isinstance(private_key, EllipticCurvePrivateKey):
110            return private_key, JWTAlgorithms.ES256
111        raise ValueError(f"Invalid private key type: {type(private_key)}")
112
113    @property
114    def service_account_identifier(self) -> str:
115        return f"ak-providers-ssf-{self.pk}"
116
117    @property
118    def serializer(self):
119        from authentik.enterprise.providers.ssf.api.providers import SSFProviderSerializer
120
121        return SSFProviderSerializer
122
123    @property
124    def icon_url(self) -> str | None:
125        return static("authentik/sources/ssf.svg")
126
127    @property
128    def component(self) -> str:
129        return "ak-provider-ssf-form"
130
131    class Meta:
132        verbose_name = _("Shared Signals Framework Provider")
133        verbose_name_plural = _("Shared Signals Framework Providers")
134        permissions = [
135            # This overrides the default "add_stream" permission of the Stream object,
136            # as the user requesting to add a stream must have the permission on the provider
137            ("add_stream", _("Add stream to SSF provider")),
138        ]
139
140
141class Stream(models.Model):
142    """SSF Stream"""
143
144    uuid = models.UUIDField(default=uuid4, primary_key=True, editable=False)
145
146    status = models.TextField(choices=StreamStatus.choices, default=StreamStatus.ENABLED)
147
148    provider = models.ForeignKey(SSFProvider, on_delete=models.CASCADE)
149
150    delivery_method = models.TextField(choices=DeliveryMethods.choices)
151    endpoint_url = models.TextField(null=True)
152    authorization_header = models.TextField(null=True, default=None)
153
154    events_requested = ArrayField(models.TextField(choices=EventTypes.choices), default=list)
155    format = models.TextField()
156    aud = ArrayField(models.TextField(), default=list)
157
158    iss = models.TextField()
159
160    class Meta:
161        verbose_name = _("SSF Stream")
162        verbose_name_plural = _("SSF Streams")
163        default_permissions = ["change", "delete", "view"]
164
165    def __str__(self) -> str:
166        return "SSF Stream"
167
168    def prepare_event_payload(self, type: EventTypes, event_data: dict, **kwargs) -> dict:
169        jti = uuid4()
170        _now = now()
171        return {
172            "uuid": jti,
173            "stream_id": str(self.pk),
174            "type": type,
175            "expiring": True,
176            "status": SSFEventStatus.PENDING_NEW,
177            "expires": _now + timedelta_from_string(self.provider.event_retention),
178            "payload": {
179                "jti": jti.hex,
180                "aud": self.aud,
181                "iat": int(datetime.now().timestamp()),
182                "iss": self.iss,
183                "events": {type: event_data},
184                **kwargs,
185            },
186        }
187
188    def encode(self, data: dict) -> str:
189        headers = {"typ": "secevent+jwt"}
190        if self.provider.signing_key:
191            headers["kid"] = self.provider.signing_key.kid
192        key, alg = self.provider.jwt_key
193        return encode(data, key, algorithm=alg, headers=headers)
194
195
196class StreamEvent(InternallyManagedMixin, CreatedUpdatedModel, ExpiringModel):
197    """Single stream event to be sent"""
198
199    uuid = models.UUIDField(default=uuid4, primary_key=True, editable=False)
200
201    stream = models.ForeignKey(Stream, on_delete=models.CASCADE)
202    status = models.TextField(choices=SSFEventStatus.choices)
203
204    type = models.TextField(choices=EventTypes.choices)
205    payload = models.JSONField(default=dict)
206
207    def expire_action(self, *args, **kwargs):
208        """Only allow automatic cleanup of successfully sent event"""
209        if self.status != SSFEventStatus.SENT:
210            return
211        return super().expire_action(*args, **kwargs)
212
213    def __str__(self):
214        return f"Stream event {self.type}"
215
216    class Meta:
217        verbose_name = _("SSF Stream Event")
218        verbose_name_plural = _("SSF Stream Events")
219        ordering = ("-created",)
class EventTypes(django.db.models.enums.TextChoices):
24class EventTypes(models.TextChoices):
25    """SSF Event types supported by authentik"""
26
27    CAEP_SESSION_REVOKED = "https://schemas.openid.net/secevent/caep/event-type/session-revoked"
28    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.1"""
29    CAEP_TOKEN_CLAIMS_CHANGE = (
30        "https://schemas.openid.net/secevent/caep/event-type/token-claims-change"
31    )
32    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.2"""
33    CAEP_CREDENTIAL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/credential-change"
34    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.3"""
35    CAEP_ASSURANCE_LEVEL_CHANGE = (
36        "https://schemas.openid.net/secevent/caep/event-type/assurance-level-change"
37    )
38    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.4"""
39    CAEP_DEVICE_COMPLIANCE_CHANGE = (
40        "https://schemas.openid.net/secevent/caep/event-type/device-compliance-change"
41    )
42    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.5"""
43    CAEP_SESSION_ESTABLISHED = (
44        "https://schemas.openid.net/secevent/caep/event-type/session-established"
45    )
46    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.6"""
47    CAEP_SESSION_PRESENTED = "https://schemas.openid.net/secevent/caep/event-type/session-presented"
48    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.7"""
49    CAEP_RISK_LEVEL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/risk-level-change"
50    """https://openid.net/specs/openid-caep-1_0-final.html#section-3.8"""
51    SET_VERIFICATION = "https://schemas.openid.net/secevent/ssf/event-type/verification"
52    """https://openid.net/specs/openid-sharedsignals-framework-1_0.html#section-8.1.4.1"""

SSF Event types supported by authentik

class DeliveryMethods(django.db.models.enums.TextChoices):
55class DeliveryMethods(models.TextChoices):
56    """SSF Delivery methods"""
57
58    RISC_PUSH = "https://schemas.openid.net/secevent/risc/delivery-method/push"
59    RISC_POLL = "https://schemas.openid.net/secevent/risc/delivery-method/poll"
60    RFC_PUSH = "urn:ietf:rfc:8935", _("SSF RFC Push")
61    RFC_PULL = "urn:ietf:rfc:8936", _("SSF RFC Pull")

SSF Delivery methods

class SSFEventStatus(django.db.models.enums.TextChoices):
64class SSFEventStatus(models.TextChoices):
65    """SSF Event status"""
66
67    PENDING_NEW = "pending_new"
68    PENDING_FAILED = "pending_failed"
69    SENT = "sent"

SSF Event status

class StreamStatus(django.db.models.enums.TextChoices):
72class StreamStatus(models.TextChoices):
73    """SSF Stream status"""
74
75    ENABLED = "enabled"
76    PAUSED = "paused"
77    DISABLED = "disabled"
78    DISABLED_DELETED = "disabled_deleted"

SSF Stream status

DISABLED_DELETED = StreamStatus.DISABLED_DELETED
 81class SSFProvider(TasksModel, BackchannelProvider):
 82    """Shared Signals Framework provider to allow applications to
 83    receive user events from authentik."""
 84
 85    signing_key = models.ForeignKey(
 86        CertificateKeyPair,
 87        verbose_name=_("Signing Key"),
 88        on_delete=models.CASCADE,
 89        help_text=_("Key used to sign the SSF Events."),
 90    )
 91
 92    push_verify_certificates = models.BooleanField(default=True)
 93
 94    oidc_auth_providers = models.ManyToManyField(OAuth2Provider, blank=True, default=None)
 95
 96    token = models.ForeignKey(Token, on_delete=models.CASCADE, null=True, default=None)
 97
 98    event_retention = models.TextField(
 99        default="days=30",
100        validators=[timedelta_string_validator],
101    )
102
103    @cached_property
104    def jwt_key(self) -> tuple[PrivateKeyTypes, str]:
105        """Get either the configured certificate or the client secret"""
106        key: CertificateKeyPair = self.signing_key
107        private_key = key.private_key
108        if isinstance(private_key, RSAPrivateKey):
109            return private_key, JWTAlgorithms.RS256
110        if isinstance(private_key, EllipticCurvePrivateKey):
111            return private_key, JWTAlgorithms.ES256
112        raise ValueError(f"Invalid private key type: {type(private_key)}")
113
114    @property
115    def service_account_identifier(self) -> str:
116        return f"ak-providers-ssf-{self.pk}"
117
118    @property
119    def serializer(self):
120        from authentik.enterprise.providers.ssf.api.providers import SSFProviderSerializer
121
122        return SSFProviderSerializer
123
124    @property
125    def icon_url(self) -> str | None:
126        return static("authentik/sources/ssf.svg")
127
128    @property
129    def component(self) -> str:
130        return "ak-provider-ssf-form"
131
132    class Meta:
133        verbose_name = _("Shared Signals Framework Provider")
134        verbose_name_plural = _("Shared Signals Framework Providers")
135        permissions = [
136            # This overrides the default "add_stream" permission of the Stream object,
137            # as the user requesting to add a stream must have the permission on the provider
138            ("add_stream", _("Add stream to SSF provider")),
139        ]

Shared Signals Framework provider to allow applications to receive user events from authentik.

signing_key

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

def push_verify_certificates(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

oidc_auth_providers

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example::

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

Pizza.toppings and Topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

token

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

def event_retention(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

jwt_key: tuple[cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey | cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey | cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA44PrivateKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA65PrivateKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA87PrivateKey | cryptography.hazmat.primitives.asymmetric.mlkem.MLKEM768PrivateKey | cryptography.hazmat.primitives.asymmetric.mlkem.MLKEM1024PrivateKey | cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey | cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey | cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey | cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey | cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey, str]
103    @cached_property
104    def jwt_key(self) -> tuple[PrivateKeyTypes, str]:
105        """Get either the configured certificate or the client secret"""
106        key: CertificateKeyPair = self.signing_key
107        private_key = key.private_key
108        if isinstance(private_key, RSAPrivateKey):
109            return private_key, JWTAlgorithms.RS256
110        if isinstance(private_key, EllipticCurvePrivateKey):
111            return private_key, JWTAlgorithms.ES256
112        raise ValueError(f"Invalid private key type: {type(private_key)}")

Get either the configured certificate or the client secret

service_account_identifier: str
114    @property
115    def service_account_identifier(self) -> str:
116        return f"ak-providers-ssf-{self.pk}"
serializer
118    @property
119    def serializer(self):
120        from authentik.enterprise.providers.ssf.api.providers import SSFProviderSerializer
121
122        return SSFProviderSerializer

Get serializer for this model

icon_url: str | None
124    @property
125    def icon_url(self) -> str | None:
126        return static("authentik/sources/ssf.svg")
component: str
128    @property
129    def component(self) -> str:
130        return "ak-provider-ssf-form"

Return component used to edit this object

tasks

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

signing_key_id
token_id
provider_ptr_id
provider_ptr

Accessor to the related object on the forward side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Restaurant.place is a ForwardOneToOneDescriptor instance.

stream_set

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

class SSFProvider.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class SSFProvider.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class Stream(django.db.models.base.Model):
142class Stream(models.Model):
143    """SSF Stream"""
144
145    uuid = models.UUIDField(default=uuid4, primary_key=True, editable=False)
146
147    status = models.TextField(choices=StreamStatus.choices, default=StreamStatus.ENABLED)
148
149    provider = models.ForeignKey(SSFProvider, on_delete=models.CASCADE)
150
151    delivery_method = models.TextField(choices=DeliveryMethods.choices)
152    endpoint_url = models.TextField(null=True)
153    authorization_header = models.TextField(null=True, default=None)
154
155    events_requested = ArrayField(models.TextField(choices=EventTypes.choices), default=list)
156    format = models.TextField()
157    aud = ArrayField(models.TextField(), default=list)
158
159    iss = models.TextField()
160
161    class Meta:
162        verbose_name = _("SSF Stream")
163        verbose_name_plural = _("SSF Streams")
164        default_permissions = ["change", "delete", "view"]
165
166    def __str__(self) -> str:
167        return "SSF Stream"
168
169    def prepare_event_payload(self, type: EventTypes, event_data: dict, **kwargs) -> dict:
170        jti = uuid4()
171        _now = now()
172        return {
173            "uuid": jti,
174            "stream_id": str(self.pk),
175            "type": type,
176            "expiring": True,
177            "status": SSFEventStatus.PENDING_NEW,
178            "expires": _now + timedelta_from_string(self.provider.event_retention),
179            "payload": {
180                "jti": jti.hex,
181                "aud": self.aud,
182                "iat": int(datetime.now().timestamp()),
183                "iss": self.iss,
184                "events": {type: event_data},
185                **kwargs,
186            },
187        }
188
189    def encode(self, data: dict) -> str:
190        headers = {"typ": "secevent+jwt"}
191        if self.provider.signing_key:
192            headers["kid"] = self.provider.signing_key.kid
193        key, alg = self.provider.jwt_key
194        return encode(data, key, algorithm=alg, headers=headers)

SSF Stream

def uuid(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def status(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

provider

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

def delivery_method(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def endpoint_url(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def authorization_header(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def events_requested(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def format(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def aud(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def iss(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def prepare_event_payload( self, type: EventTypes, event_data: dict, **kwargs) -> dict:
169    def prepare_event_payload(self, type: EventTypes, event_data: dict, **kwargs) -> dict:
170        jti = uuid4()
171        _now = now()
172        return {
173            "uuid": jti,
174            "stream_id": str(self.pk),
175            "type": type,
176            "expiring": True,
177            "status": SSFEventStatus.PENDING_NEW,
178            "expires": _now + timedelta_from_string(self.provider.event_retention),
179            "payload": {
180                "jti": jti.hex,
181                "aud": self.aud,
182                "iat": int(datetime.now().timestamp()),
183                "iss": self.iss,
184                "events": {type: event_data},
185                **kwargs,
186            },
187        }
def encode(self, data: dict) -> str:
189    def encode(self, data: dict) -> str:
190        headers = {"typ": "secevent+jwt"}
191        if self.provider.signing_key:
192            headers["kid"] = self.provider.signing_key.kid
193        key, alg = self.provider.jwt_key
194        return encode(data, key, algorithm=alg, headers=headers)
def get_status_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

provider_id
def get_delivery_method_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def objects(unknown):

The type of the None singleton.

streamevent_set

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

class Stream.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class Stream.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

197class StreamEvent(InternallyManagedMixin, CreatedUpdatedModel, ExpiringModel):
198    """Single stream event to be sent"""
199
200    uuid = models.UUIDField(default=uuid4, primary_key=True, editable=False)
201
202    stream = models.ForeignKey(Stream, on_delete=models.CASCADE)
203    status = models.TextField(choices=SSFEventStatus.choices)
204
205    type = models.TextField(choices=EventTypes.choices)
206    payload = models.JSONField(default=dict)
207
208    def expire_action(self, *args, **kwargs):
209        """Only allow automatic cleanup of successfully sent event"""
210        if self.status != SSFEventStatus.SENT:
211            return
212        return super().expire_action(*args, **kwargs)
213
214    def __str__(self):
215        return f"Stream event {self.type}"
216
217    class Meta:
218        verbose_name = _("SSF Stream Event")
219        verbose_name_plural = _("SSF Stream Events")
220        ordering = ("-created",)

Single stream event to be sent

def uuid(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

stream

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

def status(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def type(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def payload(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def expire_action(self, *args, **kwargs):
208    def expire_action(self, *args, **kwargs):
209        """Only allow automatic cleanup of successfully sent event"""
210        if self.status != SSFEventStatus.SENT:
211            return
212        return super().expire_action(*args, **kwargs)

Only allow automatic cleanup of successfully sent event

def created(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def last_updated(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def expires(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def expiring(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

stream_id
def get_status_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_type_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_next_by_created(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_created(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_next_by_last_updated(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_last_updated(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

class StreamEvent.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class StreamEvent.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.