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.sp_binding, SAMLBindings.POST) 89 self.assertEqual(provider.default_name_id_policy, SAMLNameIDPolicy.EMAIL) 90 self.assertEqual( 91 len(provider.property_mappings.all()), 92 len(SAMLPropertyMapping.objects.exclude(managed__isnull=True)), 93 ) 94 95 def test_with_signing_cert(self): 96 """Test Metadata with signing cert""" 97 create_test_cert() 98 metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/cert.xml")) 99 provider = metadata.to_provider("test", self.flow, self.flow) 100 self.assertEqual(provider.acs_url, "http://localhost:8080/apps/user_saml/saml/acs") 101 self.assertEqual(provider.sp_binding, SAMLBindings.POST) 102 self.assertEqual( 103 provider.verification_kp.certificate_data, load_fixture("fixtures/cert.pem") 104 ) 105 self.assertIsNotNone(provider.signing_kp) 106 self.assertEqual(provider.audience, "http://localhost:8080/apps/user_saml/saml/metadata") 107 108 def test_with_signing_cert_invalid_signature(self): 109 """Test Metadata with signing cert (invalid signature)""" 110 with self.assertRaises(ValueError): 111 ServiceProviderMetadataParser().parse( 112 load_fixture("fixtures/cert.xml").replace("/apps/user_saml", "") 113 ) 114 115 def test_signature_rsa(self): 116 """Test signature validation (RSA)""" 117 provider = SAMLProvider.objects.create( 118 name=generate_id(), 119 authorization_flow=self.flow, 120 signing_kp=create_test_cert(PrivateKeyAlg.RSA), 121 ) 122 Application.objects.create( 123 name=generate_id(), 124 slug=generate_id(), 125 provider=provider, 126 ) 127 request = self.factory.get("/") 128 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 129 130 root = fromstring(metadata.encode()) 131 xmlsec.tree.add_ids(root, ["ID"]) 132 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 133 signature_node = signature_nodes[0] 134 ctx = xmlsec.SignatureContext() 135 key = xmlsec.Key.from_memory( 136 provider.signing_kp.certificate_data, 137 xmlsec.constants.KeyDataFormatCertPem, 138 None, 139 ) 140 ctx.key = key 141 ctx.verify(signature_node) 142 143 def test_signature_ecdsa(self): 144 """Test signature validation (ECDSA)""" 145 provider = SAMLProvider.objects.create( 146 name=generate_id(), 147 authorization_flow=self.flow, 148 signing_kp=create_test_cert(PrivateKeyAlg.ECDSA), 149 signature_algorithm=ECDSA_SHA256, 150 ) 151 Application.objects.create( 152 name=generate_id(), 153 slug=generate_id(), 154 provider=provider, 155 ) 156 request = self.factory.get("/") 157 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 158 159 root = fromstring(metadata.encode()) 160 xmlsec.tree.add_ids(root, ["ID"]) 161 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 162 signature_node = signature_nodes[0] 163 ctx = xmlsec.SignatureContext() 164 key = xmlsec.Key.from_memory( 165 provider.signing_kp.certificate_data, 166 xmlsec.constants.KeyDataFormatCertPem, 167 None, 168 ) 169 ctx.key = key 170 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.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.sp_binding, SAMLBindings.POST) 103 self.assertEqual( 104 provider.verification_kp.certificate_data, load_fixture("fixtures/cert.pem") 105 ) 106 self.assertIsNotNone(provider.signing_kp) 107 self.assertEqual(provider.audience, "http://localhost:8080/apps/user_saml/saml/metadata") 108 109 def test_with_signing_cert_invalid_signature(self): 110 """Test Metadata with signing cert (invalid signature)""" 111 with self.assertRaises(ValueError): 112 ServiceProviderMetadataParser().parse( 113 load_fixture("fixtures/cert.xml").replace("/apps/user_saml", "") 114 ) 115 116 def test_signature_rsa(self): 117 """Test signature validation (RSA)""" 118 provider = SAMLProvider.objects.create( 119 name=generate_id(), 120 authorization_flow=self.flow, 121 signing_kp=create_test_cert(PrivateKeyAlg.RSA), 122 ) 123 Application.objects.create( 124 name=generate_id(), 125 slug=generate_id(), 126 provider=provider, 127 ) 128 request = self.factory.get("/") 129 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 130 131 root = fromstring(metadata.encode()) 132 xmlsec.tree.add_ids(root, ["ID"]) 133 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 134 signature_node = signature_nodes[0] 135 ctx = xmlsec.SignatureContext() 136 key = xmlsec.Key.from_memory( 137 provider.signing_kp.certificate_data, 138 xmlsec.constants.KeyDataFormatCertPem, 139 None, 140 ) 141 ctx.key = key 142 ctx.verify(signature_node) 143 144 def test_signature_ecdsa(self): 145 """Test signature validation (ECDSA)""" 146 provider = SAMLProvider.objects.create( 147 name=generate_id(), 148 authorization_flow=self.flow, 149 signing_kp=create_test_cert(PrivateKeyAlg.ECDSA), 150 signature_algorithm=ECDSA_SHA256, 151 ) 152 Application.objects.create( 153 name=generate_id(), 154 slug=generate_id(), 155 provider=provider, 156 ) 157 request = self.factory.get("/") 158 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 159 160 root = fromstring(metadata.encode()) 161 xmlsec.tree.add_ids(root, ["ID"]) 162 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 163 signature_node = signature_nodes[0] 164 ctx = xmlsec.SignatureContext() 165 key = xmlsec.Key.from_memory( 166 provider.signing_kp.certificate_data, 167 xmlsec.constants.KeyDataFormatCertPem, 168 None, 169 ) 170 ctx.key = key 171 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.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 )
Test simple metadata without Signing
def
test_with_signing_cert(self):
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.sp_binding, SAMLBindings.POST) 103 self.assertEqual( 104 provider.verification_kp.certificate_data, load_fixture("fixtures/cert.pem") 105 ) 106 self.assertIsNotNone(provider.signing_kp) 107 self.assertEqual(provider.audience, "http://localhost:8080/apps/user_saml/saml/metadata")
Test Metadata with signing cert
def
test_with_signing_cert_invalid_signature(self):
109 def test_with_signing_cert_invalid_signature(self): 110 """Test Metadata with signing cert (invalid signature)""" 111 with self.assertRaises(ValueError): 112 ServiceProviderMetadataParser().parse( 113 load_fixture("fixtures/cert.xml").replace("/apps/user_saml", "") 114 )
Test Metadata with signing cert (invalid signature)
def
test_signature_rsa(self):
116 def test_signature_rsa(self): 117 """Test signature validation (RSA)""" 118 provider = SAMLProvider.objects.create( 119 name=generate_id(), 120 authorization_flow=self.flow, 121 signing_kp=create_test_cert(PrivateKeyAlg.RSA), 122 ) 123 Application.objects.create( 124 name=generate_id(), 125 slug=generate_id(), 126 provider=provider, 127 ) 128 request = self.factory.get("/") 129 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 130 131 root = fromstring(metadata.encode()) 132 xmlsec.tree.add_ids(root, ["ID"]) 133 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 134 signature_node = signature_nodes[0] 135 ctx = xmlsec.SignatureContext() 136 key = xmlsec.Key.from_memory( 137 provider.signing_kp.certificate_data, 138 xmlsec.constants.KeyDataFormatCertPem, 139 None, 140 ) 141 ctx.key = key 142 ctx.verify(signature_node)
Test signature validation (RSA)
def
test_signature_ecdsa(self):
144 def test_signature_ecdsa(self): 145 """Test signature validation (ECDSA)""" 146 provider = SAMLProvider.objects.create( 147 name=generate_id(), 148 authorization_flow=self.flow, 149 signing_kp=create_test_cert(PrivateKeyAlg.ECDSA), 150 signature_algorithm=ECDSA_SHA256, 151 ) 152 Application.objects.create( 153 name=generate_id(), 154 slug=generate_id(), 155 provider=provider, 156 ) 157 request = self.factory.get("/") 158 metadata = MetadataProcessor(provider, request).build_entity_descriptor() 159 160 root = fromstring(metadata.encode()) 161 xmlsec.tree.add_ids(root, ["ID"]) 162 signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP) 163 signature_node = signature_nodes[0] 164 ctx = xmlsec.SignatureContext() 165 key = xmlsec.Key.from_memory( 166 provider.signing_kp.certificate_data, 167 xmlsec.constants.KeyDataFormatCertPem, 168 None, 169 ) 170 ctx.key = key 171 ctx.verify(signature_node)
Test signature validation (ECDSA)