diff --git a/authentik/providers/saml/api/providers.py b/authentik/providers/saml/api/providers.py
index 2465595f19..40ff7b0eb4 100644
--- a/authentik/providers/saml/api/providers.py
+++ b/authentik/providers/saml/api/providers.py
@@ -133,6 +133,17 @@ class SAMLProviderSerializer(ProviderSerializer):
except Provider.application.RelatedObjectDoesNotExist:
return "-"
+ def validate(self, attrs: dict):
+ if attrs.get("signing_kp"):
+ if not attrs.get("sign_assertion") and not attrs.get("sign_response"):
+ raise ValidationError(
+ _(
+ "With a signing keypair selected, at least one of 'Sign assertion' "
+ "and 'Sign Response' must be selected."
+ )
+ )
+ return super().validate(attrs)
+
class Meta:
model = SAMLProvider
fields = ProviderSerializer.Meta.fields + [
@@ -148,6 +159,9 @@ class SAMLProviderSerializer(ProviderSerializer):
"signature_algorithm",
"signing_kp",
"verification_kp",
+ "encryption_kp",
+ "sign_assertion",
+ "sign_response",
"sp_binding",
"default_relay_state",
"url_download_metadata",
diff --git a/authentik/providers/saml/migrations/0016_samlprovider_encryption_kp_and_more.py b/authentik/providers/saml/migrations/0016_samlprovider_encryption_kp_and_more.py
new file mode 100644
index 0000000000..e15242f54d
--- /dev/null
+++ b/authentik/providers/saml/migrations/0016_samlprovider_encryption_kp_and_more.py
@@ -0,0 +1,39 @@
+# Generated by Django 5.0.8 on 2024-08-15 14:52
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("authentik_crypto", "0004_alter_certificatekeypair_name"),
+ ("authentik_providers_saml", "0015_alter_samlpropertymapping_options"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="samlprovider",
+ name="encryption_kp",
+ field=models.ForeignKey(
+ blank=True,
+ default=None,
+ help_text="When selected, incoming assertions are encrypted by the IdP using the public key of the encryption keypair. The assertion is decrypted by the SP using the the private key.",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="authentik_crypto.certificatekeypair",
+ verbose_name="Encryption Keypair",
+ ),
+ ),
+ migrations.AddField(
+ model_name="samlprovider",
+ name="sign_assertion",
+ field=models.BooleanField(default=True),
+ ),
+ migrations.AddField(
+ model_name="samlprovider",
+ name="sign_response",
+ field=models.BooleanField(default=True),
+ ),
+ ]
diff --git a/authentik/providers/saml/models.py b/authentik/providers/saml/models.py
index 309d386f33..5cf410728a 100644
--- a/authentik/providers/saml/models.py
+++ b/authentik/providers/saml/models.py
@@ -144,11 +144,28 @@ class SAMLProvider(Provider):
on_delete=models.SET_NULL,
verbose_name=_("Signing Keypair"),
)
+ encryption_kp = models.ForeignKey(
+ CertificateKeyPair,
+ default=None,
+ null=True,
+ blank=True,
+ help_text=_(
+ "When selected, incoming assertions are encrypted by the IdP using the public "
+ "key of the encryption keypair. The assertion is decrypted by the SP using the "
+ "the private key."
+ ),
+ on_delete=models.SET_NULL,
+ verbose_name=_("Encryption Keypair"),
+ related_name="+",
+ )
default_relay_state = models.TextField(
default="", blank=True, help_text=_("Default relay_state value for IDP-initiated logins")
)
+ sign_assertion = models.BooleanField(default=True)
+ sign_response = models.BooleanField(default=True)
+
@property
def launch_url(self) -> str | None:
"""Use IDP-Initiated SAML flow as launch URL"""
diff --git a/authentik/providers/saml/processors/assertion.py b/authentik/providers/saml/processors/assertion.py
index 845a7b9395..6dc735a87f 100644
--- a/authentik/providers/saml/processors/assertion.py
+++ b/authentik/providers/saml/processors/assertion.py
@@ -18,7 +18,11 @@ from authentik.providers.saml.processors.authn_request_parser import AuthNReques
from authentik.providers.saml.utils import get_random_id
from authentik.providers.saml.utils.time import get_time_string
from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
-from authentik.sources.saml.exceptions import InvalidSignature, UnsupportedNameIDFormat
+from authentik.sources.saml.exceptions import (
+ InvalidEncryption,
+ InvalidSignature,
+ UnsupportedNameIDFormat,
+)
from authentik.sources.saml.processors.constants import (
DIGEST_ALGORITHM_TRANSLATION_MAP,
NS_MAP,
@@ -256,9 +260,17 @@ class AssertionProcessor:
assertion,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
- ns="ds", # type: ignore
+ ns=xmlsec.constants.DSigNs,
)
assertion.append(signature)
+ if self.provider.encryption_kp:
+ encryption = xmlsec.template.encrypted_data_create(
+ assertion,
+ xmlsec.constants.TransformAes128Cbc,
+ self._assertion_id,
+ ns=xmlsec.constants.DSigNs,
+ )
+ assertion.append(encryption)
assertion.append(self.get_assertion_subject())
assertion.append(self.get_assertion_conditions())
@@ -286,41 +298,86 @@ class AssertionProcessor:
response.append(self.get_assertion())
return response
+ def _sign(self, element: Element):
+ """Sign an XML element based on the providers' configured signing settings"""
+ digest_algorithm_transform = DIGEST_ALGORITHM_TRANSLATION_MAP.get(
+ self.provider.digest_algorithm, xmlsec.constants.TransformSha1
+ )
+ xmlsec.tree.add_ids(element, ["ID"])
+ signature_node = xmlsec.tree.find_node(element, xmlsec.constants.NodeSignature)
+ ref = xmlsec.template.add_reference(
+ signature_node,
+ digest_algorithm_transform,
+ uri="#" + self._assertion_id,
+ )
+ xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped)
+ xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N)
+ key_info = xmlsec.template.ensure_key_info(signature_node)
+ xmlsec.template.add_x509_data(key_info)
+
+ ctx = xmlsec.SignatureContext()
+
+ key = xmlsec.Key.from_memory(
+ self.provider.signing_kp.key_data,
+ xmlsec.constants.KeyDataFormatPem,
+ None,
+ )
+ key.load_cert_from_memory(
+ self.provider.signing_kp.certificate_data,
+ xmlsec.constants.KeyDataFormatCertPem,
+ )
+ ctx.key = key
+ try:
+ ctx.sign(signature_node)
+ except xmlsec.Error as exc:
+ raise InvalidSignature() from exc
+
+ def _encrypt(self, element: Element, parent: Element):
+ """Encrypt SAMLResponse EncryptedAssertion Element"""
+ manager = xmlsec.KeysManager()
+ key = xmlsec.Key.from_memory(
+ self.provider.encryption_kp.key_data,
+ xmlsec.constants.KeyDataFormatPem,
+ )
+ key.load_cert_from_memory(
+ self.provider.encryption_kp.certificate_data,
+ xmlsec.constants.KeyDataFormatCertPem,
+ )
+
+ manager.add_key(key)
+ encryption_context = xmlsec.EncryptionContext(manager)
+ encryption_context.key = xmlsec.Key.generate(
+ xmlsec.constants.KeyDataAes, 128, xmlsec.constants.KeyDataTypeSession
+ )
+
+ container = SubElement(parent, f"{{{NS_SAML_ASSERTION}}}EncryptedAssertion")
+ enc_data = xmlsec.template.encrypted_data_create(
+ container, xmlsec.Transform.AES128, type=xmlsec.EncryptionType.ELEMENT, ns="xenc"
+ )
+ xmlsec.template.encrypted_data_ensure_cipher_value(enc_data)
+ key_info = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns="ds")
+ enc_key = xmlsec.template.add_encrypted_key(key_info, xmlsec.Transform.RSA_OAEP)
+ xmlsec.template.encrypted_data_ensure_cipher_value(enc_key)
+
+ try:
+ enc_data = encryption_context.encrypt_xml(enc_data, element)
+ except xmlsec.Error as exc:
+ raise InvalidEncryption() from exc
+
+ parent.remove(enc_data)
+ container.append(enc_data)
+
def build_response(self) -> str:
"""Build string XML Response and sign if signing is enabled."""
root_response = self.get_response()
if self.provider.signing_kp:
- digest_algorithm_transform = DIGEST_ALGORITHM_TRANSLATION_MAP.get(
- self.provider.digest_algorithm, xmlsec.constants.TransformSha1
- )
+ if self.provider.sign_assertion:
+ assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
+ self._sign(assertion)
+ if self.provider.sign_response:
+ response = root_response.xpath("//samlp:Response", namespaces=NS_MAP)[0]
+ self._sign(response)
+ if self.provider.encryption_kp:
assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
- xmlsec.tree.add_ids(assertion, ["ID"])
- signature_node = xmlsec.tree.find_node(assertion, xmlsec.constants.NodeSignature)
- ref = xmlsec.template.add_reference(
- signature_node,
- digest_algorithm_transform,
- uri="#" + self._assertion_id,
- )
- xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped)
- xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N)
- key_info = xmlsec.template.ensure_key_info(signature_node)
- xmlsec.template.add_x509_data(key_info)
-
- ctx = xmlsec.SignatureContext()
-
- key = xmlsec.Key.from_memory(
- self.provider.signing_kp.key_data,
- xmlsec.constants.KeyDataFormatPem,
- None,
- )
- key.load_cert_from_memory(
- self.provider.signing_kp.certificate_data,
- xmlsec.constants.KeyDataFormatCertPem,
- )
- ctx.key = key
- try:
- ctx.sign(signature_node)
- except xmlsec.Error as exc:
- raise InvalidSignature() from exc
-
+ self._encrypt(assertion, root_response)
return etree.tostring(root_response).decode("utf-8") # nosec
diff --git a/authentik/providers/saml/processors/metadata.py b/authentik/providers/saml/processors/metadata.py
index e6a2dd0139..ed38882753 100644
--- a/authentik/providers/saml/processors/metadata.py
+++ b/authentik/providers/saml/processors/metadata.py
@@ -126,7 +126,7 @@ class MetadataProcessor:
entity_descriptor,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
- ns="ds", # type: ignore
+ ns=xmlsec.constants.DSigNs,
)
entity_descriptor.append(signature)
diff --git a/authentik/providers/saml/tests/test_api.py b/authentik/providers/saml/tests/test_api.py
index 8ccb0c29a2..e273b4b607 100644
--- a/authentik/providers/saml/tests/test_api.py
+++ b/authentik/providers/saml/tests/test_api.py
@@ -8,7 +8,7 @@ from rest_framework.test import APITestCase
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application
-from authentik.core.tests.utils import create_test_admin_user, create_test_flow
+from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.flows.models import FlowDesignation
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import load_fixture
@@ -29,12 +29,52 @@ class TestSAMLProviderAPI(APITestCase):
name=generate_id(),
authorization_flow=create_test_flow(),
)
+ response = self.client.get(
+ reverse("authentik_api:samlprovider-detail", kwargs={"pk": provider.pk}),
+ )
+ self.assertEqual(200, response.status_code)
Application.objects.create(name=generate_id(), provider=provider, slug=generate_id())
response = self.client.get(
reverse("authentik_api:samlprovider-detail", kwargs={"pk": provider.pk}),
)
self.assertEqual(200, response.status_code)
+ def test_create_validate_signing_kp(self):
+ """Test create"""
+ cert = create_test_cert()
+ response = self.client.post(
+ reverse("authentik_api:samlprovider-list"),
+ data={
+ "name": generate_id(),
+ "authorization_flow": create_test_flow().pk,
+ "acs_url": "http://localhost",
+ "signing_kp": cert.pk,
+ },
+ )
+ self.assertEqual(400, response.status_code)
+ self.assertJSONEqual(
+ response.content,
+ {
+ "non_field_errors": [
+ (
+ "With a signing keypair selected, at least one "
+ "of 'Sign assertion' and 'Sign Response' must be selected."
+ )
+ ]
+ },
+ )
+ response = self.client.post(
+ reverse("authentik_api:samlprovider-list"),
+ data={
+ "name": generate_id(),
+ "authorization_flow": create_test_flow().pk,
+ "acs_url": "http://localhost",
+ "signing_kp": cert.pk,
+ "sign_assertion": True,
+ },
+ )
+ self.assertEqual(201, response.status_code)
+
def test_metadata(self):
"""Test metadata export (normal)"""
self.client.logout()
diff --git a/authentik/providers/saml/tests/test_auth_n_request.py b/authentik/providers/saml/tests/test_auth_n_request.py
index 88a563ef87..bb1a5b2754 100644
--- a/authentik/providers/saml/tests/test_auth_n_request.py
+++ b/authentik/providers/saml/tests/test_auth_n_request.py
@@ -78,12 +78,12 @@ class TestAuthNRequest(TestCase):
@apply_blueprint("system/providers-saml.yaml")
def setUp(self):
- cert = create_test_cert()
+ self.cert = create_test_cert()
self.provider: SAMLProvider = SAMLProvider.objects.create(
authorization_flow=create_test_flow(),
acs_url="http://testserver/source/saml/provider/acs/",
- signing_kp=cert,
- verification_kp=cert,
+ signing_kp=self.cert,
+ verification_kp=self.cert,
)
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
self.provider.save()
@@ -91,8 +91,8 @@ class TestAuthNRequest(TestCase):
slug="provider",
issuer="authentik",
pre_authentication_flow=create_test_flow(),
- signing_kp=cert,
- verification_kp=cert,
+ signing_kp=self.cert,
+ verification_kp=self.cert,
)
def test_signed_valid(self):
@@ -112,7 +112,34 @@ class TestAuthNRequest(TestCase):
self.assertEqual(parsed_request.id, request_proc.request_id)
self.assertEqual(parsed_request.relay_state, "test_state")
- def test_request_full_signed(self):
+ def test_request_encrypt(self):
+ """Test full SAML Request/Response flow, fully encrypted"""
+ self.provider.encryption_kp = self.cert
+ self.provider.save()
+ self.source.encryption_kp = self.cert
+ self.source.save()
+ http_request = get_request("/")
+
+ # First create an AuthNRequest
+ request_proc = RequestProcessor(self.source, http_request, "test_state")
+ request = request_proc.build_auth_n()
+
+ # To get an assertion we need a parsed request (parsed by provider)
+ parsed_request = AuthNRequestParser(self.provider).parse(
+ b64encode(request.encode()).decode(), "test_state"
+ )
+ # Now create a response and convert it to string (provider)
+ response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
+ response = response_proc.build_response()
+
+ # Now parse the response (source)
+ http_request.POST = QueryDict(mutable=True)
+ http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
+
+ response_parser = ResponseProcessor(self.source, http_request)
+ response_parser.parse()
+
+ def test_request_signed(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = get_request("/")
@@ -135,6 +162,32 @@ class TestAuthNRequest(TestCase):
response_parser = ResponseProcessor(self.source, http_request)
response_parser.parse()
+ def test_request_signed_both(self):
+ """Test full SAML Request/Response flow, fully signed"""
+ self.provider.sign_assertion = True
+ self.provider.sign_response = True
+ self.provider.save()
+ http_request = get_request("/")
+
+ # First create an AuthNRequest
+ request_proc = RequestProcessor(self.source, http_request, "test_state")
+ request = request_proc.build_auth_n()
+
+ # To get an assertion we need a parsed request (parsed by provider)
+ parsed_request = AuthNRequestParser(self.provider).parse(
+ b64encode(request.encode()).decode(), "test_state"
+ )
+ # Now create a response and convert it to string (provider)
+ response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
+ response = response_proc.build_response()
+
+ # Now parse the response (source)
+ http_request.POST = QueryDict(mutable=True)
+ http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
+
+ response_parser = ResponseProcessor(self.source, http_request)
+ response_parser.parse()
+
def test_request_id_invalid(self):
"""Test generated AuthNRequest with invalid request ID"""
http_request = get_request("/")
diff --git a/authentik/root/settings.py b/authentik/root/settings.py
index 0639998098..8b3c7666b3 100644
--- a/authentik/root/settings.py
+++ b/authentik/root/settings.py
@@ -9,6 +9,7 @@ import orjson
from celery.schedules import crontab
from django.conf import ImproperlyConfigured
from sentry_sdk import set_tag
+from xmlsec import enable_debug_trace
from authentik import __version__
from authentik.lib.config import CONFIG, redis_url
@@ -520,6 +521,7 @@ if DEBUG:
"rest_framework.renderers.BrowsableAPIRenderer"
)
SHARED_APPS.insert(SHARED_APPS.index("django.contrib.staticfiles"), "daphne")
+ enable_debug_trace(True)
TENANT_APPS.append("authentik.core")
diff --git a/authentik/sources/saml/processors/constants.py b/authentik/sources/saml/processors/constants.py
index df126c6a44..1f037d28a3 100644
--- a/authentik/sources/saml/processors/constants.py
+++ b/authentik/sources/saml/processors/constants.py
@@ -6,12 +6,14 @@ NS_SAML_PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
NS_SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
NS_SAML_METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
NS_SIGNATURE = "http://www.w3.org/2000/09/xmldsig#"
+NS_ENC = "http://www.w3.org/2001/04/xmlenc#"
NS_MAP = {
"samlp": NS_SAML_PROTOCOL,
"saml": NS_SAML_ASSERTION,
"ds": NS_SIGNATURE,
"md": NS_SAML_METADATA,
+ "xenc": NS_ENC,
}
SAML_NAME_ID_FORMAT_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
diff --git a/authentik/sources/saml/processors/request.py b/authentik/sources/saml/processors/request.py
index ff36812a1f..f51d7f0909 100644
--- a/authentik/sources/saml/processors/request.py
+++ b/authentik/sources/saml/processors/request.py
@@ -76,7 +76,7 @@ class RequestProcessor:
auth_n_request,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
- ns="ds", # type: ignore
+ ns=xmlsec.constants.DSigNs,
)
auth_n_request.append(signature)
diff --git a/blueprints/schema.json b/blueprints/schema.json
index f63ce69963..8806577773 100644
--- a/blueprints/schema.json
+++ b/blueprints/schema.json
@@ -5189,6 +5189,7 @@
"permission": {
"type": "string",
"enum": [
+ "search_full_directory",
"add_ldapprovider",
"change_ldapprovider",
"delete_ldapprovider",
@@ -5773,6 +5774,20 @@
"title": "Verification Certificate",
"description": "When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default."
},
+ "encryption_kp": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Encryption Keypair",
+ "description": "When selected, incoming assertions are encrypted by the IdP using the public key of the encryption keypair. The assertion is decrypted by the SP using the the private key."
+ },
+ "sign_assertion": {
+ "type": "boolean",
+ "title": "Sign assertion"
+ },
+ "sign_response": {
+ "type": "boolean",
+ "title": "Sign response"
+ },
"sp_binding": {
"type": "string",
"enum": [
@@ -6212,6 +6227,7 @@
"authentik_providers_ldap.add_ldapprovider",
"authentik_providers_ldap.change_ldapprovider",
"authentik_providers_ldap.delete_ldapprovider",
+ "authentik_providers_ldap.search_full_directory",
"authentik_providers_ldap.view_ldapprovider",
"authentik_providers_microsoft_entra.add_microsoftentraprovider",
"authentik_providers_microsoft_entra.change_microsoftentraprovider",
@@ -11867,6 +11883,7 @@
"authentik_providers_ldap.add_ldapprovider",
"authentik_providers_ldap.change_ldapprovider",
"authentik_providers_ldap.delete_ldapprovider",
+ "authentik_providers_ldap.search_full_directory",
"authentik_providers_ldap.view_ldapprovider",
"authentik_providers_microsoft_entra.add_microsoftentraprovider",
"authentik_providers_microsoft_entra.change_microsoftentraprovider",
diff --git a/schema.yml b/schema.yml
index bf42d1cc2b..e8f9838aaf 100644
--- a/schema.yml
+++ b/schema.yml
@@ -20664,6 +20664,11 @@ paths:
- http://www.w3.org/2001/04/xmldsig-more#sha384
- http://www.w3.org/2001/04/xmlenc#sha256
- http://www.w3.org/2001/04/xmlenc#sha512
+ - in: query
+ name: encryption_kp
+ schema:
+ type: string
+ format: uuid
- in: query
name: is_backchannel
schema:
@@ -20718,6 +20723,14 @@ paths:
name: session_valid_not_on_or_after
schema:
type: string
+ - in: query
+ name: sign_assertion
+ schema:
+ type: boolean
+ - in: query
+ name: sign_response
+ schema:
+ type: boolean
- in: query
name: signature_algorithm
schema:
@@ -46866,6 +46879,18 @@ components:
title: Verification Certificate
description: When selected, incoming assertion's Signatures will be validated
against this certificate. To allow unsigned Requests, leave on default.
+ encryption_kp:
+ type: string
+ format: uuid
+ nullable: true
+ title: Encryption Keypair
+ description: When selected, incoming assertions are encrypted by the IdP
+ using the public key of the encryption keypair. The assertion is decrypted
+ by the SP using the the private key.
+ sign_assertion:
+ type: boolean
+ sign_response:
+ type: boolean
sp_binding:
allOf:
- $ref: '#/components/schemas/SpBindingEnum'
@@ -49581,6 +49606,18 @@ components:
title: Verification Certificate
description: When selected, incoming assertion's Signatures will be validated
against this certificate. To allow unsigned Requests, leave on default.
+ encryption_kp:
+ type: string
+ format: uuid
+ nullable: true
+ title: Encryption Keypair
+ description: When selected, incoming assertions are encrypted by the IdP
+ using the public key of the encryption keypair. The assertion is decrypted
+ by the SP using the the private key.
+ sign_assertion:
+ type: boolean
+ sign_response:
+ type: boolean
sp_binding:
allOf:
- $ref: '#/components/schemas/SpBindingEnum'
@@ -49725,6 +49762,18 @@ components:
title: Verification Certificate
description: When selected, incoming assertion's Signatures will be validated
against this certificate. To allow unsigned Requests, leave on default.
+ encryption_kp:
+ type: string
+ format: uuid
+ nullable: true
+ title: Encryption Keypair
+ description: When selected, incoming assertions are encrypted by the IdP
+ using the public key of the encryption keypair. The assertion is decrypted
+ by the SP using the the private key.
+ sign_assertion:
+ type: boolean
+ sign_response:
+ type: boolean
sp_binding:
allOf:
- $ref: '#/components/schemas/SpBindingEnum'
diff --git a/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts
index c5700af0d7..f2947a0381 100644
--- a/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts
+++ b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts
@@ -1,8 +1,10 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
+import AkCryptoCertificateSearch from "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
+import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-multi-select";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input";
@@ -13,7 +15,7 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement, state } from "@lit/reactive-element/decorators.js";
-import { html } from "lit";
+import { html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import {
@@ -36,6 +38,9 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
@state()
propertyMappings?: PaginatedSAMLPropertyMappingList;
+ @state()
+ hasSigningKp = false;
+
constructor() {
super();
new PropertymappingsApi(DEFAULT_CONFIG)
@@ -167,6 +172,11 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
>
${msg( @@ -174,6 +184,52 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane )}
+ ${this.hasSigningKp + ? html`+ ${msg( + "When enabled, the assertion element of the SAML response will be signed.", + )} +
++ ${msg( + "When enabled, the assertion element of the SAML response will be signed.", + )} +
++ ${msg( + "When selected, encrypted assertions will be decrypted using this keypair.", + )} +
+
${msg(
@@ -191,6 +203,52 @@ export class SAMLProviderFormPage extends BaseProviderForm
+ ${msg( + "When enabled, the assertion element of the SAML response will be signed.", + )} +
++ ${msg( + "When enabled, the assertion element of the SAML response will be signed.", + )} +
++ ${msg( + "When selected, assertions will be encrypted using this keypair.", + )} +
+