authentik.sources.saml.models

saml sp models

  1"""saml sp models"""
  2
  3from typing import Any
  4
  5from django.db import models
  6from django.http import HttpRequest
  7from django.templatetags.static import static
  8from django.urls import reverse
  9from django.utils.translation import gettext_lazy as _
 10from lxml.etree import _Element  # nosec
 11from rest_framework.serializers import Serializer
 12
 13from authentik.common.saml.constants import (
 14    DSA_SHA1,
 15    ECDSA_SHA1,
 16    ECDSA_SHA256,
 17    ECDSA_SHA384,
 18    ECDSA_SHA512,
 19    NS_SAML_ASSERTION,
 20    RSA_SHA1,
 21    RSA_SHA256,
 22    RSA_SHA384,
 23    RSA_SHA512,
 24    SAML_ATTRIBUTES_GROUP,
 25    SAML_BINDING_POST,
 26    SAML_BINDING_REDIRECT,
 27    SAML_NAME_ID_FORMAT_EMAIL,
 28    SAML_NAME_ID_FORMAT_PERSISTENT,
 29    SAML_NAME_ID_FORMAT_TRANSIENT,
 30    SAML_NAME_ID_FORMAT_UNSPECIFIED,
 31    SAML_NAME_ID_FORMAT_WINDOWS,
 32    SAML_NAME_ID_FORMAT_X509,
 33    SHA1,
 34    SHA256,
 35    SHA384,
 36    SHA512,
 37)
 38from authentik.core.models import (
 39    GroupSourceConnection,
 40    PropertyMapping,
 41    Source,
 42    UserSourceConnection,
 43)
 44from authentik.core.types import UILoginButton, UserSettingSerializer
 45from authentik.crypto.models import CertificateKeyPair
 46from authentik.flows.challenge import RedirectChallenge
 47from authentik.flows.models import Flow
 48from authentik.lib.expression.evaluator import BaseEvaluator
 49from authentik.lib.models import DomainlessURLValidator
 50from authentik.lib.utils.time import timedelta_string_validator
 51
 52
 53class SAMLBindingTypes(models.TextChoices):
 54    """SAML Binding types"""
 55
 56    REDIRECT = "REDIRECT", _("Redirect Binding")
 57    POST = "POST", _("POST Binding")
 58    POST_AUTO = "POST_AUTO", _("POST Binding with auto-confirmation")
 59
 60    @property
 61    def uri(self) -> str:
 62        """Convert database field to URI"""
 63        return {
 64            SAMLBindingTypes.POST: SAML_BINDING_POST,
 65            SAMLBindingTypes.POST_AUTO: SAML_BINDING_POST,
 66            SAMLBindingTypes.REDIRECT: SAML_BINDING_REDIRECT,
 67        }[self]
 68
 69
 70class SAMLNameIDPolicy(models.TextChoices):
 71    """SAML NameID Policies"""
 72
 73    EMAIL = SAML_NAME_ID_FORMAT_EMAIL
 74    PERSISTENT = SAML_NAME_ID_FORMAT_PERSISTENT
 75    X509 = SAML_NAME_ID_FORMAT_X509
 76    WINDOWS = SAML_NAME_ID_FORMAT_WINDOWS
 77    TRANSIENT = SAML_NAME_ID_FORMAT_TRANSIENT
 78    UNSPECIFIED = SAML_NAME_ID_FORMAT_UNSPECIFIED
 79
 80
 81class SAMLSource(Source):
 82    """Authenticate using an external SAML Identity Provider."""
 83
 84    pre_authentication_flow = models.ForeignKey(
 85        Flow,
 86        on_delete=models.CASCADE,
 87        help_text=_("Flow used before authentication."),
 88        related_name="source_pre_authentication",
 89    )
 90
 91    issuer = models.TextField(
 92        blank=True,
 93        default=None,
 94        verbose_name=_("Issuer"),
 95        help_text=_("Also known as Entity ID. Defaults the Metadata URL."),
 96    )
 97
 98    sso_url = models.TextField(
 99        validators=[DomainlessURLValidator(schemes=("http", "https"))],
100        verbose_name=_("SSO URL"),
101        help_text=_("URL that the initial Login request is sent to."),
102    )
103    slo_url = models.TextField(
104        validators=[DomainlessURLValidator(schemes=("http", "https"))],
105        default=None,
106        blank=True,
107        null=True,
108        verbose_name=_("SLO URL"),
109        help_text=_("Optional URL if your IDP supports Single-Logout."),
110    )
111
112    allow_idp_initiated = models.BooleanField(
113        default=False,
114        help_text=_(
115            "Allows authentication flows initiated by the IdP. This can be a security risk, "
116            "as no validation of the request ID is done."
117        ),
118    )
119    force_authn = models.BooleanField(
120        default=False,
121        help_text=_(
122            "When enabled, the IdP will re-authenticate the user even if a session exists."
123        ),
124    )
125    name_id_policy = models.TextField(
126        choices=SAMLNameIDPolicy.choices,
127        default=SAMLNameIDPolicy.PERSISTENT,
128        help_text=_(
129            "NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent."
130        ),
131    )
132    binding_type = models.CharField(
133        max_length=100,
134        choices=SAMLBindingTypes.choices,
135        default=SAMLBindingTypes.REDIRECT,
136    )
137
138    temporary_user_delete_after = models.TextField(
139        default="days=1",
140        verbose_name=_("Delete temporary users after"),
141        validators=[timedelta_string_validator],
142        help_text=_(
143            "Time offset when temporary users should be deleted. This only applies if your IDP "
144            "uses the NameID Format 'transient', and the user doesn't log out manually. "
145            "(Format: hours=1;minutes=2;seconds=3)."
146        ),
147    )
148
149    verification_kp = models.ForeignKey(
150        CertificateKeyPair,
151        default=None,
152        null=True,
153        blank=True,
154        help_text=_(
155            "When selected, incoming assertion's Signatures will be validated against this "
156            "certificate. To allow unsigned Requests, leave on default."
157        ),
158        on_delete=models.SET_NULL,
159        verbose_name=_("Verification Certificate"),
160        related_name="+",
161    )
162    signing_kp = models.ForeignKey(
163        CertificateKeyPair,
164        default=None,
165        null=True,
166        blank=True,
167        help_text=_("Keypair used to sign outgoing Responses going to the Identity Provider."),
168        on_delete=models.SET_NULL,
169        verbose_name=_("Signing Keypair"),
170    )
171    encryption_kp = models.ForeignKey(
172        CertificateKeyPair,
173        default=None,
174        null=True,
175        blank=True,
176        help_text=_(
177            "When selected, incoming assertions are encrypted by the IdP using the public "
178            "key of the encryption keypair. The assertion is decrypted by the SP using the "
179            "the private key."
180        ),
181        on_delete=models.SET_NULL,
182        verbose_name=_("Encryption Keypair"),
183        related_name="+",
184    )
185
186    digest_algorithm = models.TextField(
187        choices=(
188            (SHA1, _("SHA1")),
189            (SHA256, _("SHA256")),
190            (SHA384, _("SHA384")),
191            (SHA512, _("SHA512")),
192        ),
193        default=SHA256,
194    )
195    signature_algorithm = models.TextField(
196        choices=(
197            (RSA_SHA1, _("RSA-SHA1")),
198            (RSA_SHA256, _("RSA-SHA256")),
199            (RSA_SHA384, _("RSA-SHA384")),
200            (RSA_SHA512, _("RSA-SHA512")),
201            (ECDSA_SHA1, _("ECDSA-SHA1")),
202            (ECDSA_SHA256, _("ECDSA-SHA256")),
203            (ECDSA_SHA384, _("ECDSA-SHA384")),
204            (ECDSA_SHA512, _("ECDSA-SHA512")),
205            (DSA_SHA1, _("DSA-SHA1")),
206        ),
207        default=RSA_SHA256,
208    )
209
210    signed_assertion = models.BooleanField(default=True)
211    signed_response = models.BooleanField(default=False)
212
213    @property
214    def component(self) -> str:
215        return "ak-source-saml-form"
216
217    @property
218    def serializer(self) -> type[Serializer]:
219        from authentik.sources.saml.api.source import SAMLSourceSerializer
220
221        return SAMLSourceSerializer
222
223    @property
224    def property_mapping_type(self) -> type[PropertyMapping]:
225        return SAMLSourcePropertyMapping
226
227    def get_base_user_properties(self, root: _Element, assertion: _Element, name_id: Any, **kwargs):
228        attributes = {}
229        if assertion is None:
230            raise ValueError("Assertion element not found")
231        attribute_statement = assertion.find(f"{{{NS_SAML_ASSERTION}}}AttributeStatement")
232        if attribute_statement is None:
233            raise ValueError("Attribute statement element not found")
234        # Get all attributes and their values into a dict
235        for attribute in attribute_statement.iterchildren():
236            key = attribute.attrib["Name"]
237            attributes.setdefault(key, [])
238            for value in attribute.iterchildren():
239                attributes[key].append(value.text)
240        if SAML_ATTRIBUTES_GROUP in attributes:
241            attributes["groups"] = attributes[SAML_ATTRIBUTES_GROUP]
242            del attributes[SAML_ATTRIBUTES_GROUP]
243        # Flatten all lists in the dict
244        for key, value in attributes.items():
245            if key == "groups":
246                continue
247            attributes[key] = BaseEvaluator.expr_flatten(value)
248        attributes["username"] = name_id.text
249
250        return attributes
251
252    def get_base_group_properties(self, group_id: str, **kwargs):
253        return {
254            "name": group_id,
255        }
256
257    def get_issuer(self, request: HttpRequest) -> str:
258        """Get Source's Issuer, falling back to our Metadata URL if none is set"""
259        if self.issuer is None:
260            return self.build_full_url(request, view="metadata")
261        return self.issuer
262
263    def build_full_url(self, request: HttpRequest, view: str = "acs") -> str:
264        """Build Full ACS URL to be used in IDP"""
265        return request.build_absolute_uri(
266            reverse(f"authentik_sources_saml:{view}", kwargs={"source_slug": self.slug})
267        )
268
269    @property
270    def icon_url(self) -> str:
271        icon = super().icon_url
272        if not icon:
273            return static("authentik/sources/saml.png")
274        return icon
275
276    def ui_login_button(self, request: HttpRequest) -> UILoginButton:
277        return UILoginButton(
278            challenge=RedirectChallenge(
279                data={
280                    "to": reverse(
281                        "authentik_sources_saml:login",
282                        kwargs={"source_slug": self.slug},
283                    ),
284                }
285            ),
286            name=self.name,
287            icon_url=self.icon_url,
288            promoted=self.promoted,
289        )
290
291    def ui_user_settings(self) -> UserSettingSerializer | None:
292        return UserSettingSerializer(
293            data={
294                "title": self.name,
295                "component": "ak-user-settings-source-saml",
296                "configure_url": reverse(
297                    "authentik_sources_saml:login",
298                    kwargs={"source_slug": self.slug},
299                ),
300                "icon_url": self.icon_url,
301            }
302        )
303
304    def __str__(self):
305        return f"SAML Source {self.name}"
306
307    class Meta:
308        verbose_name = _("SAML Source")
309        verbose_name_plural = _("SAML Sources")
310
311
312class SAMLSourcePropertyMapping(PropertyMapping):
313    """Map SAML properties to User or Group object attributes"""
314
315    @property
316    def component(self) -> str:
317        return "ak-property-mapping-source-saml-form"
318
319    @property
320    def serializer(self) -> type[Serializer]:
321        from authentik.sources.saml.api.property_mappings import SAMLSourcePropertyMappingSerializer
322
323        return SAMLSourcePropertyMappingSerializer
324
325    class Meta:
326        verbose_name = _("SAML Source Property Mapping")
327        verbose_name_plural = _("SAML Source Property Mappings")
328
329
330class UserSAMLSourceConnection(UserSourceConnection):
331    """Connection to configured SAML Sources."""
332
333    @property
334    def serializer(self) -> Serializer:
335        from authentik.sources.saml.api.source_connection import UserSAMLSourceConnectionSerializer
336
337        return UserSAMLSourceConnectionSerializer
338
339    class Meta:
340        verbose_name = _("User SAML Source Connection")
341        verbose_name_plural = _("User SAML Source Connections")
342
343
344class GroupSAMLSourceConnection(GroupSourceConnection):
345    """Group-source connection"""
346
347    @property
348    def serializer(self) -> type[Serializer]:
349        from authentik.sources.saml.api.source_connection import (
350            GroupSAMLSourceConnectionSerializer,
351        )
352
353        return GroupSAMLSourceConnectionSerializer
354
355    class Meta:
356        verbose_name = _("Group SAML Source Connection")
357        verbose_name_plural = _("Group SAML Source Connections")
class SAMLBindingTypes(django.db.models.enums.TextChoices):
54class SAMLBindingTypes(models.TextChoices):
55    """SAML Binding types"""
56
57    REDIRECT = "REDIRECT", _("Redirect Binding")
58    POST = "POST", _("POST Binding")
59    POST_AUTO = "POST_AUTO", _("POST Binding with auto-confirmation")
60
61    @property
62    def uri(self) -> str:
63        """Convert database field to URI"""
64        return {
65            SAMLBindingTypes.POST: SAML_BINDING_POST,
66            SAMLBindingTypes.POST_AUTO: SAML_BINDING_POST,
67            SAMLBindingTypes.REDIRECT: SAML_BINDING_REDIRECT,
68        }[self]

