mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
providers/saml: generate issuer url when provider is set on app (#18022)
* providers/saml: generate issuer url in saml processors unless overridded * remove issuer * remove duplicate * Generate url when assertion is created and save to session * cleanup * Fix front-end rendering of issuer * Update web/src/admin/providers/saml/SAMLProviderViewPage.ts Co-authored-by: Jens L. <jens@goauthentik.io> Signed-off-by: Connor Peshek <connor@connorpeshek.me> * Update authentik/providers/saml/models.py Co-authored-by: Jens L. <jens@goauthentik.io> Signed-off-by: Connor Peshek <connor@connorpeshek.me> * Update authentik/providers/saml/models.py Co-authored-by: Jens L. <jens@goauthentik.io> Signed-off-by: Connor Peshek <connor@connorpeshek.me> * use reverse for urls and update tests * update issuer description * Don't absorb sp entity id * rename issuer_url to issuer_override * fix migration file to rename to override * fix migration file order * lint, fix tests * fix tests * fix once again not importing the sp issuer * build * use const for default issuer --------- Signed-off-by: Connor Peshek <connor@connorpeshek.me> Co-authored-by: connor peshek <connorpeshek@connors-MacBook-Pro.local> Co-authored-by: Jens L. <jens@goauthentik.io>
This commit is contained in:
@@ -30,6 +30,8 @@ SAML_BINDING_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
|
||||
SAML_STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
||||
|
||||
DEFAULT_ISSUER = "authentik"
|
||||
|
||||
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
||||
# https://datatracker.ietf.org/doc/html/rfc4051#section-2.3.2
|
||||
|
||||
@@ -24,7 +24,11 @@ from rest_framework.viewsets import ModelViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.validation import validate
|
||||
from authentik.common.saml.constants import SAML_BINDING_POST, SAML_BINDING_REDIRECT
|
||||
from authentik.common.saml.constants import (
|
||||
DEFAULT_ISSUER,
|
||||
SAML_BINDING_POST,
|
||||
SAML_BINDING_REDIRECT,
|
||||
)
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer, PropertyMappingPreviewSerializer
|
||||
@@ -55,6 +59,7 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
"""SAMLProvider Serializer"""
|
||||
|
||||
url_download_metadata = SerializerMethodField()
|
||||
url_issuer = SerializerMethodField()
|
||||
|
||||
url_sso_post = SerializerMethodField()
|
||||
url_sso_redirect = SerializerMethodField()
|
||||
@@ -85,6 +90,23 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
+ "?download"
|
||||
)
|
||||
|
||||
def get_url_issuer(self, instance: SAMLProvider) -> str:
|
||||
"""Get Issuer/EntityID URL"""
|
||||
if instance.issuer_override:
|
||||
return instance.issuer_override
|
||||
if "request" not in self._context:
|
||||
return DEFAULT_ISSUER
|
||||
request: HttpRequest = self._context["request"]._request
|
||||
try:
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_saml:base",
|
||||
kwargs={"application_slug": instance.application.slug},
|
||||
)
|
||||
)
|
||||
except Provider.application.RelatedObjectDoesNotExist:
|
||||
return DEFAULT_ISSUER
|
||||
|
||||
def get_url_sso_post(self, instance: SAMLProvider) -> str:
|
||||
"""Get SSO Post URL"""
|
||||
if "request" not in self._context:
|
||||
@@ -198,7 +220,7 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
"acs_url",
|
||||
"sls_url",
|
||||
"audience",
|
||||
"issuer",
|
||||
"issuer_override",
|
||||
"assertion_valid_not_before",
|
||||
"assertion_valid_not_on_or_after",
|
||||
"session_valid_not_on_or_after",
|
||||
@@ -220,6 +242,7 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
"default_relay_state",
|
||||
"default_name_id_policy",
|
||||
"url_download_metadata",
|
||||
"url_issuer",
|
||||
"url_sso_post",
|
||||
"url_sso_redirect",
|
||||
"url_sso_init",
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.2.11 on 2026-02-24 06:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_saml", "0021_samlprovider_sign_logout_response"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="samlprovider",
|
||||
old_name="issuer",
|
||||
new_name="issuer_override",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="samlprovider",
|
||||
name="issuer_override",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
help_text="Also known as EntityID. Providing a value overrides the default issuer generated by authentik.",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="samlsession",
|
||||
name="issuer",
|
||||
field=models.TextField(
|
||||
default=None, help_text="SAML Issuer used for this session", null=True
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -77,7 +77,14 @@ class SAMLProvider(Provider):
|
||||
"no audience restriction will be added."
|
||||
),
|
||||
)
|
||||
issuer = models.TextField(help_text=_("Also known as EntityID"), default="authentik")
|
||||
issuer_override = models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
help_text=_(
|
||||
"Also known as EntityID. Providing a value overrides the default issuer "
|
||||
"generated by authentik."
|
||||
),
|
||||
)
|
||||
sls_url = models.TextField(
|
||||
blank=True,
|
||||
validators=[DomainlessURLValidator(schemes=("http", "https"))],
|
||||
@@ -318,6 +325,9 @@ class SAMLSession(InternallyManagedMixin, SerializerModel, ExpiringModel):
|
||||
session_index = models.TextField(help_text=_("SAML SessionIndex for this session"))
|
||||
name_id = models.TextField(help_text=_("SAML NameID value for this session"))
|
||||
name_id_format = models.TextField(default="", blank=True, help_text=_("SAML NameID format"))
|
||||
issuer = models.TextField(
|
||||
default=None, null=True, help_text=_("SAML Issuer used for this session")
|
||||
)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@property
|
||||
|
||||
@@ -6,6 +6,7 @@ from types import GeneratorType
|
||||
|
||||
import xmlsec
|
||||
from django.http import HttpRequest
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from lxml import etree # nosec
|
||||
from lxml.etree import Element, SubElement, _Element # nosec
|
||||
@@ -63,6 +64,7 @@ class AssertionProcessor:
|
||||
session_index: str
|
||||
name_id: str
|
||||
name_id_format: str
|
||||
issuer: str
|
||||
session_not_on_or_after_datetime: datetime
|
||||
|
||||
def __init__(self, provider: SAMLProvider, request: HttpRequest, auth_n_request: AuthNRequest):
|
||||
@@ -137,10 +139,24 @@ class AssertionProcessor:
|
||||
continue
|
||||
return attribute_statement
|
||||
|
||||
def _get_issuer_value(self) -> str:
|
||||
"""Get issuer value, with fallback to generated URL if empty"""
|
||||
# If user has set an override issuer, use it
|
||||
if self.provider.issuer_override:
|
||||
return self.provider.issuer_override
|
||||
|
||||
return self.http_request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_saml:base",
|
||||
kwargs={"application_slug": self.provider.application.slug},
|
||||
)
|
||||
)
|
||||
|
||||
def get_issuer(self) -> Element:
|
||||
"""Get Issuer Element"""
|
||||
issuer = Element(f"{{{NS_SAML_ASSERTION}}}Issuer", nsmap=NS_MAP)
|
||||
issuer.text = self.provider.issuer
|
||||
self.issuer = self._get_issuer_value()
|
||||
issuer.text = self.issuer
|
||||
return issuer
|
||||
|
||||
def get_assertion_auth_n_statement(self) -> Element:
|
||||
|
||||
@@ -8,6 +8,7 @@ from lxml import etree # nosec
|
||||
from lxml.etree import Element, _Element
|
||||
|
||||
from authentik.common.saml.constants import (
|
||||
DEFAULT_ISSUER,
|
||||
DIGEST_ALGORITHM_TRANSLATION_MAP,
|
||||
NS_MAP,
|
||||
NS_SAML_ASSERTION,
|
||||
@@ -33,11 +34,12 @@ class LogoutRequestProcessor:
|
||||
name_id_format: str
|
||||
session_index: str | None
|
||||
relay_state: str | None
|
||||
issuer: str | None
|
||||
|
||||
_issue_instant: str
|
||||
_request_id: str
|
||||
|
||||
def __init__(
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
provider: SAMLProvider,
|
||||
user: User | None,
|
||||
@@ -46,6 +48,7 @@ class LogoutRequestProcessor:
|
||||
name_id_format: str = SAML_NAME_ID_FORMAT_EMAIL,
|
||||
session_index: str | None = None,
|
||||
relay_state: str | None = None,
|
||||
issuer: str | None = None,
|
||||
):
|
||||
self.provider = provider
|
||||
self.user = user
|
||||
@@ -54,14 +57,23 @@ class LogoutRequestProcessor:
|
||||
self.name_id_format = name_id_format
|
||||
self.session_index = session_index
|
||||
self.relay_state = relay_state
|
||||
self.issuer = issuer
|
||||
|
||||
self._issue_instant = get_time_string()
|
||||
self._request_id = get_random_id()
|
||||
|
||||
def _get_issuer_value(self) -> str:
|
||||
"""Get issuer value from session, with fallback to provider"""
|
||||
if self.issuer:
|
||||
return self.issuer
|
||||
if self.provider.issuer_override:
|
||||
return self.provider.issuer_override
|
||||
return DEFAULT_ISSUER
|
||||
|
||||
def get_issuer(self) -> Element:
|
||||
"""Get Issuer element"""
|
||||
issuer = Element(f"{{{NS_SAML_ASSERTION}}}Issuer")
|
||||
issuer.text = self.provider.issuer
|
||||
issuer.text = self._get_issuer_value()
|
||||
return issuer
|
||||
|
||||
def get_name_id(self) -> Element:
|
||||
|
||||
@@ -8,6 +8,7 @@ from lxml import etree
|
||||
from lxml.etree import Element, SubElement
|
||||
|
||||
from authentik.common.saml.constants import (
|
||||
DEFAULT_ISSUER,
|
||||
DIGEST_ALGORITHM_TRANSLATION_MAP,
|
||||
NS_MAP,
|
||||
NS_SAML_ASSERTION,
|
||||
@@ -28,27 +29,38 @@ class LogoutResponseProcessor:
|
||||
logout_request: LogoutRequest
|
||||
destination: str | None
|
||||
relay_state: str | None
|
||||
issuer: str | None
|
||||
_issue_instant: str
|
||||
_response_id: str
|
||||
|
||||
def __init__(
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
provider: SAMLProvider,
|
||||
logout_request: LogoutRequest,
|
||||
destination: str | None = None,
|
||||
relay_state: str | None = None,
|
||||
issuer: str | None = None,
|
||||
):
|
||||
self.provider = provider
|
||||
self.logout_request = logout_request
|
||||
self.destination = destination
|
||||
self.relay_state = relay_state or (logout_request.relay_state if logout_request else None)
|
||||
self.issuer = issuer
|
||||
self._issue_instant = get_time_string()
|
||||
self._response_id = get_random_id()
|
||||
|
||||
def _get_issuer_value(self) -> str:
|
||||
"""Get issuer value from session, with fallback to provider"""
|
||||
if self.issuer:
|
||||
return self.issuer
|
||||
if self.provider.issuer_override:
|
||||
return self.provider.issuer_override
|
||||
return DEFAULT_ISSUER
|
||||
|
||||
def get_issuer(self) -> Element:
|
||||
"""Get Issuer element"""
|
||||
issuer = Element(f"{{{NS_SAML_ASSERTION}}}Issuer")
|
||||
issuer.text = self.provider.issuer
|
||||
issuer.text = self._get_issuer_value()
|
||||
return issuer
|
||||
|
||||
def build(self, status: str = "Success") -> Element:
|
||||
|
||||
@@ -40,6 +40,19 @@ class MetadataProcessor:
|
||||
self.force_binding = None
|
||||
self.xml_id = "_" + sha256(f"{provider.name}-{provider.pk}".encode("ascii")).hexdigest()
|
||||
|
||||
def _get_issuer_value(self) -> str:
|
||||
"""Get issuer value, with fallback to generated URL if empty"""
|
||||
# If user has set an override issuer, use it
|
||||
if self.provider.issuer_override:
|
||||
return self.provider.issuer_override
|
||||
|
||||
return self.http_request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_saml:base",
|
||||
kwargs={"application_slug": self.provider.application.slug},
|
||||
)
|
||||
)
|
||||
|
||||
# Using type unions doesn't work with cython types (which is what lxml is)
|
||||
def get_signing_key_descriptor(self) -> Element | None:
|
||||
"""Get Signing KeyDescriptor, if enabled for the provider"""
|
||||
@@ -189,7 +202,7 @@ class MetadataProcessor:
|
||||
"""Build full EntityDescriptor"""
|
||||
entity_descriptor = Element(f"{{{NS_SAML_METADATA}}}EntityDescriptor", nsmap=NS_MAP)
|
||||
entity_descriptor.attrib["ID"] = self.xml_id
|
||||
entity_descriptor.attrib["entityID"] = self.provider.issuer
|
||||
entity_descriptor.attrib["entityID"] = self._get_issuer_value()
|
||||
|
||||
if self.provider.signing_kp:
|
||||
self._prepare_signature(entity_descriptor)
|
||||
|
||||
@@ -51,7 +51,6 @@ class ServiceProviderMetadata:
|
||||
provider = SAMLProvider.objects.create(
|
||||
name=name, authorization_flow=authorization_flow, invalidation_flow=invalidation_flow
|
||||
)
|
||||
provider.issuer = self.entity_id
|
||||
provider.sp_binding = self.acs_binding
|
||||
provider.acs_url = self.acs_location
|
||||
provider.default_name_id_policy = self.name_id_policy
|
||||
|
||||
@@ -75,6 +75,7 @@ def handle_saml_iframe_pre_user_logout(
|
||||
name_id_format=session.name_id_format,
|
||||
session_index=session.session_index,
|
||||
relay_state=relay_state,
|
||||
issuer=session.issuer,
|
||||
)
|
||||
|
||||
if session.provider.sls_binding == SAMLBindings.POST:
|
||||
@@ -163,6 +164,7 @@ def handle_flow_pre_user_logout(
|
||||
name_id_format=session.name_id_format,
|
||||
session_index=session.session_index,
|
||||
relay_state=relay_state,
|
||||
issuer=session.issuer,
|
||||
)
|
||||
|
||||
if session.provider.sls_binding == SAMLBindings.POST:
|
||||
@@ -224,6 +226,7 @@ def user_session_deleted_saml_logout(sender, instance: AuthenticatedSession, **_
|
||||
name_id=saml_session.name_id,
|
||||
name_id_format=saml_session.name_id_format,
|
||||
session_index=saml_session.session_index,
|
||||
issuer=saml_session.issuer,
|
||||
)
|
||||
|
||||
|
||||
@@ -257,4 +260,5 @@ def user_deactivated_saml_logout(sender, instance: User, **kwargs):
|
||||
name_id=saml_session.name_id,
|
||||
name_id_format=saml_session.name_id_format,
|
||||
session_index=saml_session.session_index,
|
||||
issuer=saml_session.issuer,
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ def send_saml_logout_request(
|
||||
name_id: str,
|
||||
name_id_format: str,
|
||||
session_index: str,
|
||||
issuer: str,
|
||||
):
|
||||
"""Send SAML LogoutRequest to a Service Provider using session data"""
|
||||
provider = SAMLProvider.objects.filter(pk=provider_pk).first()
|
||||
@@ -47,6 +48,7 @@ def send_saml_logout_request(
|
||||
name_id=name_id,
|
||||
name_id_format=name_id_format,
|
||||
session_index=session_index,
|
||||
issuer=issuer,
|
||||
)
|
||||
|
||||
return send_post_logout_request(provider, processor)
|
||||
@@ -89,6 +91,7 @@ def send_saml_logout_response(
|
||||
sls_url: str,
|
||||
logout_request_id: str | None = None,
|
||||
relay_state: str | None = None,
|
||||
issuer: str | None = None,
|
||||
):
|
||||
"""Send SAML LogoutResponse to a Service Provider using backchannel (server-to-server)"""
|
||||
provider = SAMLProvider.objects.filter(pk=provider_pk).first()
|
||||
@@ -119,6 +122,7 @@ def send_saml_logout_response(
|
||||
logout_request=logout_request,
|
||||
destination=sls_url,
|
||||
relay_state=relay_state,
|
||||
issuer=issuer,
|
||||
)
|
||||
|
||||
encoded_response = processor.encode_post()
|
||||
|
||||
@@ -15,6 +15,7 @@ from authentik.common.saml.constants import (
|
||||
SAML_NAME_ID_FORMAT_EMAIL,
|
||||
SAML_NAME_ID_FORMAT_UNSPECIFIED,
|
||||
)
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import (
|
||||
RequestFactory,
|
||||
create_test_admin_user,
|
||||
@@ -97,6 +98,11 @@ class TestAuthNRequest(TestCase):
|
||||
)
|
||||
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||
self.provider.save()
|
||||
Application.objects.create(
|
||||
name="test-app",
|
||||
slug="test-app",
|
||||
provider=self.provider,
|
||||
)
|
||||
self.source = SAMLSource.objects.create(
|
||||
slug="provider",
|
||||
issuer="authentik",
|
||||
@@ -526,7 +532,7 @@ class TestAuthNRequest(TestCase):
|
||||
authorization_flow=create_test_flow(),
|
||||
acs_url="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
audience="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
issuer="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
issuer_override="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
signing_kp=static_keypair,
|
||||
verification_kp=static_keypair,
|
||||
)
|
||||
@@ -547,7 +553,7 @@ class TestAuthNRequest(TestCase):
|
||||
"saml/acs/2d737f96-55fb-4035-953e-5e24134eb778"
|
||||
),
|
||||
audience="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
issuer="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
issuer_override="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
signing_kp=create_test_cert(),
|
||||
)
|
||||
parsed_request = AuthNRequestParser(provider).parse(POST_REQUEST)
|
||||
|
||||
@@ -47,7 +47,7 @@ class TestNativeLogoutStageView(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp1.example.com/acs",
|
||||
sls_url="https://sp1.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
logout_method=SAMLLogoutMethods.FRONTCHANNEL_NATIVE,
|
||||
@@ -58,7 +58,7 @@ class TestNativeLogoutStageView(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp2.example.com/acs",
|
||||
sls_url="https://sp2.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
sp_binding="post",
|
||||
sls_binding="post",
|
||||
logout_method=SAMLLogoutMethods.FRONTCHANNEL_NATIVE,
|
||||
@@ -218,7 +218,7 @@ class TestIframeLogoutStageView(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp1.example.com/acs",
|
||||
sls_url="https://sp1.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
logout_method="frontchannel_iframe",
|
||||
@@ -229,7 +229,7 @@ class TestIframeLogoutStageView(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp2.example.com/acs",
|
||||
sls_url="https://sp2.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
sp_binding="post",
|
||||
sls_binding="post",
|
||||
logout_method="frontchannel_iframe",
|
||||
@@ -372,7 +372,7 @@ class TestIdPLogoutIntegration(FlowTestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
signing_kp=self.keypair,
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestLogoutIntegration(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
signature_algorithm=RSA_SHA256,
|
||||
@@ -57,7 +57,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = self.parser.parse(encoded)
|
||||
|
||||
# Verify all fields match
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.name_id, "test@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
self.assertEqual(parsed.session_index, "test-session-123")
|
||||
@@ -72,7 +72,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = self.parser.parse_detached(encoded)
|
||||
|
||||
# Verify all fields match
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.name_id, "test@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
self.assertEqual(parsed.session_index, "test-session-123")
|
||||
@@ -106,7 +106,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = parser.parse(encoded)
|
||||
|
||||
# Verify all fields match
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.name_id, "signed@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
self.assertEqual(parsed.session_index, "signed-session-456")
|
||||
@@ -125,7 +125,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = self.parser.parse_detached(saml_request)
|
||||
|
||||
# Verify parsing succeeded
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.name_id, "test@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
|
||||
@@ -164,7 +164,7 @@ class TestLogoutIntegration(TestCase):
|
||||
|
||||
# Parse the SAMLRequest (unsigned XML)
|
||||
parsed = self.parser.parse_detached(params["SAMLRequest"][0])
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
|
||||
def test_form_data_can_be_parsed(self):
|
||||
"""Test that form data generates parseable POST request"""
|
||||
@@ -175,7 +175,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = self.parser.parse(form_data["SAMLRequest"])
|
||||
|
||||
# Verify parsing succeeded
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.name_id, "test@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
self.assertEqual(parsed.session_index, "test-session-123")
|
||||
@@ -244,4 +244,4 @@ class TestLogoutIntegration(TestCase):
|
||||
|
||||
# But same issuer
|
||||
self.assertEqual(parsed1.issuer, parsed2.issuer)
|
||||
self.assertEqual(parsed1.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed1.issuer, self.provider.issuer_override)
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestLogoutRequestProcessor(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
signature_algorithm=RSA_SHA256,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""logout response tests"""
|
||||
|
||||
from defusedxml import ElementTree
|
||||
from django.test import TestCase
|
||||
from django.test import RequestFactory, TestCase
|
||||
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.common.saml.constants import (
|
||||
@@ -9,10 +9,13 @@ from authentik.common.saml.constants import (
|
||||
NS_SAML_PROTOCOL,
|
||||
NS_SIGNATURE,
|
||||
)
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
|
||||
from authentik.providers.saml.processors.logout_request_parser import LogoutRequest
|
||||
from authentik.providers.saml.processors.logout_response_processor import LogoutResponseProcessor
|
||||
from authentik.providers.saml.processors.metadata import MetadataProcessor
|
||||
|
||||
|
||||
class TestLogoutResponse(TestCase):
|
||||
@@ -21,6 +24,7 @@ class TestLogoutResponse(TestCase):
|
||||
@apply_blueprint("system/providers-saml.yaml")
|
||||
def setUp(self):
|
||||
cert = create_test_cert()
|
||||
self.factory = RequestFactory()
|
||||
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
||||
authorization_flow=create_test_flow(),
|
||||
acs_url="http://testserver/source/saml/provider/acs/",
|
||||
@@ -30,17 +34,31 @@ class TestLogoutResponse(TestCase):
|
||||
)
|
||||
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||
self.provider.save()
|
||||
self.application = Application.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
provider=self.provider,
|
||||
)
|
||||
|
||||
def test_build_response(self):
|
||||
"""Test building a LogoutResponse"""
|
||||
"""Test building a LogoutResponse uses the generated issuer from the assertion"""
|
||||
# Generate the issuer the same way the assertion/metadata processors would
|
||||
request = self.factory.get("/")
|
||||
metadata_processor = MetadataProcessor(self.provider, request)
|
||||
generated_issuer = metadata_processor._get_issuer_value()
|
||||
|
||||
logout_request = LogoutRequest(
|
||||
id="test-request-id",
|
||||
issuer="test-sp",
|
||||
relay_state="test-relay-state",
|
||||
)
|
||||
|
||||
# Pass the generated issuer as if it came from SAMLSession.issuer
|
||||
processor = LogoutResponseProcessor(
|
||||
self.provider, logout_request, destination=self.provider.sls_url
|
||||
self.provider,
|
||||
logout_request,
|
||||
destination=self.provider.sls_url,
|
||||
issuer=generated_issuer,
|
||||
)
|
||||
response_xml = processor.build_response(status="Success")
|
||||
|
||||
@@ -51,9 +69,9 @@ class TestLogoutResponse(TestCase):
|
||||
self.assertEqual(root.attrib["Destination"], self.provider.sls_url)
|
||||
self.assertEqual(root.attrib["InResponseTo"], "test-request-id")
|
||||
|
||||
# Check Issuer
|
||||
# Check Issuer matches the generated issuer from the assertion processor
|
||||
issuer = root.find(f"{{{NS_SAML_ASSERTION}}}Issuer")
|
||||
self.assertEqual(issuer.text, self.provider.issuer)
|
||||
self.assertEqual(issuer.text, generated_issuer)
|
||||
|
||||
# Check Status
|
||||
status = root.find(f".//{{{NS_SAML_PROTOCOL}}}StatusCode")
|
||||
|
||||
@@ -85,7 +85,6 @@ class TestServiceProviderMetadataParser(TestCase):
|
||||
metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/simple.xml"))
|
||||
provider = metadata.to_provider("test", self.flow, self.flow)
|
||||
self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs")
|
||||
self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata")
|
||||
self.assertEqual(provider.sp_binding, SAMLBindings.POST)
|
||||
self.assertEqual(provider.default_name_id_policy, SAMLNameIDPolicy.EMAIL)
|
||||
self.assertEqual(
|
||||
@@ -99,7 +98,6 @@ class TestServiceProviderMetadataParser(TestCase):
|
||||
metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/cert.xml"))
|
||||
provider = metadata.to_provider("test", self.flow, self.flow)
|
||||
self.assertEqual(provider.acs_url, "http://localhost:8080/apps/user_saml/saml/acs")
|
||||
self.assertEqual(provider.issuer, "http://localhost:8080/apps/user_saml/saml/metadata")
|
||||
self.assertEqual(provider.sp_binding, SAMLBindings.POST)
|
||||
self.assertEqual(
|
||||
provider.verification_kp.certificate_data, load_fixture("fixtures/cert.pem")
|
||||
|
||||
@@ -32,7 +32,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name="test-provider",
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
)
|
||||
|
||||
# Create another provider for testing
|
||||
@@ -40,7 +40,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name="test-provider-2",
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp2.example.com/acs",
|
||||
issuer="https://idp2.example.com",
|
||||
issuer_override="https://idp2.example.com",
|
||||
)
|
||||
|
||||
# Create a session first (using authentik's custom Session model)
|
||||
@@ -72,6 +72,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify the session was created
|
||||
@@ -100,6 +101,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Try to create another session with same session_index and provider
|
||||
@@ -113,6 +115,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
def test_cascade_deletion_user(self):
|
||||
@@ -127,6 +130,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify session exists
|
||||
@@ -150,6 +154,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify session exists
|
||||
@@ -173,6 +178,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify session exists
|
||||
@@ -196,6 +202,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Create second session with different provider
|
||||
@@ -208,6 +215,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify both sessions exist
|
||||
@@ -229,6 +237,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=future_time,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify expiry time
|
||||
@@ -248,6 +257,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=past_time,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Check if marked as expired
|
||||
@@ -265,6 +275,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format="", # Blank format
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify it was created successfully
|
||||
@@ -283,6 +294,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
session2 = SAMLSession.objects.create(
|
||||
@@ -294,6 +306,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Query by provider
|
||||
@@ -316,6 +329,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Check serializer property
|
||||
@@ -334,6 +348,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify sessions exist
|
||||
|
||||
@@ -7,6 +7,7 @@ from guardian.shortcuts import get_anonymous_user
|
||||
from lxml import etree # nosec
|
||||
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import RequestFactory, create_test_cert, create_test_flow
|
||||
from authentik.lib.xml import lxml_from_string
|
||||
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
|
||||
@@ -30,6 +31,11 @@ class TestSchema(TestCase):
|
||||
)
|
||||
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||
self.provider.save()
|
||||
Application.objects.create(
|
||||
name="test-app",
|
||||
slug="test-app",
|
||||
provider=self.provider,
|
||||
)
|
||||
self.source = SAMLSource.objects.create(
|
||||
slug="provider",
|
||||
issuer="authentik",
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestSendSamlLogoutResponse(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
signing_kp=self.cert,
|
||||
)
|
||||
|
||||
@@ -137,7 +137,7 @@ class TestSendSamlLogoutRequest(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
signing_kp=self.cert,
|
||||
)
|
||||
|
||||
@@ -155,6 +155,7 @@ class TestSendSamlLogoutRequest(TestCase):
|
||||
name_id="test@example.com",
|
||||
name_id_format=SAML_NAME_ID_FORMAT_EMAIL,
|
||||
session_index="test-session-123",
|
||||
issuer="https://idp.example.com",
|
||||
)
|
||||
|
||||
self.assertTrue(result)
|
||||
@@ -179,6 +180,7 @@ class TestSendSamlLogoutRequest(TestCase):
|
||||
name_id="test@example.com",
|
||||
name_id_format=SAML_NAME_ID_FORMAT_EMAIL,
|
||||
session_index="test-session-123",
|
||||
issuer="https://idp.example.com",
|
||||
)
|
||||
|
||||
self.assertFalse(result)
|
||||
@@ -198,6 +200,7 @@ class TestSendSamlLogoutRequest(TestCase):
|
||||
name_id="test@example.com",
|
||||
name_id_format=SAML_NAME_ID_FORMAT_EMAIL,
|
||||
session_index="test-session-123",
|
||||
issuer="https://idp.example.com",
|
||||
)
|
||||
|
||||
|
||||
@@ -214,7 +217,7 @@ class TestSendPostLogoutRequest(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
signing_kp=self.cert,
|
||||
)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class TestSPInitiatedSLOViews(TestCase):
|
||||
invalidation_flow=self.invalidation_flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
)
|
||||
@@ -90,7 +90,7 @@ class TestSPInitiatedSLOViews(TestCase):
|
||||
# Verify logout request was stored in plan context
|
||||
self.assertIn("authentik/providers/saml/logout_request", view.plan_context)
|
||||
logout_request = view.plan_context["authentik/providers/saml/logout_request"]
|
||||
self.assertEqual(logout_request.issuer, self.provider.issuer)
|
||||
self.assertEqual(logout_request.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(logout_request.session_index, "test-session-123")
|
||||
|
||||
def test_redirect_view_handles_logout_response_with_plan_context(self):
|
||||
@@ -228,7 +228,7 @@ class TestSPInitiatedSLOViews(TestCase):
|
||||
# Verify logout request was stored in plan context
|
||||
self.assertIn("authentik/providers/saml/logout_request", view.plan_context)
|
||||
logout_request = view.plan_context["authentik/providers/saml/logout_request"]
|
||||
self.assertEqual(logout_request.issuer, self.provider.issuer)
|
||||
self.assertEqual(logout_request.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(logout_request.session_index, "test-session-123")
|
||||
|
||||
def test_post_view_handles_logout_response_with_plan_context(self):
|
||||
@@ -396,7 +396,7 @@ class TestSPInitiatedSLOViews(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp2.example.com/acs",
|
||||
sls_url="https://sp2.example.com/sls",
|
||||
issuer="https://idp2.example.com",
|
||||
issuer_override="https://idp2.example.com",
|
||||
invalidation_flow=None, # No invalidation flow
|
||||
)
|
||||
|
||||
@@ -524,7 +524,7 @@ class TestSPInitiatedSLOLogoutMethods(TestCase):
|
||||
invalidation_flow=self.invalidation_flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
signing_kp=self.cert,
|
||||
@@ -714,7 +714,7 @@ class TestSPInitiatedSLOLogoutMethods(TestCase):
|
||||
invalidation_flow=self.invalidation_flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="", # No SLS URL
|
||||
issuer="https://idp.example.com",
|
||||
issuer_override="https://idp.example.com",
|
||||
)
|
||||
|
||||
app_no_sls = Application.objects.create(
|
||||
|
||||
@@ -11,6 +11,12 @@ from authentik.providers.saml.views.sp_slo import (
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
# Base path for Issuer/Entity ID
|
||||
path(
|
||||
"<slug:application_slug>/",
|
||||
sso.SAMLSSOBindingRedirectView.as_view(),
|
||||
name="base",
|
||||
),
|
||||
# SSO Bindings
|
||||
path(
|
||||
"<slug:application_slug>/sso/binding/redirect/",
|
||||
|
||||
@@ -81,6 +81,7 @@ class SAMLFlowFinalView(ChallengeStageView):
|
||||
"session": auth_session,
|
||||
"name_id": processor.name_id,
|
||||
"name_id_format": processor.name_id_format,
|
||||
"issuer": processor.issuer,
|
||||
"expires": processor.session_not_on_or_after_datetime,
|
||||
"expiring": True,
|
||||
},
|
||||
|
||||
@@ -107,12 +107,25 @@ class SPInitiatedSLOView(PolicyAccessView):
|
||||
# Store relay state for the logout response
|
||||
plan.context[PLAN_CONTEXT_SAML_RELAY_STATE] = relay_state
|
||||
|
||||
# Look up the session issuer to use in the logout response
|
||||
auth_session = AuthenticatedSession.from_request(request, request.user)
|
||||
session_issuer = None
|
||||
if auth_session:
|
||||
saml_session = SAMLSession.objects.filter(
|
||||
session=auth_session,
|
||||
user=request.user,
|
||||
provider=self.provider,
|
||||
).first()
|
||||
if saml_session:
|
||||
session_issuer = saml_session.issuer
|
||||
|
||||
if self.provider.logout_method == SAMLLogoutMethods.FRONTCHANNEL_NATIVE:
|
||||
# Native mode - user will be redirected/posted away from authentik
|
||||
processor = LogoutResponseProcessor(
|
||||
self.provider,
|
||||
logout_request,
|
||||
destination=self.provider.sls_url,
|
||||
issuer=session_issuer,
|
||||
)
|
||||
|
||||
if self.provider.sls_binding == SAMLBindings.POST:
|
||||
@@ -152,6 +165,7 @@ class SPInitiatedSLOView(PolicyAccessView):
|
||||
sls_url=self.provider.sls_url,
|
||||
logout_request_id=logout_request.id if logout_request else None,
|
||||
relay_state=relay_state,
|
||||
issuer=session_issuer,
|
||||
)
|
||||
|
||||
LOGGER.debug(
|
||||
@@ -168,6 +182,7 @@ class SPInitiatedSLOView(PolicyAccessView):
|
||||
self.provider,
|
||||
logout_request,
|
||||
destination=self.provider.sls_url,
|
||||
issuer=session_issuer,
|
||||
)
|
||||
|
||||
logout_response = processor.build_response()
|
||||
|
||||
@@ -10817,11 +10817,10 @@
|
||||
"title": "Audience",
|
||||
"description": "Value of the audience restriction field of the assertion. When left empty, no audience restriction will be added."
|
||||
},
|
||||
"issuer": {
|
||||
"issuer_override": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Issuer",
|
||||
"description": "Also known as EntityID"
|
||||
"title": "Issuer override",
|
||||
"description": "Also known as EntityID. Providing a value overrides the default issuer generated by authentik."
|
||||
},
|
||||
"assertion_valid_not_before": {
|
||||
"type": "string",
|
||||
|
||||
+6
-6
@@ -634,7 +634,7 @@ export interface ProvidersSamlListRequest {
|
||||
encryptionKp?: string;
|
||||
invalidationFlow?: string;
|
||||
isBackchannel?: boolean;
|
||||
issuer?: string;
|
||||
issuerOverride?: string;
|
||||
logoutMethod?: SAMLLogoutMethods;
|
||||
name?: string;
|
||||
nameIdMapping?: string;
|
||||
@@ -841,7 +841,7 @@ export interface ProvidersWsfedListRequest {
|
||||
encryptionKp?: string;
|
||||
invalidationFlow?: string;
|
||||
isBackchannel?: boolean;
|
||||
issuer?: string;
|
||||
issuerOverride?: string;
|
||||
logoutMethod?: SAMLLogoutMethods;
|
||||
name?: string;
|
||||
nameIdMapping?: string;
|
||||
@@ -6842,8 +6842,8 @@ export class ProvidersApi extends runtime.BaseAPI {
|
||||
queryParameters["is_backchannel"] = requestParameters["isBackchannel"];
|
||||
}
|
||||
|
||||
if (requestParameters["issuer"] != null) {
|
||||
queryParameters["issuer"] = requestParameters["issuer"];
|
||||
if (requestParameters["issuerOverride"] != null) {
|
||||
queryParameters["issuer_override"] = requestParameters["issuerOverride"];
|
||||
}
|
||||
|
||||
if (requestParameters["logoutMethod"] != null) {
|
||||
@@ -9326,8 +9326,8 @@ export class ProvidersApi extends runtime.BaseAPI {
|
||||
queryParameters["is_backchannel"] = requestParameters["isBackchannel"];
|
||||
}
|
||||
|
||||
if (requestParameters["issuer"] != null) {
|
||||
queryParameters["issuer"] = requestParameters["issuer"];
|
||||
if (requestParameters["issuerOverride"] != null) {
|
||||
queryParameters["issuer_override"] = requestParameters["issuerOverride"];
|
||||
}
|
||||
|
||||
if (requestParameters["logoutMethod"] != null) {
|
||||
|
||||
+4
-4
@@ -81,11 +81,11 @@ export interface PatchedSAMLProviderRequest {
|
||||
*/
|
||||
audience?: string;
|
||||
/**
|
||||
* Also known as EntityID
|
||||
* Also known as EntityID. Providing a value overrides the default issuer generated by authentik.
|
||||
* @type {string}
|
||||
* @memberof PatchedSAMLProviderRequest
|
||||
*/
|
||||
issuer?: string;
|
||||
issuerOverride?: string;
|
||||
/**
|
||||
* Assertion valid not before current time + this value (Format: hours=-1;minutes=-2;seconds=-3).
|
||||
* @type {string}
|
||||
@@ -233,7 +233,7 @@ export function PatchedSAMLProviderRequestFromJSONTyped(
|
||||
acsUrl: json["acs_url"] == null ? undefined : json["acs_url"],
|
||||
slsUrl: json["sls_url"] == null ? undefined : json["sls_url"],
|
||||
audience: json["audience"] == null ? undefined : json["audience"],
|
||||
issuer: json["issuer"] == null ? undefined : json["issuer"],
|
||||
issuerOverride: json["issuer_override"] == null ? undefined : json["issuer_override"],
|
||||
assertionValidNotBefore:
|
||||
json["assertion_valid_not_before"] == null
|
||||
? undefined
|
||||
@@ -306,7 +306,7 @@ export function PatchedSAMLProviderRequestToJSONTyped(
|
||||
acs_url: value["acsUrl"],
|
||||
sls_url: value["slsUrl"],
|
||||
audience: value["audience"],
|
||||
issuer: value["issuer"],
|
||||
issuer_override: value["issuerOverride"],
|
||||
assertion_valid_not_before: value["assertionValidNotBefore"],
|
||||
assertion_valid_not_on_or_after: value["assertionValidNotOnOrAfter"],
|
||||
session_valid_not_on_or_after: value["sessionValidNotOnOrAfter"],
|
||||
|
||||
+13
-4
@@ -135,11 +135,11 @@ export interface SAMLProvider {
|
||||
*/
|
||||
audience?: string;
|
||||
/**
|
||||
* Also known as EntityID
|
||||
* Also known as EntityID. Providing a value overrides the default issuer generated by authentik.
|
||||
* @type {string}
|
||||
* @memberof SAMLProvider
|
||||
*/
|
||||
issuer?: string;
|
||||
issuerOverride?: string;
|
||||
/**
|
||||
* Assertion valid not before current time + this value (Format: hours=-1;minutes=-2;seconds=-3).
|
||||
* @type {string}
|
||||
@@ -260,6 +260,12 @@ export interface SAMLProvider {
|
||||
* @memberof SAMLProvider
|
||||
*/
|
||||
readonly urlDownloadMetadata: string;
|
||||
/**
|
||||
* Get Issuer/EntityID URL
|
||||
* @type {string}
|
||||
* @memberof SAMLProvider
|
||||
*/
|
||||
readonly urlIssuer: string;
|
||||
/**
|
||||
* Get SSO Post URL
|
||||
* @type {string}
|
||||
@@ -321,6 +327,7 @@ export function instanceOfSAMLProvider(value: object): value is SAMLProvider {
|
||||
if (!("acsUrl" in value) || value["acsUrl"] === undefined) return false;
|
||||
if (!("urlDownloadMetadata" in value) || value["urlDownloadMetadata"] === undefined)
|
||||
return false;
|
||||
if (!("urlIssuer" in value) || value["urlIssuer"] === undefined) return false;
|
||||
if (!("urlSsoPost" in value) || value["urlSsoPost"] === undefined) return false;
|
||||
if (!("urlSsoRedirect" in value) || value["urlSsoRedirect"] === undefined) return false;
|
||||
if (!("urlSsoInit" in value) || value["urlSsoInit"] === undefined) return false;
|
||||
@@ -356,7 +363,7 @@ export function SAMLProviderFromJSONTyped(json: any, ignoreDiscriminator: boolea
|
||||
acsUrl: json["acs_url"],
|
||||
slsUrl: json["sls_url"] == null ? undefined : json["sls_url"],
|
||||
audience: json["audience"] == null ? undefined : json["audience"],
|
||||
issuer: json["issuer"] == null ? undefined : json["issuer"],
|
||||
issuerOverride: json["issuer_override"] == null ? undefined : json["issuer_override"],
|
||||
assertionValidNotBefore:
|
||||
json["assertion_valid_not_before"] == null
|
||||
? undefined
|
||||
@@ -406,6 +413,7 @@ export function SAMLProviderFromJSONTyped(json: any, ignoreDiscriminator: boolea
|
||||
? undefined
|
||||
: SAMLNameIDPolicyEnumFromJSON(json["default_name_id_policy"]),
|
||||
urlDownloadMetadata: json["url_download_metadata"],
|
||||
urlIssuer: json["url_issuer"],
|
||||
urlSsoPost: json["url_sso_post"],
|
||||
urlSsoRedirect: json["url_sso_redirect"],
|
||||
urlSsoInit: json["url_sso_init"],
|
||||
@@ -431,6 +439,7 @@ export function SAMLProviderToJSONTyped(
|
||||
| "verbose_name_plural"
|
||||
| "meta_model_name"
|
||||
| "url_download_metadata"
|
||||
| "url_issuer"
|
||||
| "url_sso_post"
|
||||
| "url_sso_redirect"
|
||||
| "url_sso_init"
|
||||
@@ -452,7 +461,7 @@ export function SAMLProviderToJSONTyped(
|
||||
acs_url: value["acsUrl"],
|
||||
sls_url: value["slsUrl"],
|
||||
audience: value["audience"],
|
||||
issuer: value["issuer"],
|
||||
issuer_override: value["issuerOverride"],
|
||||
assertion_valid_not_before: value["assertionValidNotBefore"],
|
||||
assertion_valid_not_on_or_after: value["assertionValidNotOnOrAfter"],
|
||||
session_valid_not_on_or_after: value["sessionValidNotOnOrAfter"],
|
||||
|
||||
+4
-4
@@ -81,11 +81,11 @@ export interface SAMLProviderRequest {
|
||||
*/
|
||||
audience?: string;
|
||||
/**
|
||||
* Also known as EntityID
|
||||
* Also known as EntityID. Providing a value overrides the default issuer generated by authentik.
|
||||
* @type {string}
|
||||
* @memberof SAMLProviderRequest
|
||||
*/
|
||||
issuer?: string;
|
||||
issuerOverride?: string;
|
||||
/**
|
||||
* Assertion valid not before current time + this value (Format: hours=-1;minutes=-2;seconds=-3).
|
||||
* @type {string}
|
||||
@@ -234,7 +234,7 @@ export function SAMLProviderRequestFromJSONTyped(
|
||||
acsUrl: json["acs_url"],
|
||||
slsUrl: json["sls_url"] == null ? undefined : json["sls_url"],
|
||||
audience: json["audience"] == null ? undefined : json["audience"],
|
||||
issuer: json["issuer"] == null ? undefined : json["issuer"],
|
||||
issuerOverride: json["issuer_override"] == null ? undefined : json["issuer_override"],
|
||||
assertionValidNotBefore:
|
||||
json["assertion_valid_not_before"] == null
|
||||
? undefined
|
||||
@@ -307,7 +307,7 @@ export function SAMLProviderRequestToJSONTyped(
|
||||
acs_url: value["acsUrl"],
|
||||
sls_url: value["slsUrl"],
|
||||
audience: value["audience"],
|
||||
issuer: value["issuer"],
|
||||
issuer_override: value["issuerOverride"],
|
||||
assertion_valid_not_before: value["assertionValidNotBefore"],
|
||||
assertion_valid_not_on_or_after: value["assertionValidNotOnOrAfter"],
|
||||
session_valid_not_on_or_after: value["sessionValidNotOnOrAfter"],
|
||||
|
||||
+16
-10
@@ -18919,7 +18919,7 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: issuer
|
||||
name: issuer_override
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
@@ -20078,7 +20078,7 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: issuer
|
||||
name: issuer_override
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
@@ -50202,10 +50202,10 @@ components:
|
||||
type: string
|
||||
description: Value of the audience restriction field of the assertion. When
|
||||
left empty, no audience restriction will be added.
|
||||
issuer:
|
||||
issuer_override:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Also known as EntityID
|
||||
description: Also known as EntityID. Providing a value overrides the default
|
||||
issuer generated by authentik.
|
||||
assertion_valid_not_before:
|
||||
type: string
|
||||
minLength: 1
|
||||
@@ -53724,9 +53724,10 @@ components:
|
||||
type: string
|
||||
description: Value of the audience restriction field of the assertion. When
|
||||
left empty, no audience restriction will be added.
|
||||
issuer:
|
||||
issuer_override:
|
||||
type: string
|
||||
description: Also known as EntityID
|
||||
description: Also known as EntityID. Providing a value overrides the default
|
||||
issuer generated by authentik.
|
||||
assertion_valid_not_before:
|
||||
type: string
|
||||
description: 'Assertion valid not before current time + this value (Format:
|
||||
@@ -53816,6 +53817,10 @@ components:
|
||||
type: string
|
||||
description: Get metadata download URL
|
||||
readOnly: true
|
||||
url_issuer:
|
||||
type: string
|
||||
description: Get Issuer/EntityID URL
|
||||
readOnly: true
|
||||
url_sso_post:
|
||||
type: string
|
||||
description: Get SSO Post URL
|
||||
@@ -53849,6 +53854,7 @@ components:
|
||||
- name
|
||||
- pk
|
||||
- url_download_metadata
|
||||
- url_issuer
|
||||
- url_slo_post
|
||||
- url_slo_redirect
|
||||
- url_sso_init
|
||||
@@ -53916,10 +53922,10 @@ components:
|
||||
type: string
|
||||
description: Value of the audience restriction field of the assertion. When
|
||||
left empty, no audience restriction will be added.
|
||||
issuer:
|
||||
issuer_override:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Also known as EntityID
|
||||
description: Also known as EntityID. Providing a value overrides the default
|
||||
issuer generated by authentik.
|
||||
assertion_valid_not_before:
|
||||
type: string
|
||||
minLength: 1
|
||||
|
||||
@@ -39,7 +39,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
"9009": "9009",
|
||||
},
|
||||
environment={
|
||||
"SP_ENTITY_ID": provider.issuer,
|
||||
"SP_ENTITY_ID": provider.issuer_override,
|
||||
"SP_SSO_BINDING": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
||||
"SP_METADATA_URL": metadata_url,
|
||||
**kwargs,
|
||||
@@ -68,7 +68,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -147,7 +147,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -226,7 +226,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -321,7 +321,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -415,7 +415,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -503,7 +503,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -553,7 +553,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
invalidation_flow=invalidation_flow,
|
||||
|
||||
@@ -42,7 +42,7 @@ const renderSAMLOverview: ProviderOverview<SAMLProvider> = (provider) => {
|
||||
return renderSummary("SAML", provider.name, [
|
||||
[msg("ACS URL"), provider.acsUrl],
|
||||
[msg("Audience"), provider.audience || "-"],
|
||||
[msg("Issuer"), provider.issuer],
|
||||
[msg("Issuer"), provider.urlIssuer],
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
@@ -196,15 +196,6 @@ export function renderForm({
|
||||
required
|
||||
.errorMessages=${errors.acsUrl}
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
label=${msg("Issuer")}
|
||||
input-hint="code"
|
||||
name="issuer"
|
||||
value="${provider.issuer || "authentik"}"
|
||||
required
|
||||
.errorMessages=${errors.issuer}
|
||||
help=${msg("Also known as Entity ID.")}
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
name="audience"
|
||||
label=${msg("Audience")}
|
||||
@@ -436,6 +427,15 @@ export function renderForm({
|
||||
"When using IDP-initiated logins, the relay state will be set to this value.",
|
||||
)}
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
label=${msg("EntityID/Issuer override")}
|
||||
name="issuerOverride"
|
||||
value="${ifDefined(provider.issuerOverride ?? undefined)}"
|
||||
.errorMessages=${errors.issuerOverride}
|
||||
help=${msg(
|
||||
"Sets a custom EntityID/Issuer to override the authentik generated default.",
|
||||
)}
|
||||
></ak-text-input>
|
||||
<ak-radio-input
|
||||
label=${msg("Service Provider Binding")}
|
||||
name="spBinding"
|
||||
|
||||
@@ -350,7 +350,7 @@ export class SAMLProviderViewPage extends AKElement {
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.issuer}
|
||||
${this.provider.issuerOverride}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
@@ -385,7 +385,7 @@ export class SAMLProviderViewPage extends AKElement {
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${ifDefined(this.provider?.issuer)}"
|
||||
value="${ifDefined(this.provider?.urlIssuer)}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
|
||||
Reference in New Issue
Block a user