providers/saml: make NameID configurable using a Property Mapping

This commit is contained in:
Jens Langhammer 2021-01-28 22:50:13 +01:00
parent 66a8b52c7c
commit 5ef4354723
7 changed files with 73 additions and 1 deletions

View File

@ -22,6 +22,7 @@ class SAMLProviderSerializer(ModelSerializer, MetaNameSerializer):
"assertion_valid_not_on_or_after",
"session_valid_not_on_or_after",
"property_mappings",
"name_id_mapping",
"digest_algorithm",
"signature_algorithm",
"signing_kp",

View File

@ -42,6 +42,7 @@ class SAMLProviderForm(forms.ModelForm):
"signing_kp",
"verification_kp",
"property_mappings",
"name_id_mapping",
"assertion_valid_not_before",
"assertion_valid_not_on_or_after",
"session_valid_not_on_or_after",
@ -84,7 +85,9 @@ class SAMLPropertyMappingForm(forms.ModelForm):
"saml_name": mark_safe(
_(
"URN OID used by SAML. This is optional. "
'<a href="https://www.rfc-editor.org/rfc/rfc2798.html#section-2">Reference</a>'
'<a href="https://www.rfc-editor.org/rfc/rfc2798.html#section-2">Reference</a>.'
" If this property mapping is used for NameID Property, "
"this field is discarded."
)
),
}

View File

@ -0,0 +1,27 @@
# Generated by Django 3.1.4 on 2021-01-28 21:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_saml", "0010_auto_20201230_2112"),
]
operations = [
migrations.AddField(
model_name="samlprovider",
name="name_id_mapping",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be considered",
null=True,
verbose_name="NameID Property Mapping",
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_providers_saml.samlpropertymapping",
),
),
]

View File

@ -65,6 +65,21 @@ class SAMLProvider(Provider):
),
)
name_id_mapping = models.ForeignKey(
"SAMLPropertyMapping",
default=None,
blank=True,
null=True,
on_delete=models.SET_DEFAULT,
verbose_name=_("NameID Property Mapping"),
help_text=_(
(
"Configure how the NameID value will be created. When left empty, "
"the NameIDPolicy of the incoming request will be considered"
)
),
)
assertion_valid_not_before = models.TextField(
default="minutes=-5",
validators=[timedelta_string_validator],

View File

@ -139,13 +139,30 @@ class AssertionProcessor:
audience.text = self.provider.audience
return conditions
# pylint: disable=too-many-return-statements
def get_name_id(self) -> Element:
"""Get NameID Element"""
name_id = Element(f"{{{NS_SAML_ASSERTION}}}NameID")
name_id.attrib["Format"] = self.auth_n_request.name_id_policy
# persistent is used as a fallback, so always generate it
persistent = sha256(
f"{self.http_request.user.id}-{settings.SECRET_KEY}".encode("ascii")
).hexdigest()
name_id.text = persistent
# If name_id_mapping is set, we override the value, regardless of what the SP asks for
if self.provider.name_id_mapping:
try:
value = self.provider.name_id_mapping.evaluate(
user=self.http_request.user,
request=self.http_request,
provider=self.provider,
)
if value is not None:
name_id.text = value
return name_id
except PropertyMappingExpressionException as exc:
LOGGER.warning(str(exc))
return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_EMAIL:
name_id.text = self.http_request.user.email
return name_id

View File

@ -1,8 +1,10 @@
"""email tests"""
from os import unlink
from pathlib import Path
from sys import platform
from tempfile import gettempdir, mkstemp
from typing import Any
from unittest.case import skipUnless
from django.conf import settings
from django.test import TestCase
@ -17,6 +19,7 @@ def get_templates_setting(temp_dir: str) -> dict[str, Any]:
return templates_setting
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestEmailStageTemplates(TestCase):
"""Email tests"""

View File

@ -8839,6 +8839,12 @@ definitions:
type: string
format: uuid
uniqueItems: true
name_id_mapping:
title: NameID Property Mapping
description: Configure how the NameID value will be created. When left empty,
the NameIDPolicy of the incoming request will be considered
type: string
x-nullable: true
digest_algorithm:
title: Digest algorithm
type: string