SAML Binding types

uri: str
61    @property
62    def uri(self) -> str:
63        """Convert database field to URI"""
64        return {
65            SAMLBindingTypes.POST: SAML_BINDING_POST,
66            SAMLBindingTypes.POST_AUTO: SAML_BINDING_POST,
67            SAMLBindingTypes.REDIRECT: SAML_BINDING_REDIRECT,
68        }[self]

Convert database field to URI

class SAMLNameIDPolicy(django.db.models.enums.TextChoices):
71class SAMLNameIDPolicy(models.TextChoices):
72    """SAML NameID Policies"""
73
74    EMAIL = SAML_NAME_ID_FORMAT_EMAIL
75    PERSISTENT = SAML_NAME_ID_FORMAT_PERSISTENT
76    X509 = SAML_NAME_ID_FORMAT_X509
77    WINDOWS = SAML_NAME_ID_FORMAT_WINDOWS
78    TRANSIENT = SAML_NAME_ID_FORMAT_TRANSIENT
79    UNSPECIFIED = SAML_NAME_ID_FORMAT_UNSPECIFIED

SAML NameID Policies

class SAMLSource(authentik.core.models.Source):
 82class SAMLSource(Source):
 83    """Authenticate using an external SAML Identity Provider."""
 84
 85    pre_authentication_flow = models.ForeignKey(
 86        Flow,
 87        on_delete=models.CASCADE,
 88        help_text=_("Flow used before authentication."),
 89        related_name="source_pre_authentication",
 90    )
 91
 92    issuer = models.TextField(
 93        blank=True,
 94        default=None,
 95        verbose_name=_("Issuer"),
 96        help_text=_("Also known as Entity ID. Defaults the Metadata URL."),
 97    )
 98
 99    sso_url = models.TextField(
100        validators=[DomainlessURLValidator(schemes=("http", "https"))],
101        verbose_name=_("SSO URL"),
102        help_text=_("URL that the initial Login request is sent to."),
103    )
104    slo_url = models.TextField(
105        validators=[DomainlessURLValidator(schemes=("http", "https"))],
106        default=None,
107        blank=True,
108        null=True,
109        verbose_name=_("SLO URL"),
110        help_text=_("Optional URL if your IDP supports Single-Logout."),
111    )
112
113    allow_idp_initiated = models.BooleanField(
114        default=False,
115        help_text=_(
116            "Allows authentication flows initiated by the IdP. This can be a security risk, "
117            "as no validation of the request ID is done."
118        ),
119    )
120    force_authn = models.BooleanField(
121        default=False,
122        help_text=_(
123            "When enabled, the IdP will re-authenticate the user even if a session exists."
124        ),
125    )
126    name_id_policy = models.TextField(
127        choices=SAMLNameIDPolicy.choices,
128        default=SAMLNameIDPolicy.PERSISTENT,
129        help_text=_(
130            "NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent."
131        ),
132    )
133    binding_type = models.CharField(
134        max_length=100,
135        choices=SAMLBindingTypes.choices,
136        default=SAMLBindingTypes.REDIRECT,
137    )
138
139    temporary_user_delete_after = models.TextField(
140        default="days=1",
141        verbose_name=_("Delete temporary users after"),
142        validators=[timedelta_string_validator],
143        help_text=_(
144            "Time offset when temporary users should be deleted. This only applies if your IDP "
145            "uses the NameID Format 'transient', and the user doesn't log out manually. "
146            "(Format: hours=1;minutes=2;seconds=3)."
147        ),
148    )
149
150    verification_kp = models.ForeignKey(
151        CertificateKeyPair,
152        default=None,
153        null=True,
154        blank=True,
155        help_text=_(
156            "When selected, incoming assertion's Signatures will be validated against this "
157            "certificate. To allow unsigned Requests, leave on default."
158        ),
159        on_delete=models.SET_NULL,
160        verbose_name=_("Verification Certificate"),
161        related_name="+",
162    )
163    signing_kp = models.ForeignKey(
164        CertificateKeyPair,
165        default=None,
166        null=True,
167        blank=True,
168        help_text=_("Keypair used to sign outgoing Responses going to the Identity Provider."),
169        on_delete=models.SET_NULL,
170        verbose_name=_("Signing Keypair"),
171    )
172    encryption_kp = models.ForeignKey(
173        CertificateKeyPair,
174        default=None,
175        null=True,
176        blank=True,
177        help_text=_(
178            "When selected, incoming assertions are encrypted by the IdP using the public "
179            "key of the encryption keypair. The assertion is decrypted by the SP using the "
180            "the private key."
181        ),
182        on_delete=models.SET_NULL,
183        verbose_name=_("Encryption Keypair"),
184        related_name="+",
185    )
186
187    digest_algorithm = models.TextField(
188        choices=(
189            (SHA1, _("SHA1")),
190            (SHA256, _("SHA256")),
191            (SHA384, _("SHA384")),
192            (SHA512, _("SHA512")),
193        ),
194        default=SHA256,
195    )
196    signature_algorithm = models.TextField(
197        choices=(
198            (RSA_SHA1, _("RSA-SHA1")),
199            (RSA_SHA256, _("RSA-SHA256")),
200            (RSA_SHA384, _("RSA-SHA384")),
201            (RSA_SHA512, _("RSA-SHA512")),
202            (ECDSA_SHA1, _("ECDSA-SHA1")),
203            (ECDSA_SHA256, _("ECDSA-SHA256")),
204            (ECDSA_SHA384, _("ECDSA-SHA384")),
205            (ECDSA_SHA512, _("ECDSA-SHA512")),
206            (DSA_SHA1, _("DSA-SHA1")),
207        ),
208        default=RSA_SHA256,
209    )
210
211    signed_assertion = models.BooleanField(default=True)
212    signed_response = models.BooleanField(default=False)
213
214    @property
215    def component(self) -> str:
216        return "ak-source-saml-form"
217
218    @property
219    def serializer(self) -> type[Serializer]:
220        from authentik.sources.saml.api.source import SAMLSourceSerializer
221
222        return SAMLSourceSerializer
223
224    @property
225    def property_mapping_type(self) -> type[PropertyMapping]:
226        return SAMLSourcePropertyMapping
227
228    def get_base_user_properties(self, root: _Element, assertion: _Element, name_id: Any, **kwargs):
229        attributes = {}
230        if assertion is None:
231            raise ValueError("Assertion element not found")
232        attribute_statement = assertion.find(f"{{{NS_SAML_ASSERTION}}}AttributeStatement")
233        if attribute_statement is None:
234            raise ValueError("Attribute statement element not found")
235        # Get all attributes and their values into a dict
236        for attribute in attribute_statement.iterchildren():
237            key = attribute.attrib["Name"]
238            attributes.setdefault(key, [])
239            for value in attribute.iterchildren():
240                attributes[key].append(value.text)
241        if SAML_ATTRIBUTES_GROUP in attributes:
242            attributes["groups"] = attributes[SAML_ATTRIBUTES_GROUP]
243            del attributes[SAML_ATTRIBUTES_GROUP]
244        # Flatten all lists in the dict
245        for key, value in attributes.items():
246            if key == "groups":
247                continue
248            attributes[key] = BaseEvaluator.expr_flatten(value)
249        attributes["username"] = name_id.text
250
251        return attributes
252
253    def get_base_group_properties(self, group_id: str, **kwargs):
254        return {
255            "name": group_id,
256        }
257
258    def get_issuer(self, request: HttpRequest) -> str:
259        """Get Source's Issuer, falling back to our Metadata URL if none is set"""
260        if self.issuer is None:
261            return self.build_full_url(request, view="metadata")
262        return self.issuer
263
264    def build_full_url(self, request: HttpRequest, view: str = "acs") -> str:
265        """Build Full ACS URL to be used in IDP"""
266        return request.build_absolute_uri(
267            reverse(f"authentik_sources_saml:{view}", kwargs={"source_slug": self.slug})
268        )
269
270    @property
271    def icon_url(self) -> str:
272        icon = super().icon_url
273        if not icon:
274            return static("authentik/sources/saml.png")
275        return icon
276
277    def ui_login_button(self, request: HttpRequest) -> UILoginButton:
278        return UILoginButton(
279            challenge=RedirectChallenge(
280                data={
281                    "to": reverse(
282                        "authentik_sources_saml:login",
283                        kwargs={"source_slug": self.slug},
284                    ),
285                }
286            ),
287            name=self.name,
288            icon_url=self.icon_url,
289            promoted=self.promoted,
290        )
291
292    def ui_user_settings(self) -> UserSettingSerializer | None:
293        return UserSettingSerializer(
294            data={
295                "title": self.name,
296                "component": "ak-user-settings-source-saml",
297                "configure_url": reverse(
298                    "authentik_sources_saml:login",
299                    kwargs={"source_slug": self.slug},
300                ),
301                "icon_url": self.icon_url,
302            }
303        )
304
305    def __str__(self):
306        return f"SAML Source {self.name}"
307
308    class Meta:
309        verbose_name = _("SAML Source")
310        verbose_name_plural = _("SAML Sources")

