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 > { + const target = ev.target as AkCryptoCertificateSearch; + if (!target) return; + this.hasSigningKp = !!target.selectedKeypair; + }} >

${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.", + )} +

+
` + : nothing} + + +

+ ${msg( + "When selected, encrypted assertions will be decrypted using this keypair.", + )} +

+
+ { - loadInstance(pk: number): Promise { - return new ProvidersApi(DEFAULT_CONFIG).providersSamlRetrieve({ + @state() + hasSigningKp = false; + + async loadInstance(pk: number): Promise { + const provider = await new ProvidersApi(DEFAULT_CONFIG).providersSamlRetrieve({ id: pk, }); + this.hasSigningKp = !!provider.signingKp; + return provider; } async send(data: SAMLProvider): Promise { @@ -184,6 +191,11 @@ export class SAMLProviderFormPage extends BaseProviderForm { > { + const target = ev.target as AkCryptoCertificateSearch; + if (!target) return; + this.hasSigningKp = !!target.selectedKeypair; + }} >

${msg( @@ -191,6 +203,52 @@ export class SAMLProviderFormPage extends BaseProviderForm { )}

+ ${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.", + )} +

+
` + : nothing} { )}

+ + +

+ ${msg( + "When selected, assertions will be encrypted using this keypair.", + )} +

+