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:
Connor Peshek
2026-04-28 17:31:12 -05:00
committed by GitHub
parent aed634734b
commit a2ca19d718
34 changed files with 307 additions and 96 deletions
+2
View File
@@ -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
+25 -2
View File
@@ -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
),
),
]
+11 -1
View File
@@ -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
+4
View File
@@ -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,
)
+4
View File
@@ -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",
+6 -3
View File
@@ -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(
+6
View File
@@ -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/",
+1
View File
@@ -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,
},
+15
View File
@@ -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()
+3 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+8 -8
View File
@@ -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">