Authenticate using an external SAML Identity Provider.

pre_authentication_flow

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 issuer(unknown):

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

def sso_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 slo_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 allow_idp_initiated(unknown):

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

def force_authn(unknown):

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

def name_id_policy(unknown):

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

def binding_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 temporary_user_delete_after(unknown):

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

verification_kp

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.

signing_kp

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.

encryption_kp

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 digest_algorithm(unknown):

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

def signature_algorithm(unknown):

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

def signed_assertion(unknown):

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

def signed_response(unknown):

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

component: str
214    @property
215    def component(self) -> str:
216        return "ak-source-saml-form"

Return component used to edit this object

serializer: type[rest_framework.serializers.Serializer]
218    @property
219    def serializer(self) -> type[Serializer]:
220        from authentik.sources.saml.api.source import SAMLSourceSerializer
221
222        return SAMLSourceSerializer

Get serializer for this model

property_mapping_type: type[authentik.core.models.PropertyMapping]
224    @property
225    def property_mapping_type(self) -> type[PropertyMapping]:
226        return SAMLSourcePropertyMapping

Return property mapping type used by this object

def get_base_user_properties( self, root: lxml.etree._Element, assertion: lxml.etree._Element, name_id: Any, **kwargs):
228    def get_base_user_properties(self, root: _Element, assertion: _Element, name_id: Any, **kwargs):
229        attributes = {}
230        if assertion is None:
231            raise ValueError("Assertion element not found")
232        attribute_statement = assertion.find(f"{{{NS_SAML_ASSERTION}}}AttributeStatement")
233        if attribute_statement is None:
234            raise ValueError("Attribute statement element not found")
235        # Get all attributes and their values into a dict
236        for attribute in attribute_statement.iterchildren():
237            key = attribute.attrib["Name"]
238            attributes.setdefault(key, [])
239            for value in attribute.iterchildren():
240                attributes[key].append(value.text)
241        if SAML_ATTRIBUTES_GROUP in attributes:
242            attributes["groups"] = attributes[SAML_ATTRIBUTES_GROUP]
243            del attributes[SAML_ATTRIBUTES_GROUP]
244        # Flatten all lists in the dict
245        for key, value in attributes.items():
246            if key == "groups":
247                continue
248            attributes[key] = BaseEvaluator.expr_flatten(value)
249        attributes["username"] = name_id.text
250
251        return attributes

