authentik.providers.saml.tests.test_metadata
Test Service-Provider Metadata Parser
1"""Test Service-Provider Metadata Parser""" 2 3import xmlsec 4from defusedxml.lxml import fromstring 5from django.test import RequestFactory, TestCase 6from lxml import etree # nosec 7 8from authentik.common.saml.constants import ECDSA_SHA256, NS_MAP, NS_SAML_METADATA 9from authentik.core.models import Application 10from authentik.core.tests.utils import create_test_cert, create_test_flow 11from authentik.crypto.builder import PrivateKeyAlg 12from authentik.lib.generators import generate_id 13from authentik.lib.tests.utils import load_fixture 14from authentik.lib.xml import lxml_from_string 15from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider 16from authentik.providers.saml.processors.metadata import MetadataProcessor 17from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser 18from authentik.sources.saml.models import SAMLNameIDPolicy 19 20 21class TestServiceProviderMetadataParser(TestCase): 22 """Test ServiceProviderMetadataParser parsing and creation of SAML Provider""" 23 24 def setUp(self) -> None: 25 self.flow = create_test_flow() 26 self.factory = RequestFactory() 27 28 def test_consistent(self): 29 """Test that metadata generation is consistent""" 30 provider = SAMLProvider.objects.create( 31 name=generate_id(), 32 authorization_flow=self.flow, 33 ) 34 Application.objects.create( 35 name=generate_id(), 36 slug=generate_id(), 37 provider=provider, 38 ) 39 request = self.factory.get("/") 40 metadata_a = MetadataProcessor(provider, request).build_entity_descriptor() 41 metadata_b = MetadataProcessor(provider, request).build_entity_descriptor() 42 self.assertEqual(metadata_a, metadata_b) 43 44 def test_schema(self): 45 """Test that metadata generation is consistent""" 46 provider = SAMLProvider.objects.create( 47 name=generate_id(), 48 authorization_flow=self.flow, 49 ) 50 Application.objects.create( 51 name=generate_id(), 52 slug=generate_id(), 53 provider=provider, 54 ) 55 request = self.factory.get("/") 56 metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor()) 57 58 schema = etree.XMLSchema( 59 etree.parse( 60 source="schemas/saml-schema-metadata-2.0.xsd", parser=etree.XMLParser() 61 ) # nosec 62 ) 63 self.assertTrue(schema.validate(metadata)) 64 65 def test_schema_want_authn_requests_signed(self): 66 """Test metadata generation with WantAuthnRequestsSigned""" 67 cert = create_test_cert() 68 provider = SAMLProvider.objects.create( 69 name=generate_id(), 70 authorization_flow=self.flow, 71 verification_kp=cert, 72 ) 73 Application.objects.create( 74 name=generate_id(), 75 slug=generate_id(), 76 provider=provider, 77 ) 78 request = self.factory.get("/") 79 metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor()) 80 idp_sso_descriptor = metadata.findall(f"{{{NS_SAML_METADATA}}}IDPSSODescriptor")[0] 81 self.assertEqual(idp_sso_descriptor.attrib["WantAuthnRequestsSigned"], "true") 82 83 def test_simple(self): 84 """Test simple metadata without Signing""" 85 metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/simple.xml")) 86 provider = metadata.to_provider("test", self.flow, self.flow) 87 self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs") 88 self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata") 89 self.assertEqual(provider.sp_binding, SAMLBindings.POST) 90 self.assertEqual(provider.default_name_id_policy, SAMLNameIDPolicy.EMAIL) 91 self.assertEqual( 92 len(provider.property_mappings.all()), 93 len(SAMLPropertyMapping.objects.exclude(managed__isnull=True)), 94 ) 95 96 def test_with_signing_cert(self): 97 """Test Metadata with signing cert""" 98 create_test_cert() 99 metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/cert.xml")) 100 provider = metadata.to_provider("test", self.flow, self.flow) 101 self.assertEqual(provider.acs_url, "http://localhost:8080/apps/user_saml/saml/acs") 102 self.assertEqual(provider.issuer, "http://localhost:8080/apps/user_saml/saml/metadata") 103 self.assertEqual(provider.sp_binding, SAMLBindings.POST) 104 self.assertEqual( 105 provider.verification_kp.certificate_data, load_fixture("fixtures/cert.pem") 106 ) 107 self.assertIsNotNone(provider.signing_kp) 108 self.assertEqual(provider.audience, "") 109 110 def test_with_signing_cert_invalid_signature(self): 111 """Test Metadata with signing cert (invalid signature)""" 112 with self.assertRaises(ValueError): 113 ServiceProviderMetadataParser().parse( 114 load_fixture("fixtures/cert.xml").replace("/apps/user_saml", "") 115 ) 116 117 def test_signature_rsa(self): 118 """Test signature validation (RSA)""" 119 provider = SAMLProvider.objects.create( 120 name=generate_id(), 121 authorization_flow=self.flow, 122 signing_kp=create_test_cert(PrivateKeyAlg.RSA), 123 ) 124 Application.objects.create( 125 name=generate_id(), 126 slug=generate_id(), 127 provider=provider, 128 ) 129 request = self.factory.get("/") 130 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 131 132 root = fromstring(metadata.encode()) 133 xmlsec.tree.add_ids(root, ["ID"]) 134 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 135 signature_node = signature_nodes[0] 136 ctx = xmlsec.SignatureContext() 137 key = xmlsec.Key.from_memory( 138 provider.signing_kp.certificate_data, 139 xmlsec.constants.KeyDataFormatCertPem, 140 None, 141 ) 142 ctx.key = key 143 ctx.verify(signature_node) 144 145 def test_signature_ecdsa(self): 146 """Test signature validation (ECDSA)""" 147 provider = SAMLProvider.objects.create( 148 name=generate_id(), 149 authorization_flow=self.flow, 150 signing_kp=create_test_cert(PrivateKeyAlg.ECDSA), 151 signature_algorithm=ECDSA_SHA256, 152 ) 153 Application.objects.create( 154 name=generate_id(), 155 slug=generate_id(), 156 provider=provider, 157 ) 158 request = self.factory.get("/") 159 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 160 161 root = fromstring(metadata.encode()) 162 xmlsec.tree.add_ids(root, ["ID"]) 163 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 164 signature_node = signature_nodes[0] 165 ctx = xmlsec.SignatureContext() 166 key = xmlsec.Key.from_memory( 167 provider.signing_kp.certificate_data, 168 xmlsec.constants.KeyDataFormatCertPem, 169 None, 170 ) 171 ctx.key = key 172 ctx.verify(signature_node)
class
TestServiceProviderMetadataParser(django.test.testcases.TestCase):
22class TestServiceProviderMetadataParser(TestCase): 23 """Test ServiceProviderMetadataParser parsing and creation of SAML Provider""" 24 25 def setUp(self) -> None: 26 self.flow = create_test_flow() 27 self.factory = RequestFactory() 28 29 def test_consistent(self): 30 """Test that metadata generation is consistent""" 31 provider = SAMLProvider.objects.create( 32 name=generate_id(), 33 authorization_flow=self.flow, 34 ) 35 Application.objects.create( 36 name=generate_id(), 37 slug=generate_id(), 38 provider=provider, 39 ) 40 request = self.factory.get("/") 41 metadata_a = MetadataProcessor(provider, request).build_entity_descriptor() 42 metadata_b = MetadataProcessor(provider, request).build_entity_descriptor() 43 self.assertEqual(metadata_a, metadata_b) 44 45 def test_schema(self): 46 """Test that metadata generation is consistent""" 47 provider = SAMLProvider.objects.create( 48 name=generate_id(), 49 authorization_flow=self.flow, 50 ) 51 Application.objects.create( 52 name=generate_id(), 53 slug=generate_id(), 54 provider=provider, 55 ) 56 request = self.factory.get("/") 57 metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor()) 58 59 schema = etree.XMLSchema( 60 etree.parse( 61 source="schemas/saml-schema-metadata-2.0.xsd", parser=etree.XMLParser() 62 ) # nosec 63 ) 64 self.assertTrue(schema.validate(metadata)) 65 66 def test_schema_want_authn_requests_signed(self): 67 """Test metadata generation with WantAuthnRequestsSigned""" 68 cert = create_test_cert() 69 provider = SAMLProvider.objects.create( 70 name=generate_id(), 71 authorization_flow=self.flow, 72 verification_kp=cert, 73 ) 74 Application.objects.create( 75 name=generate_id(), 76 slug=generate_id(), 77 provider=provider, 78 ) 79 request = self.factory.get("/") 80 metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor()) 81 idp_sso_descriptor = metadata.findall(f"{{{NS_SAML_METADATA}}}IDPSSODescriptor")[0] 82 self.assertEqual(idp_sso_descriptor.attrib["WantAuthnRequestsSigned"], "true") 83 84 def test_simple(self): 85 """Test simple metadata without Signing""" 86 metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/simple.xml")) 87 provider = metadata.to_provider("test", self.flow, self.flow) 88 self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs") 89 self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata") 90 self.assertEqual(provider.sp_binding, SAMLBindings.POST) 91 self.assertEqual(provider.default_name_id_policy, SAMLNameIDPolicy.EMAIL) 92 self.assertEqual( 93 len(provider.property_mappings.all()), 94 len(SAMLPropertyMapping.objects.exclude(managed__isnull=True)), 95 ) 96 97 def test_with_signing_cert(self): 98 """Test Metadata with signing cert""" 99 create_test_cert() 100 metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/cert.xml")) 101 provider = metadata.to_provider("test", self.flow, self.flow) 102 self.assertEqual(provider.acs_url, "http://localhost:8080/apps/user_saml/saml/acs") 103 self.assertEqual(provider.issuer, "http://localhost:8080/apps/user_saml/saml/metadata") 104 self.assertEqual(provider.sp_binding, SAMLBindings.POST) 105 self.assertEqual( 106 provider.verification_kp.certificate_data, load_fixture("fixtures/cert.pem") 107 ) 108 self.assertIsNotNone(provider.signing_kp) 109 self.assertEqual(provider.audience, "") 110 111 def test_with_signing_cert_invalid_signature(self): 112 """Test Metadata with signing cert (invalid signature)""" 113 with self.assertRaises(ValueError): 114 ServiceProviderMetadataParser().parse( 115 load_fixture("fixtures/cert.xml").replace("/apps/user_saml", "") 116 ) 117 118 def test_signature_rsa(self): 119 """Test signature validation (RSA)""" 120 provider = SAMLProvider.objects.create( 121 name=generate_id(), 122 authorization_flow=self.flow, 123 signing_kp=create_test_cert(PrivateKeyAlg.RSA), 124 ) 125 Application.objects.create( 126 name=generate_id(), 127 slug=generate_id(), 128 provider=provider, 129 ) 130 request = self.factory.get("/") 131 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 132 133 root = fromstring(metadata.encode()) 134 xmlsec.tree.add_ids(root, ["ID"]) 135 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 136 signature_node = signature_nodes[0] 137 ctx = xmlsec.SignatureContext() 138 key = xmlsec.Key.from_memory( 139 provider.signing_kp.certificate_data, 140 xmlsec.constants.KeyDataFormatCertPem, 141 None, 142 ) 143 ctx.key = key 144 ctx.verify(signature_node) 145 146 def test_signature_ecdsa(self): 147 """Test signature validation (ECDSA)""" 148 provider = SAMLProvider.objects.create( 149 name=generate_id(), 150 authorization_flow=self.flow, 151 signing_kp=create_test_cert(PrivateKeyAlg.ECDSA), 152 signature_algorithm=ECDSA_SHA256, 153 ) 154 Application.objects.create( 155 name=generate_id(), 156 slug=generate_id(), 157 provider=provider, 158 ) 159 request = self.factory.get("/") 160 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 161 162 root = fromstring(metadata.encode()) 163 xmlsec.tree.add_ids(root, ["ID"]) 164 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 165 signature_node = signature_nodes[0] 166 ctx = xmlsec.SignatureContext() 167 key = xmlsec.Key.from_memory( 168 provider.signing_kp.certificate_data, 169 xmlsec.constants.KeyDataFormatCertPem, 170 None, 171 ) 172 ctx.key = key 173 ctx.verify(signature_node)
Test ServiceProviderMetadataParser parsing and creation of SAML Provider
def
test_consistent(self):
29 def test_consistent(self): 30 """Test that metadata generation is consistent""" 31 provider = SAMLProvider.objects.create( 32 name=generate_id(), 33 authorization_flow=self.flow, 34 ) 35 Application.objects.create( 36 name=generate_id(), 37 slug=generate_id(), 38 provider=provider, 39 ) 40 request = self.factory.get("/") 41 metadata_a = MetadataProcessor(provider, request).build_entity_descriptor() 42 metadata_b = MetadataProcessor(provider, request).build_entity_descriptor() 43 self.assertEqual(metadata_a, metadata_b)
Test that metadata generation is consistent
def
test_schema(self):
45 def test_schema(self): 46 """Test that metadata generation is consistent""" 47 provider = SAMLProvider.objects.create( 48 name=generate_id(), 49 authorization_flow=self.flow, 50 ) 51 Application.objects.create( 52 name=generate_id(), 53 slug=generate_id(), 54 provider=provider, 55 ) 56 request = self.factory.get("/") 57 metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor()) 58 59 schema = etree.XMLSchema( 60 etree.parse( 61 source="schemas/saml-schema-metadata-2.0.xsd", parser=etree.XMLParser() 62 ) # nosec 63 ) 64 self.assertTrue(schema.validate(metadata))
Test that metadata generation is consistent
def
test_schema_want_authn_requests_signed(self):
66 def test_schema_want_authn_requests_signed(self): 67 """Test metadata generation with WantAuthnRequestsSigned""" 68 cert = create_test_cert() 69 provider = SAMLProvider.objects.create( 70 name=generate_id(), 71 authorization_flow=self.flow, 72 verification_kp=cert, 73 ) 74 Application.objects.create( 75 name=generate_id(), 76 slug=generate_id(), 77 provider=provider, 78 ) 79 request = self.factory.get("/") 80 metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor()) 81 idp_sso_descriptor = metadata.findall(f"{{{NS_SAML_METADATA}}}IDPSSODescriptor")[0] 82 self.assertEqual(idp_sso_descriptor.attrib["WantAuthnRequestsSigned"], "true")
Test metadata generation with WantAuthnRequestsSigned
def
test_simple(self):
84 def test_simple(self): 85 """Test simple metadata without Signing""" 86 metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/simple.xml")) 87 provider = metadata.to_provider("test", self.flow, self.flow) 88 self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs") 89 self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata") 90 self.assertEqual(provider.sp_binding, SAMLBindings.POST) 91 self.assertEqual(provider.default_name_id_policy, SAMLNameIDPolicy.EMAIL) 92 self.assertEqual( 93 len(provider.property_mappings.all()), 94 len(SAMLPropertyMapping.objects.exclude(managed__isnull=True)), 95 )
Test simple metadata without Signing
def
test_with_signing_cert(self):
97 def test_with_signing_cert(self): 98 """Test Metadata with signing cert""" 99 create_test_cert() 100 metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/cert.xml")) 101 provider = metadata.to_provider("test", self.flow, self.flow) 102 self.assertEqual(provider.acs_url, "http://localhost:8080/apps/user_saml/saml/acs") 103 self.assertEqual(provider.issuer, "http://localhost:8080/apps/user_saml/saml/metadata") 104 self.assertEqual(provider.sp_binding, SAMLBindings.POST) 105 self.assertEqual( 106 provider.verification_kp.certificate_data, load_fixture("fixtures/cert.pem") 107 ) 108 self.assertIsNotNone(provider.signing_kp) 109 self.assertEqual(provider.audience, "")
Test Metadata with signing cert
def
test_with_signing_cert_invalid_signature(self):
111 def test_with_signing_cert_invalid_signature(self): 112 """Test Metadata with signing cert (invalid signature)""" 113 with self.assertRaises(ValueError): 114 ServiceProviderMetadataParser().parse( 115 load_fixture("fixtures/cert.xml").replace("/apps/user_saml", "") 116 )
Test Metadata with signing cert (invalid signature)
def
test_signature_rsa(self):
118 def test_signature_rsa(self): 119 """Test signature validation (RSA)""" 120 provider = SAMLProvider.objects.create( 121 name=generate_id(), 122 authorization_flow=self.flow, 123 signing_kp=create_test_cert(PrivateKeyAlg.RSA), 124 ) 125 Application.objects.create( 126 name=generate_id(), 127 slug=generate_id(), 128 provider=provider, 129 ) 130 request = self.factory.get("/") 131 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 132 133 root = fromstring(metadata.encode()) 134 xmlsec.tree.add_ids(root, ["ID"]) 135 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 136 signature_node = signature_nodes[0] 137 ctx = xmlsec.SignatureContext() 138 key = xmlsec.Key.from_memory( 139 provider.signing_kp.certificate_data, 140 xmlsec.constants.KeyDataFormatCertPem, 141 None, 142 ) 143 ctx.key = key 144 ctx.verify(signature_node)
Test signature validation (RSA)
def
test_signature_ecdsa(self):
146 def test_signature_ecdsa(self): 147 """Test signature validation (ECDSA)""" 148 provider = SAMLProvider.objects.create( 149 name=generate_id(), 150 authorization_flow=self.flow, 151 signing_kp=create_test_cert(PrivateKeyAlg.ECDSA), 152 signature_algorithm=ECDSA_SHA256, 153 ) 154 Application.objects.create( 155 name=generate_id(), 156 slug=generate_id(), 157 provider=provider, 158 ) 159 request = self.factory.get("/") 160 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 161 162 root = fromstring(metadata.encode()) 163 xmlsec.tree.add_ids(root, ["ID"]) 164 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 165 signature_node = signature_nodes[0] 166 ctx = xmlsec.SignatureContext() 167 key = xmlsec.Key.from_memory( 168 provider.signing_kp.certificate_data, 169 xmlsec.constants.KeyDataFormatCertPem, 170 None, 171 ) 172 ctx.key = key 173 ctx.verify(signature_node)
Test signature validation (ECDSA)