Compare commits

...

1 Commits

Author SHA1 Message Date
connor peshek
1d42baf212 providers/saml: make issuer generated url 2025-11-05 03:09:36 -06:00
9 changed files with 107 additions and 20 deletions

View File

@@ -39,6 +39,55 @@ def post_save_application(sender: type[Model], instance, created: bool, **_):
cache.delete_many(keys)
@receiver(post_save, sender=Application)
def post_save_application_saml_issuer(sender: type[Model], instance: Application, **_):
"""Generate SAML provider issuer when application is linked to a SAML provider"""
from authentik.lib.config import CONFIG
from authentik.providers.saml.models import SAMLProvider
LOGGER.debug("Application saved, checking for SAML issuer generation", app=instance.slug)
# Only process if application has a provider
if not instance.provider:
LOGGER.debug("Application has no provider, skipping", app=instance.slug)
return
# Check if provider is a SAML provider by trying to access the samlprovider attribute
try:
provider = instance.provider.samlprovider
except (AttributeError, SAMLProvider.DoesNotExist):
LOGGER.debug(
"Provider is not SAML, skipping",
app=instance.slug,
provider_type=type(instance.provider).__name__,
)
return
# Only set issuer if it's null
if provider.issuer:
LOGGER.debug(
"Issuer already set, skipping",
app=instance.slug,
provider=provider.name,
issuer=provider.issuer,
)
return
# Generate the issuer URL
scheme = "https" if not CONFIG.get_bool("authentik.debug", False) else "http"
domain = CONFIG.get("server.domain", "localhost:9000")
path = f"/application/saml/{instance.slug}/"
provider.issuer = f"{scheme}://{domain}{path}"
provider.save()
LOGGER.info(
"Generated issuer for SAML provider",
provider=provider.name,
application=instance.slug,
issuer=provider.issuer,
)
@receiver(user_logged_in)
def user_logged_in_session(sender, request: HttpRequest, user: User, **_):
"""Create an AuthenticatedSession from request"""

View File

@@ -211,7 +211,9 @@ class SAMLProviderSerializer(ProviderSerializer):
"url_slo_post",
"url_slo_redirect",
]
extra_kwargs = ProviderSerializer.Meta.extra_kwargs
extra_kwargs = ProviderSerializer.Meta.extra_kwargs | {
"issuer": {"required": False, "allow_blank": True, "allow_null": True}
}
class SAMLMetadataSerializer(PassiveSerializer):

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.7 on 2025-11-05 03:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_saml", "0020_samlprovider_logout_method_and_more"),
]
operations = [
migrations.AlterField(
model_name="samlprovider",
name="issuer",
field=models.TextField(
blank=True,
default=None,
help_text="Also known as EntityID. When left empty, defaults to: https://<your-domain>/application/saml/<application-slug>/",
null=True,
),
),
]

View File

@@ -69,7 +69,14 @@ class SAMLProvider(Provider):
"no audience restriction will be added."
),
)
issuer = models.TextField(help_text=_("Also known as EntityID"), default="authentik")
issuer = models.TextField(
blank=True,
null=True,
default=None,
help_text=_(
"Also known as EntityID. When left empty, defaults to: https://<your-domain>/application/saml/<application-slug>/"
),
)
sp_binding = models.TextField(
choices=SAMLBindings.choices,
default=SAMLBindings.REDIRECT,

View File

@@ -9409,10 +9409,12 @@
"description": "Value of the audience restriction field of the assertion. When left empty, no audience restriction will be added."
},
"issuer": {
"type": "string",
"minLength": 1,
"type": [
"string",
"null"
],
"title": "Issuer",
"description": "Also known as EntityID"
"description": "Also known as EntityID. When left empty, defaults to: https://<your-domain>/application/saml/<application-slug>/"
},
"assertion_valid_not_before": {
"type": "string",

View File

@@ -45476,8 +45476,8 @@ components:
left empty, no audience restriction will be added.
issuer:
type: string
minLength: 1
description: Also known as EntityID
nullable: true
description: 'Also known as EntityID. When left empty, defaults to: https://<your-domain>/application/saml/<application-slug>/'
assertion_valid_not_before:
type: string
minLength: 1
@@ -48663,7 +48663,8 @@ components:
left empty, no audience restriction will be added.
issuer:
type: string
description: Also known as EntityID
nullable: true
description: 'Also known as EntityID. When left empty, defaults to: https://<your-domain>/application/saml/<application-slug>/'
assertion_valid_not_before:
type: string
description: 'Assertion valid not before current time + this value (Format:
@@ -48859,8 +48860,8 @@ components:
left empty, no audience restriction will be added.
issuer:
type: string
minLength: 1
description: Also known as EntityID
nullable: true
description: 'Also known as EntityID. When left empty, defaults to: https://<your-domain>/application/saml/<application-slug>/'
assertion_valid_not_before:
type: string
minLength: 1

View File

@@ -33,7 +33,7 @@ function renderSAMLOverview(rawProvider: OneOfProvider) {
return renderSummary("SAML", provider.name, [
[msg("ACS URL"), provider.acsUrl],
[msg("Audience"), provider.audience || "-"],
[msg("Issuer"), provider.issuer],
[msg("Issuer"), provider.issuer || "-"],
]);
}

View File

@@ -169,14 +169,6 @@ export function renderForm({
required
.errorMessages=${errors.acsUrl}
></ak-text-input>
<ak-text-input
label=${msg("Issuer")}
name="issuer"
value="${provider.issuer || "authentik"}"
required
.errorMessages=${errors.issuer}
help=${msg("Also known as EntityID.")}
></ak-text-input>
<ak-radio-input
label=${msg("Service Provider Binding")}
name="spBinding"
@@ -411,6 +403,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="issuer"
value="${ifDefined(provider.issuer ?? undefined)}"
.errorMessages=${errors.issuer}
help=${msg(
"Input a value to set a custom EntityID/Issuer. defaults is https://<your-domain>/application/saml/<application-slug>/",
)}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Default NameID Policy")}
required

View File

@@ -390,7 +390,9 @@ export class SAMLProviderViewPage extends AKElement {
class="pf-c-form-control"
readonly
type="text"
value="${ifDefined(this.provider?.issuer)}"
value="${ifDefined(
this.provider?.issuer ?? undefined,
)}"
/>
</div>
<div class="pf-c-form__group">