Get base properties for a user to build final properties upon.

def get_base_group_properties(self, group_id: str, **kwargs):
253    def get_base_group_properties(self, group_id: str, **kwargs):
254        return {
255            "name": group_id,
256        }

Get base properties for a group to build final properties upon.

def get_issuer(self, request: django.http.request.HttpRequest) -> str:
258    def get_issuer(self, request: HttpRequest) -> str:
259        """Get Source's Issuer, falling back to our Metadata URL if none is set"""
260        if self.issuer is None:
261            return self.build_full_url(request, view="metadata")
262        return self.issuer

Get Source's Issuer, falling back to our Metadata URL if none is set

def build_full_url(self, request: django.http.request.HttpRequest, view: str = 'acs') -> str:
264    def build_full_url(self, request: HttpRequest, view: str = "acs") -> str:
265        """Build Full ACS URL to be used in IDP"""
266        return request.build_absolute_uri(
267            reverse(f"authentik_sources_saml:{view}", kwargs={"source_slug": self.slug})
268        )

Build Full ACS URL to be used in IDP

icon_url: str
270    @property
271    def icon_url(self) -> str:
272        icon = super().icon_url
273        if not icon:
274            return static("authentik/sources/saml.png")
275        return icon

Get the URL to the source icon

def ui_login_button( self, request: django.http.request.HttpRequest) -> authentik.core.types.UILoginButton:
277    def ui_login_button(self, request: HttpRequest) -> UILoginButton:
278        return UILoginButton(
279            challenge=RedirectChallenge(
280                data={
281                    "to": reverse(
282                        "authentik_sources_saml:login",
283                        kwargs={"source_slug": self.slug},
284                    ),
285                }
286            ),
287            name=self.name,
288            icon_url=self.icon_url,
289            promoted=self.promoted,
290        )

