sources/saml: Add NameID Policy field, sent with AuthnRequest
This commit is contained in:
parent
d831599608
commit
0e3e73989d
|
@ -15,6 +15,7 @@ class SAMLSourceSerializer(ModelSerializer):
|
||||||
fields = SOURCE_FORM_FIELDS + [
|
fields = SOURCE_FORM_FIELDS + [
|
||||||
"issuer",
|
"issuer",
|
||||||
"sso_url",
|
"sso_url",
|
||||||
|
"name_id_policy",
|
||||||
"binding_type",
|
"binding_type",
|
||||||
"slo_url",
|
"slo_url",
|
||||||
"temporary_user_delete_after",
|
"temporary_user_delete_after",
|
||||||
|
|
|
@ -23,6 +23,7 @@ class SAMLSourceForm(forms.ModelForm):
|
||||||
fields = SOURCE_FORM_FIELDS + [
|
fields = SOURCE_FORM_FIELDS + [
|
||||||
"issuer",
|
"issuer",
|
||||||
"sso_url",
|
"sso_url",
|
||||||
|
"name_id_policy",
|
||||||
"binding_type",
|
"binding_type",
|
||||||
"slo_url",
|
"slo_url",
|
||||||
"temporary_user_delete_after",
|
"temporary_user_delete_after",
|
||||||
|
|
|
@ -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.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -7,6 +7,13 @@ from passbook.core.models import Source
|
||||||
from passbook.core.types import UILoginButton
|
from passbook.core.types import UILoginButton
|
||||||
from passbook.crypto.models import CertificateKeyPair
|
from passbook.crypto.models import CertificateKeyPair
|
||||||
from passbook.providers.saml.utils.time import timedelta_string_validator
|
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):
|
class SAMLBindingTypes(models.TextChoices):
|
||||||
|
@ -17,6 +24,16 @@ class SAMLBindingTypes(models.TextChoices):
|
||||||
POST_AUTO = "POST_AUTO", _("POST Binding with auto-confirmation")
|
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):
|
class SAMLSource(Source):
|
||||||
"""Authenticate using an external SAML Identity Provider."""
|
"""Authenticate using an external SAML Identity Provider."""
|
||||||
|
|
||||||
|
@ -31,6 +48,13 @@ class SAMLSource(Source):
|
||||||
verbose_name=_("SSO URL"),
|
verbose_name=_("SSO URL"),
|
||||||
help_text=_("URL that the initial Login request is sent to."),
|
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(
|
binding_type = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
choices=SAMLBindingTypes.choices,
|
choices=SAMLBindingTypes.choices,
|
||||||
|
|
|
@ -127,6 +127,13 @@ class Processor:
|
||||||
def prepare_flow(self, request: HttpRequest) -> HttpResponse:
|
def prepare_flow(self, request: HttpRequest) -> HttpResponse:
|
||||||
"""Prepare flow plan depending on whether or not the user exists"""
|
"""Prepare flow plan depending on whether or not the user exists"""
|
||||||
name_id = self._get_name_id()
|
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.
|
# transient NameIDs are handeled seperately as they don't have to go through flows.
|
||||||
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
|
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
|
||||||
return self._handle_name_id_transient(request)
|
return self._handle_name_id_transient(request)
|
||||||
|
|
|
@ -8,4 +8,5 @@
|
||||||
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
|
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>
|
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">{{ ISSUER }}</saml:Issuer>
|
||||||
{{ AUTHN_REQUEST_SIGNATURE }}
|
{{ AUTHN_REQUEST_SIGNATURE }}
|
||||||
|
<samlp:NameIDPolicy Format="{{ NAME_ID_POLICY }}"></samlp:NameIDPolicy>
|
||||||
</samlp:AuthnRequest>
|
</samlp:AuthnRequest>
|
||||||
|
|
|
@ -41,6 +41,7 @@ class InitiateView(View):
|
||||||
"AUTHN_REQUEST_ID": get_random_id(),
|
"AUTHN_REQUEST_ID": get_random_id(),
|
||||||
"ISSUE_INSTANT": get_time_string(),
|
"ISSUE_INSTANT": get_time_string(),
|
||||||
"ISSUER": get_issuer(request, source),
|
"ISSUER": get_issuer(request, source),
|
||||||
|
"NAME_ID_POLICY": source.name_id_policy,
|
||||||
}
|
}
|
||||||
authn_req = get_authnrequest_xml(parameters, signed=False)
|
authn_req = get_authnrequest_xml(parameters, signed=False)
|
||||||
# If the source is configured for Redirect bindings, we can just redirect there
|
# If the source is configured for Redirect bindings, we can just redirect there
|
||||||
|
|
11
swagger.yaml
11
swagger.yaml
|
@ -6505,6 +6505,17 @@ definitions:
|
||||||
format: uri
|
format: uri
|
||||||
maxLength: 200
|
maxLength: 200
|
||||||
minLength: 1
|
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:
|
binding_type:
|
||||||
title: Binding type
|
title: Binding type
|
||||||
type: string
|
type: string
|
||||||
|
|
Reference in New Issue