diff --git a/authentik/providers/saml/processors/metadata.py b/authentik/providers/saml/processors/metadata.py index cd87ad2f5..83c1a1ee3 100644 --- a/authentik/providers/saml/processors/metadata.py +++ b/authentik/providers/saml/processors/metadata.py @@ -65,7 +65,7 @@ class MetadataProcessor: element.text = name_id_format yield element - def get_bindings(self) -> Iterator[Element]: + def get_sso_bindings(self) -> Iterator[Element]: """Get all Bindings supported""" binding_url_map = { (SAML_BINDING_REDIRECT, "SingleSignOnService"): self.http_request.build_absolute_uri( @@ -80,6 +80,19 @@ class MetadataProcessor: kwargs={"application_slug": self.provider.application.slug}, ) ), + } + for binding_svc, url in binding_url_map.items(): + binding, svc = binding_svc + if self.force_binding and self.force_binding != binding: + continue + element = Element(f"{{{NS_SAML_METADATA}}}{svc}") + element.attrib["Binding"] = binding + element.attrib["Location"] = url + yield element + + def get_slo_bindings(self) -> Iterator[Element]: + """Get all Bindings supported""" + binding_url_map = { (SAML_BINDING_REDIRECT, "SingleLogoutService"): self.http_request.build_absolute_uri( reverse( "authentik_providers_saml:slo-redirect", @@ -163,10 +176,13 @@ class MetadataProcessor: if signing_descriptor is not None: idp_sso_descriptor.append(signing_descriptor) + for binding in self.get_slo_bindings(): + idp_sso_descriptor.append(binding) + for name_id_format in self.get_name_id_formats(): idp_sso_descriptor.append(name_id_format) - for binding in self.get_bindings(): + for binding in self.get_sso_bindings(): idp_sso_descriptor.append(binding) if self.provider.signing_kp: diff --git a/authentik/providers/saml/tests/test_metadata.py b/authentik/providers/saml/tests/test_metadata.py index 9ee444bfd..b891736cc 100644 --- a/authentik/providers/saml/tests/test_metadata.py +++ b/authentik/providers/saml/tests/test_metadata.py @@ -4,10 +4,12 @@ from pathlib import Path import xmlsec from defusedxml.lxml import fromstring from django.test import RequestFactory, TestCase +from lxml import etree # nosec from authentik.core.models import Application from authentik.core.tests.utils import create_test_cert, create_test_flow from authentik.lib.generators import generate_id +from authentik.lib.xml import lxml_from_string from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.processors.metadata import MetadataProcessor from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser @@ -43,6 +45,23 @@ class TestServiceProviderMetadataParser(TestCase): metadata_b = MetadataProcessor(provider, request).build_entity_descriptor() self.assertEqual(metadata_a, metadata_b) + def test_schema(self): + """Test that metadata generation is consistent""" + provider = SAMLProvider.objects.create( + name=generate_id(), + authorization_flow=self.flow, + ) + Application.objects.create( + name=generate_id(), + slug=generate_id(), + provider=provider, + ) + request = self.factory.get("/") + metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor()) + + schema = etree.XMLSchema(etree.parse("xml/saml-schema-metadata-2.0.xsd")) # nosec + self.assertTrue(schema.validate(metadata)) + def test_simple(self): """Test simple metadata without Signing""" metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/simple.xml"))