providers/SAML: encryption support (#10934)
* providers/saml: add option to sign assertion and or response Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add encryption Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add form option Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add tests for API Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -133,6 +133,17 @@ class SAMLProviderSerializer(ProviderSerializer):
|
|||||||
except Provider.application.RelatedObjectDoesNotExist:
|
except Provider.application.RelatedObjectDoesNotExist:
|
||||||
return "-"
|
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:
|
class Meta:
|
||||||
model = SAMLProvider
|
model = SAMLProvider
|
||||||
fields = ProviderSerializer.Meta.fields + [
|
fields = ProviderSerializer.Meta.fields + [
|
||||||
@ -148,6 +159,9 @@ class SAMLProviderSerializer(ProviderSerializer):
|
|||||||
"signature_algorithm",
|
"signature_algorithm",
|
||||||
"signing_kp",
|
"signing_kp",
|
||||||
"verification_kp",
|
"verification_kp",
|
||||||
|
"encryption_kp",
|
||||||
|
"sign_assertion",
|
||||||
|
"sign_response",
|
||||||
"sp_binding",
|
"sp_binding",
|
||||||
"default_relay_state",
|
"default_relay_state",
|
||||||
"url_download_metadata",
|
"url_download_metadata",
|
||||||
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -144,11 +144,28 @@ class SAMLProvider(Provider):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
verbose_name=_("Signing Keypair"),
|
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_relay_state = models.TextField(
|
||||||
default="", blank=True, help_text=_("Default relay_state value for IDP-initiated logins")
|
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
|
@property
|
||||||
def launch_url(self) -> str | None:
|
def launch_url(self) -> str | None:
|
||||||
"""Use IDP-Initiated SAML flow as launch URL"""
|
"""Use IDP-Initiated SAML flow as launch URL"""
|
||||||
|
@ -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 import get_random_id
|
||||||
from authentik.providers.saml.utils.time import get_time_string
|
from authentik.providers.saml.utils.time import get_time_string
|
||||||
from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
|
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 (
|
from authentik.sources.saml.processors.constants import (
|
||||||
DIGEST_ALGORITHM_TRANSLATION_MAP,
|
DIGEST_ALGORITHM_TRANSLATION_MAP,
|
||||||
NS_MAP,
|
NS_MAP,
|
||||||
@ -256,9 +260,17 @@ class AssertionProcessor:
|
|||||||
assertion,
|
assertion,
|
||||||
xmlsec.constants.TransformExclC14N,
|
xmlsec.constants.TransformExclC14N,
|
||||||
sign_algorithm_transform,
|
sign_algorithm_transform,
|
||||||
ns="ds", # type: ignore
|
ns=xmlsec.constants.DSigNs,
|
||||||
)
|
)
|
||||||
assertion.append(signature)
|
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_subject())
|
||||||
assertion.append(self.get_assertion_conditions())
|
assertion.append(self.get_assertion_conditions())
|
||||||
@ -286,41 +298,86 @@ class AssertionProcessor:
|
|||||||
response.append(self.get_assertion())
|
response.append(self.get_assertion())
|
||||||
return response
|
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:
|
def build_response(self) -> str:
|
||||||
"""Build string XML Response and sign if signing is enabled."""
|
"""Build string XML Response and sign if signing is enabled."""
|
||||||
root_response = self.get_response()
|
root_response = self.get_response()
|
||||||
if self.provider.signing_kp:
|
if self.provider.signing_kp:
|
||||||
digest_algorithm_transform = DIGEST_ALGORITHM_TRANSLATION_MAP.get(
|
if self.provider.sign_assertion:
|
||||||
self.provider.digest_algorithm, xmlsec.constants.TransformSha1
|
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]
|
assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
|
||||||
xmlsec.tree.add_ids(assertion, ["ID"])
|
self._encrypt(assertion, root_response)
|
||||||
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
|
|
||||||
|
|
||||||
return etree.tostring(root_response).decode("utf-8") # nosec
|
return etree.tostring(root_response).decode("utf-8") # nosec
|
||||||
|
@ -126,7 +126,7 @@ class MetadataProcessor:
|
|||||||
entity_descriptor,
|
entity_descriptor,
|
||||||
xmlsec.constants.TransformExclC14N,
|
xmlsec.constants.TransformExclC14N,
|
||||||
sign_algorithm_transform,
|
sign_algorithm_transform,
|
||||||
ns="ds", # type: ignore
|
ns=xmlsec.constants.DSigNs,
|
||||||
)
|
)
|
||||||
entity_descriptor.append(signature)
|
entity_descriptor.append(signature)
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ from rest_framework.test import APITestCase
|
|||||||
|
|
||||||
from authentik.blueprints.tests import apply_blueprint
|
from authentik.blueprints.tests import apply_blueprint
|
||||||
from authentik.core.models import Application
|
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.flows.models import FlowDesignation
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.lib.tests.utils import load_fixture
|
from authentik.lib.tests.utils import load_fixture
|
||||||
@ -29,12 +29,52 @@ class TestSAMLProviderAPI(APITestCase):
|
|||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
authorization_flow=create_test_flow(),
|
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())
|
Application.objects.create(name=generate_id(), provider=provider, slug=generate_id())
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("authentik_api:samlprovider-detail", kwargs={"pk": provider.pk}),
|
reverse("authentik_api:samlprovider-detail", kwargs={"pk": provider.pk}),
|
||||||
)
|
)
|
||||||
self.assertEqual(200, response.status_code)
|
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):
|
def test_metadata(self):
|
||||||
"""Test metadata export (normal)"""
|
"""Test metadata export (normal)"""
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
@ -78,12 +78,12 @@ class TestAuthNRequest(TestCase):
|
|||||||
|
|
||||||
@apply_blueprint("system/providers-saml.yaml")
|
@apply_blueprint("system/providers-saml.yaml")
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
cert = create_test_cert()
|
self.cert = create_test_cert()
|
||||||
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
||||||
authorization_flow=create_test_flow(),
|
authorization_flow=create_test_flow(),
|
||||||
acs_url="http://testserver/source/saml/provider/acs/",
|
acs_url="http://testserver/source/saml/provider/acs/",
|
||||||
signing_kp=cert,
|
signing_kp=self.cert,
|
||||||
verification_kp=cert,
|
verification_kp=self.cert,
|
||||||
)
|
)
|
||||||
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||||
self.provider.save()
|
self.provider.save()
|
||||||
@ -91,8 +91,8 @@ class TestAuthNRequest(TestCase):
|
|||||||
slug="provider",
|
slug="provider",
|
||||||
issuer="authentik",
|
issuer="authentik",
|
||||||
pre_authentication_flow=create_test_flow(),
|
pre_authentication_flow=create_test_flow(),
|
||||||
signing_kp=cert,
|
signing_kp=self.cert,
|
||||||
verification_kp=cert,
|
verification_kp=self.cert,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_signed_valid(self):
|
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.id, request_proc.request_id)
|
||||||
self.assertEqual(parsed_request.relay_state, "test_state")
|
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"""
|
"""Test full SAML Request/Response flow, fully signed"""
|
||||||
http_request = get_request("/")
|
http_request = get_request("/")
|
||||||
|
|
||||||
@ -135,6 +162,32 @@ class TestAuthNRequest(TestCase):
|
|||||||
response_parser = ResponseProcessor(self.source, http_request)
|
response_parser = ResponseProcessor(self.source, http_request)
|
||||||
response_parser.parse()
|
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):
|
def test_request_id_invalid(self):
|
||||||
"""Test generated AuthNRequest with invalid request ID"""
|
"""Test generated AuthNRequest with invalid request ID"""
|
||||||
http_request = get_request("/")
|
http_request = get_request("/")
|
||||||
|
@ -9,6 +9,7 @@ import orjson
|
|||||||
from celery.schedules import crontab
|
from celery.schedules import crontab
|
||||||
from django.conf import ImproperlyConfigured
|
from django.conf import ImproperlyConfigured
|
||||||
from sentry_sdk import set_tag
|
from sentry_sdk import set_tag
|
||||||
|
from xmlsec import enable_debug_trace
|
||||||
|
|
||||||
from authentik import __version__
|
from authentik import __version__
|
||||||
from authentik.lib.config import CONFIG, redis_url
|
from authentik.lib.config import CONFIG, redis_url
|
||||||
@ -520,6 +521,7 @@ if DEBUG:
|
|||||||
"rest_framework.renderers.BrowsableAPIRenderer"
|
"rest_framework.renderers.BrowsableAPIRenderer"
|
||||||
)
|
)
|
||||||
SHARED_APPS.insert(SHARED_APPS.index("django.contrib.staticfiles"), "daphne")
|
SHARED_APPS.insert(SHARED_APPS.index("django.contrib.staticfiles"), "daphne")
|
||||||
|
enable_debug_trace(True)
|
||||||
|
|
||||||
TENANT_APPS.append("authentik.core")
|
TENANT_APPS.append("authentik.core")
|
||||||
|
|
||||||
|
@ -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_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
||||||
NS_SAML_METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
NS_SAML_METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
NS_SIGNATURE = "http://www.w3.org/2000/09/xmldsig#"
|
NS_SIGNATURE = "http://www.w3.org/2000/09/xmldsig#"
|
||||||
|
NS_ENC = "http://www.w3.org/2001/04/xmlenc#"
|
||||||
|
|
||||||
NS_MAP = {
|
NS_MAP = {
|
||||||
"samlp": NS_SAML_PROTOCOL,
|
"samlp": NS_SAML_PROTOCOL,
|
||||||
"saml": NS_SAML_ASSERTION,
|
"saml": NS_SAML_ASSERTION,
|
||||||
"ds": NS_SIGNATURE,
|
"ds": NS_SIGNATURE,
|
||||||
"md": NS_SAML_METADATA,
|
"md": NS_SAML_METADATA,
|
||||||
|
"xenc": NS_ENC,
|
||||||
}
|
}
|
||||||
|
|
||||||
SAML_NAME_ID_FORMAT_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
SAML_NAME_ID_FORMAT_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
||||||
|
@ -76,7 +76,7 @@ class RequestProcessor:
|
|||||||
auth_n_request,
|
auth_n_request,
|
||||||
xmlsec.constants.TransformExclC14N,
|
xmlsec.constants.TransformExclC14N,
|
||||||
sign_algorithm_transform,
|
sign_algorithm_transform,
|
||||||
ns="ds", # type: ignore
|
ns=xmlsec.constants.DSigNs,
|
||||||
)
|
)
|
||||||
auth_n_request.append(signature)
|
auth_n_request.append(signature)
|
||||||
|
|
||||||
|
@ -5189,6 +5189,7 @@
|
|||||||
"permission": {
|
"permission": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
"search_full_directory",
|
||||||
"add_ldapprovider",
|
"add_ldapprovider",
|
||||||
"change_ldapprovider",
|
"change_ldapprovider",
|
||||||
"delete_ldapprovider",
|
"delete_ldapprovider",
|
||||||
@ -5773,6 +5774,20 @@
|
|||||||
"title": "Verification Certificate",
|
"title": "Verification Certificate",
|
||||||
"description": "When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default."
|
"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": {
|
"sp_binding": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -6212,6 +6227,7 @@
|
|||||||
"authentik_providers_ldap.add_ldapprovider",
|
"authentik_providers_ldap.add_ldapprovider",
|
||||||
"authentik_providers_ldap.change_ldapprovider",
|
"authentik_providers_ldap.change_ldapprovider",
|
||||||
"authentik_providers_ldap.delete_ldapprovider",
|
"authentik_providers_ldap.delete_ldapprovider",
|
||||||
|
"authentik_providers_ldap.search_full_directory",
|
||||||
"authentik_providers_ldap.view_ldapprovider",
|
"authentik_providers_ldap.view_ldapprovider",
|
||||||
"authentik_providers_microsoft_entra.add_microsoftentraprovider",
|
"authentik_providers_microsoft_entra.add_microsoftentraprovider",
|
||||||
"authentik_providers_microsoft_entra.change_microsoftentraprovider",
|
"authentik_providers_microsoft_entra.change_microsoftentraprovider",
|
||||||
@ -11867,6 +11883,7 @@
|
|||||||
"authentik_providers_ldap.add_ldapprovider",
|
"authentik_providers_ldap.add_ldapprovider",
|
||||||
"authentik_providers_ldap.change_ldapprovider",
|
"authentik_providers_ldap.change_ldapprovider",
|
||||||
"authentik_providers_ldap.delete_ldapprovider",
|
"authentik_providers_ldap.delete_ldapprovider",
|
||||||
|
"authentik_providers_ldap.search_full_directory",
|
||||||
"authentik_providers_ldap.view_ldapprovider",
|
"authentik_providers_ldap.view_ldapprovider",
|
||||||
"authentik_providers_microsoft_entra.add_microsoftentraprovider",
|
"authentik_providers_microsoft_entra.add_microsoftentraprovider",
|
||||||
"authentik_providers_microsoft_entra.change_microsoftentraprovider",
|
"authentik_providers_microsoft_entra.change_microsoftentraprovider",
|
||||||
|
49
schema.yml
49
schema.yml
@ -20664,6 +20664,11 @@ paths:
|
|||||||
- http://www.w3.org/2001/04/xmldsig-more#sha384
|
- http://www.w3.org/2001/04/xmldsig-more#sha384
|
||||||
- http://www.w3.org/2001/04/xmlenc#sha256
|
- http://www.w3.org/2001/04/xmlenc#sha256
|
||||||
- http://www.w3.org/2001/04/xmlenc#sha512
|
- http://www.w3.org/2001/04/xmlenc#sha512
|
||||||
|
- in: query
|
||||||
|
name: encryption_kp
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
- in: query
|
- in: query
|
||||||
name: is_backchannel
|
name: is_backchannel
|
||||||
schema:
|
schema:
|
||||||
@ -20718,6 +20723,14 @@ paths:
|
|||||||
name: session_valid_not_on_or_after
|
name: session_valid_not_on_or_after
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: sign_assertion
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
- in: query
|
||||||
|
name: sign_response
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
- in: query
|
- in: query
|
||||||
name: signature_algorithm
|
name: signature_algorithm
|
||||||
schema:
|
schema:
|
||||||
@ -46866,6 +46879,18 @@ components:
|
|||||||
title: Verification Certificate
|
title: Verification Certificate
|
||||||
description: When selected, incoming assertion's Signatures will be validated
|
description: When selected, incoming assertion's Signatures will be validated
|
||||||
against this certificate. To allow unsigned Requests, leave on default.
|
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:
|
sp_binding:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/SpBindingEnum'
|
- $ref: '#/components/schemas/SpBindingEnum'
|
||||||
@ -49581,6 +49606,18 @@ components:
|
|||||||
title: Verification Certificate
|
title: Verification Certificate
|
||||||
description: When selected, incoming assertion's Signatures will be validated
|
description: When selected, incoming assertion's Signatures will be validated
|
||||||
against this certificate. To allow unsigned Requests, leave on default.
|
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:
|
sp_binding:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/SpBindingEnum'
|
- $ref: '#/components/schemas/SpBindingEnum'
|
||||||
@ -49725,6 +49762,18 @@ components:
|
|||||||
title: Verification Certificate
|
title: Verification Certificate
|
||||||
description: When selected, incoming assertion's Signatures will be validated
|
description: When selected, incoming assertion's Signatures will be validated
|
||||||
against this certificate. To allow unsigned Requests, leave on default.
|
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:
|
sp_binding:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/SpBindingEnum'
|
- $ref: '#/components/schemas/SpBindingEnum'
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
||||||
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 "@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 "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/components/ak-multi-select";
|
import "@goauthentik/components/ak-multi-select";
|
||||||
import "@goauthentik/components/ak-number-input";
|
import "@goauthentik/components/ak-number-input";
|
||||||
import "@goauthentik/components/ak-radio-input";
|
import "@goauthentik/components/ak-radio-input";
|
||||||
@ -13,7 +15,7 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
|
|||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
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 { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -36,6 +38,9 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
|
|||||||
@state()
|
@state()
|
||||||
propertyMappings?: PaginatedSAMLPropertyMappingList;
|
propertyMappings?: PaginatedSAMLPropertyMappingList;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
hasSigningKp = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
new PropertymappingsApi(DEFAULT_CONFIG)
|
||||||
@ -167,6 +172,11 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
|
|||||||
>
|
>
|
||||||
<ak-crypto-certificate-search
|
<ak-crypto-certificate-search
|
||||||
certificate=${ifDefined(provider?.signingKp ?? undefined)}
|
certificate=${ifDefined(provider?.signingKp ?? undefined)}
|
||||||
|
@input=${(ev: InputEvent) => {
|
||||||
|
const target = ev.target as AkCryptoCertificateSearch;
|
||||||
|
if (!target) return;
|
||||||
|
this.hasSigningKp = !!target.selectedKeypair;
|
||||||
|
}}
|
||||||
></ak-crypto-certificate-search>
|
></ak-crypto-certificate-search>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${msg(
|
${msg(
|
||||||
@ -174,6 +184,52 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
${this.hasSigningKp
|
||||||
|
? html` <ak-form-element-horizontal name="signAssertion">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${first(provider?.signAssertion, true)}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label"
|
||||||
|
>${msg("Sign assertions")}</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When enabled, the assertion element of the SAML response will be signed.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal name="signResponse">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${first(provider?.signResponse, false)}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label"
|
||||||
|
>${msg("Sign responses")}</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When enabled, the assertion element of the SAML response will be signed.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Verification Certificate")}
|
label=${msg("Verification Certificate")}
|
||||||
@ -190,6 +246,20 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
|
|||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Encryption Certificate")}
|
||||||
|
name="encryptionKp"
|
||||||
|
>
|
||||||
|
<ak-crypto-certificate-search
|
||||||
|
certificate=${ifDefined(provider?.encryptionKp ?? undefined)}
|
||||||
|
></ak-crypto-certificate-search>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When selected, encrypted assertions will be decrypted using this keypair.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
<ak-multi-select
|
<ak-multi-select
|
||||||
label=${msg("Property Mappings")}
|
label=${msg("Property Mappings")}
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
|
@ -3,9 +3,11 @@ import {
|
|||||||
signatureAlgorithmOptions,
|
signatureAlgorithmOptions,
|
||||||
} from "@goauthentik/admin/applications/wizard/methods/saml/SamlProviderOptions";
|
} from "@goauthentik/admin/applications/wizard/methods/saml/SamlProviderOptions";
|
||||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
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-flow-search";
|
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||||
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
|
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||||
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
|
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
@ -15,8 +17,8 @@ import "@goauthentik/elements/forms/SearchSelect";
|
|||||||
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html, nothing } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -54,10 +56,15 @@ export function makeSAMLPropertyMappingsSelector(instanceMappings?: string[]) {
|
|||||||
|
|
||||||
@customElement("ak-provider-saml-form")
|
@customElement("ak-provider-saml-form")
|
||||||
export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
|
export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
|
||||||
loadInstance(pk: number): Promise<SAMLProvider> {
|
@state()
|
||||||
return new ProvidersApi(DEFAULT_CONFIG).providersSamlRetrieve({
|
hasSigningKp = false;
|
||||||
|
|
||||||
|
async loadInstance(pk: number): Promise<SAMLProvider> {
|
||||||
|
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersSamlRetrieve({
|
||||||
id: pk,
|
id: pk,
|
||||||
});
|
});
|
||||||
|
this.hasSigningKp = !!provider.signingKp;
|
||||||
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(data: SAMLProvider): Promise<SAMLProvider> {
|
async send(data: SAMLProvider): Promise<SAMLProvider> {
|
||||||
@ -184,6 +191,11 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
|
|||||||
>
|
>
|
||||||
<ak-crypto-certificate-search
|
<ak-crypto-certificate-search
|
||||||
.certificate=${this.instance?.signingKp}
|
.certificate=${this.instance?.signingKp}
|
||||||
|
@input=${(ev: InputEvent) => {
|
||||||
|
const target = ev.target as AkCryptoCertificateSearch;
|
||||||
|
if (!target) return;
|
||||||
|
this.hasSigningKp = !!target.selectedKeypair;
|
||||||
|
}}
|
||||||
></ak-crypto-certificate-search>
|
></ak-crypto-certificate-search>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${msg(
|
${msg(
|
||||||
@ -191,6 +203,52 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
${this.hasSigningKp
|
||||||
|
? html` <ak-form-element-horizontal name="signAssertion">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${first(this.instance?.signAssertion, true)}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label"
|
||||||
|
>${msg("Sign assertions")}</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When enabled, the assertion element of the SAML response will be signed.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal name="signResponse">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${first(this.instance?.signResponse, false)}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label"
|
||||||
|
>${msg("Sign responses")}</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When enabled, the assertion element of the SAML response will be signed.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>`
|
||||||
|
: nothing}
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Verification Certificate")}
|
label=${msg("Verification Certificate")}
|
||||||
name="verificationKp"
|
name="verificationKp"
|
||||||
@ -205,6 +263,19 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Encryption Certificate")}
|
||||||
|
name="encryptionKp"
|
||||||
|
>
|
||||||
|
<ak-crypto-certificate-search
|
||||||
|
.certificate=${this.instance?.encryptionKp}
|
||||||
|
></ak-crypto-certificate-search>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When selected, assertions will be encrypted using this keypair.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Property mappings")}
|
label=${msg("Property mappings")}
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
|
Reference in New Issue
Block a user