If source uses a http-based flow, return UI Information about the login button. If source doesn't use http-based flow, return None.

def ui_user_settings(self) -> authentik.core.types.UserSettingSerializer | None:
292    def ui_user_settings(self) -> UserSettingSerializer | None:
293        return UserSettingSerializer(
294            data={
295                "title": self.name,
296                "component": "ak-user-settings-source-saml",
297                "configure_url": reverse(
298                    "authentik_sources_saml:login",
299                    kwargs={"source_slug": self.slug},
300                ),
301                "icon_url": self.icon_url,
302            }
303        )

Entrypoint to integrate with User settings. Can either return None if no user settings are available, or UserSettingSerializer.

pre_authentication_flow_id
def get_name_id_policy_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_binding_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.

verification_kp_id
signing_kp_id
encryption_kp_id
def get_digest_algorithm_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_signature_algorithm_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.

source_ptr_id
source_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.

class SAMLSource.DoesNotExist(authentik.core.models.Source.DoesNotExist):

The requested object does not exist

class SAMLSource.MultipleObjectsReturned(authentik.core.models.Source.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class SAMLSourcePropertyMapping(authentik.core.models.PropertyMapping):
313class SAMLSourcePropertyMapping(PropertyMapping):
314    """Map SAML properties to User or Group object attributes"""
315
316    @property
317    def component(self) -> str:
318        return "ak-property-mapping-source-saml-form"
319
320    @property
321    def serializer(self) -> type[Serializer]:
322        from authentik.sources.saml.api.property_mappings import SAMLSourcePropertyMappingSerializer
323
324        return SAMLSourcePropertyMappingSerializer
325
326    class Meta:
327        verbose_name = _("SAML Source Property Mapping")
328        verbose_name_plural = _("SAML Source Property Mappings")

Map SAML properties to User or Group object attributes

component: str
316    @property
317    def component(self) -> str:
318        return "ak-property-mapping-source-saml-form"

Return component used to edit this object

serializer: type[rest_framework.serializers.Serializer]
320    @property
321    def serializer(self) -> type[Serializer]:
322        from authentik.sources.saml.api.property_mappings import SAMLSourcePropertyMappingSerializer
323
324        return SAMLSourcePropertyMappingSerializer

Get serializer for this model

propertymapping_ptr_id
propertymapping_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.

class SAMLSourcePropertyMapping.DoesNotExist(authentik.core.models.PropertyMapping.DoesNotExist):

The requested object does not exist

class SAMLSourcePropertyMapping.MultipleObjectsReturned(authentik.core.models.PropertyMapping.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class UserSAMLSourceConnection(authentik.core.models.UserSourceConnection):
331class UserSAMLSourceConnection(UserSourceConnection):
332    """Connection to configured SAML Sources."""
333
334    @property
335    def serializer(self) -> Serializer:
336        from authentik.sources.saml.api.source_connection import UserSAMLSourceConnectionSerializer
337
338        return UserSAMLSourceConnectionSerializer
339
340    class Meta:
341        verbose_name = _("User SAML Source Connection")
342        verbose_name_plural = _("User SAML Source Connections")

Connection to configured SAML Sources.

serializer: rest_framework.serializers.Serializer
334    @property
335    def serializer(self) -> Serializer:
336        from authentik.sources.saml.api.source_connection import UserSAMLSourceConnectionSerializer
337
338        return UserSAMLSourceConnectionSerializer

Get serializer for this model

usersourceconnection_ptr_id
usersourceconnection_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.

class UserSAMLSourceConnection.DoesNotExist(authentik.core.models.UserSourceConnection.DoesNotExist):

The requested object does not exist

class UserSAMLSourceConnection.MultipleObjectsReturned(authentik.core.models.UserSourceConnection.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class GroupSAMLSourceConnection(authentik.core.models.GroupSourceConnection):
345class GroupSAMLSourceConnection(GroupSourceConnection):
346    """Group-source connection"""
347
348    @property
349    def serializer(self) -> type[Serializer]:
350        from authentik.sources.saml.api.source_connection import (
351            GroupSAMLSourceConnectionSerializer,
352        )
353
354        return GroupSAMLSourceConnectionSerializer
355
356    class Meta:
357        verbose_name = _("Group SAML Source Connection")
358        verbose_name_plural = _("Group SAML Source Connections")

Group-source connection

serializer: type[rest_framework.serializers.Serializer]
348    @property
349    def serializer(self) -> type[Serializer]:
350        from authentik.sources.saml.api.source_connection import (
351            GroupSAMLSourceConnectionSerializer,
352        )
353
354        return GroupSAMLSourceConnectionSerializer

Get serializer for this model

groupsourceconnection_ptr_id
groupsourceconnection_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.

class GroupSAMLSourceConnection.DoesNotExist(authentik.core.models.GroupSourceConnection.DoesNotExist):

The requested object does not exist

class GroupSAMLSourceConnection.MultipleObjectsReturned(authentik.core.models.GroupSourceConnection.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.