sources/saml: Add NameID Policy field, sent with AuthnRequest

This commit is contained in:
Jens Langhammer 2020-07-08 16:18:02 +02:00
parent d831599608
commit 0e3e73989d
8 changed files with 95 additions and 9 deletions

View file

@ -15,6 +15,7 @@ class SAMLSourceSerializer(ModelSerializer):
fields = SOURCE_FORM_FIELDS + [
"issuer",
"sso_url",
"name_id_policy",
"binding_type",
"slo_url",
"temporary_user_delete_after",

View file

@ -23,6 +23,7 @@ class SAMLSourceForm(forms.ModelForm):
fields = SOURCE_FORM_FIELDS + [
"issuer",
"sso_url",
"name_id_policy",
"binding_type",
"slo_url",
"temporary_user_delete_after",

View file

@ -0,0 +1,40 @@
# Generated by Django 3.0.8 on 2020-07-08 13:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_sources_saml", "0004_auto_20200708_1207"),
]
operations = [
migrations.AddField(
model_name="samlsource",
name="name_id_policy",
field=models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"Persistent",
),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName",
"X509",
),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
"Transient",
),
],
default="urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.",
),
),
]

View file

@ -7,6 +7,13 @@ from passbook.core.models import Source
from passbook.core.types import UILoginButton
from passbook.crypto.models import CertificateKeyPair
from passbook.providers.saml.utils.time import timedelta_string_validator
from passbook.sources.saml.processors.constants import (
SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PRESISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_WINDOWS,
SAML_NAME_ID_FORMAT_X509,
)
class SAMLBindingTypes(models.TextChoices):
@ -17,6 +24,16 @@ class SAMLBindingTypes(models.TextChoices):
POST_AUTO = "POST_AUTO", _("POST Binding with auto-confirmation")
class SAMLNameIDPolicy(models.TextChoices):
"""SAML NameID Policies"""
EMAIL = SAML_NAME_ID_FORMAT_EMAIL
PERSISTENT = SAML_NAME_ID_FORMAT_PRESISTENT
X509 = SAML_NAME_ID_FORMAT_X509
WINDOWS = SAML_NAME_ID_FORMAT_WINDOWS
TRANSIENT = SAML_NAME_ID_FORMAT_TRANSIENT
class SAMLSource(Source):
"""Authenticate using an external SAML Identity Provider."""
@ -31,6 +48,13 @@ class SAMLSource(Source):
verbose_name=_("SSO URL"),
help_text=_("URL that the initial Login request is sent to."),
)
name_id_policy = models.TextField(
choices=SAMLNameIDPolicy.choices,
default=SAMLNameIDPolicy.TRANSIENT,
help_text=_(
"NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent."
),
)
binding_type = models.CharField(
max_length=100,
choices=SAMLBindingTypes.choices,

View file

@ -127,6 +127,13 @@ class Processor:
def prepare_flow(self, request: HttpRequest) -> HttpResponse:
"""Prepare flow plan depending on whether or not the user exists"""
name_id = self._get_name_id()
# Sanity check, show a warning if NameIDPolicy doesn't match what we go
if self._source.name_id_policy != name_id.attrib["Format"]:
LOGGER.warning(
"NameID from IdP doesn't match our policy",
expected=self._source.name_id_policy,
got=name_id.attrib["Format"],
)
# transient NameIDs are handeled seperately as they don't have to go through flows.
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
return self._handle_name_id_transient(request)

View file

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<samlp:AuthnRequest AssertionConsumerServiceURL="{{ ACS_URL }}"
Destination="{{ DESTINATION }}"
ID="{{ AUTHN_REQUEST_ID }}"
IssueInstant="{{ ISSUE_INSTANT }}"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">{{ ISSUER }}</saml:Issuer>
{{ AUTHN_REQUEST_SIGNATURE }}
<samlp:AuthnRequest AssertionConsumerServiceURL="{{ ACS_URL }}"
Destination="{{ DESTINATION }}"
ID="{{ AUTHN_REQUEST_ID }}"
IssueInstant="{{ ISSUE_INSTANT }}"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">{{ ISSUER }}</saml:Issuer>
{{ AUTHN_REQUEST_SIGNATURE }}
<samlp:NameIDPolicy Format="{{ NAME_ID_POLICY }}"></samlp:NameIDPolicy>
</samlp:AuthnRequest>

View file

@ -41,6 +41,7 @@ class InitiateView(View):
"AUTHN_REQUEST_ID": get_random_id(),
"ISSUE_INSTANT": get_time_string(),
"ISSUER": get_issuer(request, source),
"NAME_ID_POLICY": source.name_id_policy,
}
authn_req = get_authnrequest_xml(parameters, signed=False)
# If the source is configured for Redirect bindings, we can just redirect there

View file

@ -6505,6 +6505,17 @@ definitions:
format: uri
maxLength: 200
minLength: 1
name_id_policy:
title: Name id policy
description: NameID Policy sent to the IdP. Can be unset, in which case no
Policy is sent.
type: string
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
binding_type:
title: Binding type
type: string