authentik.providers.saml.tests.test_logout_response_processor
logout response tests
1"""logout response tests""" 2 3from defusedxml import ElementTree 4from django.test import TestCase 5 6from authentik.blueprints.tests import apply_blueprint 7from authentik.common.saml.constants import ( 8 NS_SAML_ASSERTION, 9 NS_SAML_PROTOCOL, 10 NS_SIGNATURE, 11) 12from authentik.core.tests.utils import create_test_cert, create_test_flow 13from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider 14from authentik.providers.saml.processors.logout_request_parser import LogoutRequest 15from authentik.providers.saml.processors.logout_response_processor import LogoutResponseProcessor 16 17 18class TestLogoutResponse(TestCase): 19 """Test LogoutResponse processor""" 20 21 @apply_blueprint("system/providers-saml.yaml") 22 def setUp(self): 23 cert = create_test_cert() 24 self.provider: SAMLProvider = SAMLProvider.objects.create( 25 authorization_flow=create_test_flow(), 26 acs_url="http://testserver/source/saml/provider/acs/", 27 sls_url="http://testserver/source/saml/provider/sls/", 28 signing_kp=cert, 29 verification_kp=cert, 30 ) 31 self.provider.property_mappings.set(SAMLPropertyMapping.objects.all()) 32 self.provider.save() 33 34 def test_build_response(self): 35 """Test building a LogoutResponse""" 36 logout_request = LogoutRequest( 37 id="test-request-id", 38 issuer="test-sp", 39 relay_state="test-relay-state", 40 ) 41 42 processor = LogoutResponseProcessor( 43 self.provider, logout_request, destination=self.provider.sls_url 44 ) 45 response_xml = processor.build_response(status="Success") 46 47 # Parse and verify 48 root = ElementTree.fromstring(response_xml) 49 self.assertEqual(root.tag, f"{{{NS_SAML_PROTOCOL}}}LogoutResponse") 50 self.assertEqual(root.attrib["Version"], "2.0") 51 self.assertEqual(root.attrib["Destination"], self.provider.sls_url) 52 self.assertEqual(root.attrib["InResponseTo"], "test-request-id") 53 54 # Check Issuer 55 issuer = root.find(f"{{{NS_SAML_ASSERTION}}}Issuer") 56 self.assertEqual(issuer.text, self.provider.issuer) 57 58 # Check Status 59 status = root.find(f".//{{{NS_SAML_PROTOCOL}}}StatusCode") 60 self.assertEqual(status.attrib["Value"], "urn:oasis:names:tc:SAML:2.0:status:Success") 61 62 def test_build_response_signed(self): 63 """Test building a signed LogoutResponse""" 64 self.provider.sign_logout_response = True 65 self.provider.save() 66 67 logout_request = LogoutRequest( 68 id="test-request-id", 69 issuer="test-sp", 70 relay_state="test-relay-state", 71 ) 72 73 processor = LogoutResponseProcessor( 74 self.provider, logout_request, destination=self.provider.sls_url 75 ) 76 response_xml = processor.build_response(status="Success") 77 78 # Parse and verify signature is present 79 root = ElementTree.fromstring(response_xml) 80 signature = root.find(f".//{{{NS_SIGNATURE}}}Signature") 81 self.assertIsNotNone(signature) 82 83 # Verify signature structure 84 signed_info = signature.find(f"{{{NS_SIGNATURE}}}SignedInfo") 85 self.assertIsNotNone(signed_info) 86 signature_value = signature.find(f"{{{NS_SIGNATURE}}}SignatureValue") 87 self.assertIsNotNone(signature_value) 88 self.assertIsNotNone(signature_value.text) 89 90 def test_no_inresponseto(self): 91 """Test building response without a logout request omits InResponseTo attribute""" 92 processor = LogoutResponseProcessor(self.provider, None, destination=self.provider.sls_url) 93 response_xml = processor.build_response(status="Success") 94 95 root = ElementTree.fromstring(response_xml) 96 self.assertEqual(root.tag, f"{{{NS_SAML_PROTOCOL}}}LogoutResponse") 97 self.assertNotIn("InResponseTo", root.attrib) 98 99 def test_no_destination(self): 100 """Test building response without destination""" 101 logout_request = LogoutRequest( 102 id="test-request-id", 103 issuer="test-sp", 104 ) 105 106 processor = LogoutResponseProcessor(self.provider, logout_request, destination=None) 107 response_xml = processor.build_response(status="Success") 108 109 root = ElementTree.fromstring(response_xml) 110 self.assertNotIn("Destination", root.attrib) 111 112 def test_relay_state_from_logout_request(self): 113 """Test that relay_state is taken from logout_request if not provided""" 114 logout_request = LogoutRequest( 115 id="test-request-id", 116 issuer="test-sp", 117 relay_state="request-relay-state", 118 ) 119 120 processor = LogoutResponseProcessor( 121 self.provider, logout_request, destination=self.provider.sls_url 122 ) 123 self.assertEqual(processor.relay_state, "request-relay-state") 124 125 def test_relay_state_override(self): 126 """Test that explicit relay_state overrides logout_request relay_state""" 127 logout_request = LogoutRequest( 128 id="test-request-id", 129 issuer="test-sp", 130 relay_state="request-relay-state", 131 ) 132 133 processor = LogoutResponseProcessor( 134 self.provider, 135 logout_request, 136 destination=self.provider.sls_url, 137 relay_state="explicit-relay-state", 138 ) 139 self.assertEqual(processor.relay_state, "explicit-relay-state")
class
TestLogoutResponse(django.test.testcases.TestCase):
19class TestLogoutResponse(TestCase): 20 """Test LogoutResponse processor""" 21 22 @apply_blueprint("system/providers-saml.yaml") 23 def setUp(self): 24 cert = create_test_cert() 25 self.provider: SAMLProvider = SAMLProvider.objects.create( 26 authorization_flow=create_test_flow(), 27 acs_url="http://testserver/source/saml/provider/acs/", 28 sls_url="http://testserver/source/saml/provider/sls/", 29 signing_kp=cert, 30 verification_kp=cert, 31 ) 32 self.provider.property_mappings.set(SAMLPropertyMapping.objects.all()) 33 self.provider.save() 34 35 def test_build_response(self): 36 """Test building a LogoutResponse""" 37 logout_request = LogoutRequest( 38 id="test-request-id", 39 issuer="test-sp", 40 relay_state="test-relay-state", 41 ) 42 43 processor = LogoutResponseProcessor( 44 self.provider, logout_request, destination=self.provider.sls_url 45 ) 46 response_xml = processor.build_response(status="Success") 47 48 # Parse and verify 49 root = ElementTree.fromstring(response_xml) 50 self.assertEqual(root.tag, f"{{{NS_SAML_PROTOCOL}}}LogoutResponse") 51 self.assertEqual(root.attrib["Version"], "2.0") 52 self.assertEqual(root.attrib["Destination"], self.provider.sls_url) 53 self.assertEqual(root.attrib["InResponseTo"], "test-request-id") 54 55 # Check Issuer 56 issuer = root.find(f"{{{NS_SAML_ASSERTION}}}Issuer") 57 self.assertEqual(issuer.text, self.provider.issuer) 58 59 # Check Status 60 status = root.find(f".//{{{NS_SAML_PROTOCOL}}}StatusCode") 61 self.assertEqual(status.attrib["Value"], "urn:oasis:names:tc:SAML:2.0:status:Success") 62 63 def test_build_response_signed(self): 64 """Test building a signed LogoutResponse""" 65 self.provider.sign_logout_response = True 66 self.provider.save() 67 68 logout_request = LogoutRequest( 69 id="test-request-id", 70 issuer="test-sp", 71 relay_state="test-relay-state", 72 ) 73 74 processor = LogoutResponseProcessor( 75 self.provider, logout_request, destination=self.provider.sls_url 76 ) 77 response_xml = processor.build_response(status="Success") 78 79 # Parse and verify signature is present 80 root = ElementTree.fromstring(response_xml) 81 signature = root.find(f".//{{{NS_SIGNATURE}}}Signature") 82 self.assertIsNotNone(signature) 83 84 # Verify signature structure 85 signed_info = signature.find(f"{{{NS_SIGNATURE}}}SignedInfo") 86 self.assertIsNotNone(signed_info) 87 signature_value = signature.find(f"{{{NS_SIGNATURE}}}SignatureValue") 88 self.assertIsNotNone(signature_value) 89 self.assertIsNotNone(signature_value.text) 90 91 def test_no_inresponseto(self): 92 """Test building response without a logout request omits InResponseTo attribute""" 93 processor = LogoutResponseProcessor(self.provider, None, destination=self.provider.sls_url) 94 response_xml = processor.build_response(status="Success") 95 96 root = ElementTree.fromstring(response_xml) 97 self.assertEqual(root.tag, f"{{{NS_SAML_PROTOCOL}}}LogoutResponse") 98 self.assertNotIn("InResponseTo", root.attrib) 99 100 def test_no_destination(self): 101 """Test building response without destination""" 102 logout_request = LogoutRequest( 103 id="test-request-id", 104 issuer="test-sp", 105 ) 106 107 processor = LogoutResponseProcessor(self.provider, logout_request, destination=None) 108 response_xml = processor.build_response(status="Success") 109 110 root = ElementTree.fromstring(response_xml) 111 self.assertNotIn("Destination", root.attrib) 112 113 def test_relay_state_from_logout_request(self): 114 """Test that relay_state is taken from logout_request if not provided""" 115 logout_request = LogoutRequest( 116 id="test-request-id", 117 issuer="test-sp", 118 relay_state="request-relay-state", 119 ) 120 121 processor = LogoutResponseProcessor( 122 self.provider, logout_request, destination=self.provider.sls_url 123 ) 124 self.assertEqual(processor.relay_state, "request-relay-state") 125 126 def test_relay_state_override(self): 127 """Test that explicit relay_state overrides logout_request relay_state""" 128 logout_request = LogoutRequest( 129 id="test-request-id", 130 issuer="test-sp", 131 relay_state="request-relay-state", 132 ) 133 134 processor = LogoutResponseProcessor( 135 self.provider, 136 logout_request, 137 destination=self.provider.sls_url, 138 relay_state="explicit-relay-state", 139 ) 140 self.assertEqual(processor.relay_state, "explicit-relay-state")
Test LogoutResponse processor
@apply_blueprint('system/providers-saml.yaml')
def
setUp(self):
22 @apply_blueprint("system/providers-saml.yaml") 23 def setUp(self): 24 cert = create_test_cert() 25 self.provider: SAMLProvider = SAMLProvider.objects.create( 26 authorization_flow=create_test_flow(), 27 acs_url="http://testserver/source/saml/provider/acs/", 28 sls_url="http://testserver/source/saml/provider/sls/", 29 signing_kp=cert, 30 verification_kp=cert, 31 ) 32 self.provider.property_mappings.set(SAMLPropertyMapping.objects.all()) 33 self.provider.save()
Hook method for setting up the test fixture before exercising it.
def
test_build_response(self):
35 def test_build_response(self): 36 """Test building a LogoutResponse""" 37 logout_request = LogoutRequest( 38 id="test-request-id", 39 issuer="test-sp", 40 relay_state="test-relay-state", 41 ) 42 43 processor = LogoutResponseProcessor( 44 self.provider, logout_request, destination=self.provider.sls_url 45 ) 46 response_xml = processor.build_response(status="Success") 47 48 # Parse and verify 49 root = ElementTree.fromstring(response_xml) 50 self.assertEqual(root.tag, f"{{{NS_SAML_PROTOCOL}}}LogoutResponse") 51 self.assertEqual(root.attrib["Version"], "2.0") 52 self.assertEqual(root.attrib["Destination"], self.provider.sls_url) 53 self.assertEqual(root.attrib["InResponseTo"], "test-request-id") 54 55 # Check Issuer 56 issuer = root.find(f"{{{NS_SAML_ASSERTION}}}Issuer") 57 self.assertEqual(issuer.text, self.provider.issuer) 58 59 # Check Status 60 status = root.find(f".//{{{NS_SAML_PROTOCOL}}}StatusCode") 61 self.assertEqual(status.attrib["Value"], "urn:oasis:names:tc:SAML:2.0:status:Success")
Test building a LogoutResponse
def
test_build_response_signed(self):
63 def test_build_response_signed(self): 64 """Test building a signed LogoutResponse""" 65 self.provider.sign_logout_response = True 66 self.provider.save() 67 68 logout_request = LogoutRequest( 69 id="test-request-id", 70 issuer="test-sp", 71 relay_state="test-relay-state", 72 ) 73 74 processor = LogoutResponseProcessor( 75 self.provider, logout_request, destination=self.provider.sls_url 76 ) 77 response_xml = processor.build_response(status="Success") 78 79 # Parse and verify signature is present 80 root = ElementTree.fromstring(response_xml) 81 signature = root.find(f".//{{{NS_SIGNATURE}}}Signature") 82 self.assertIsNotNone(signature) 83 84 # Verify signature structure 85 signed_info = signature.find(f"{{{NS_SIGNATURE}}}SignedInfo") 86 self.assertIsNotNone(signed_info) 87 signature_value = signature.find(f"{{{NS_SIGNATURE}}}SignatureValue") 88 self.assertIsNotNone(signature_value) 89 self.assertIsNotNone(signature_value.text)
Test building a signed LogoutResponse
def
test_no_inresponseto(self):
91 def test_no_inresponseto(self): 92 """Test building response without a logout request omits InResponseTo attribute""" 93 processor = LogoutResponseProcessor(self.provider, None, destination=self.provider.sls_url) 94 response_xml = processor.build_response(status="Success") 95 96 root = ElementTree.fromstring(response_xml) 97 self.assertEqual(root.tag, f"{{{NS_SAML_PROTOCOL}}}LogoutResponse") 98 self.assertNotIn("InResponseTo", root.attrib)
Test building response without a logout request omits InResponseTo attribute
def
test_no_destination(self):
100 def test_no_destination(self): 101 """Test building response without destination""" 102 logout_request = LogoutRequest( 103 id="test-request-id", 104 issuer="test-sp", 105 ) 106 107 processor = LogoutResponseProcessor(self.provider, logout_request, destination=None) 108 response_xml = processor.build_response(status="Success") 109 110 root = ElementTree.fromstring(response_xml) 111 self.assertNotIn("Destination", root.attrib)
Test building response without destination
def
test_relay_state_from_logout_request(self):
113 def test_relay_state_from_logout_request(self): 114 """Test that relay_state is taken from logout_request if not provided""" 115 logout_request = LogoutRequest( 116 id="test-request-id", 117 issuer="test-sp", 118 relay_state="request-relay-state", 119 ) 120 121 processor = LogoutResponseProcessor( 122 self.provider, logout_request, destination=self.provider.sls_url 123 ) 124 self.assertEqual(processor.relay_state, "request-relay-state")
Test that relay_state is taken from logout_request if not provided
def
test_relay_state_override(self):
126 def test_relay_state_override(self): 127 """Test that explicit relay_state overrides logout_request relay_state""" 128 logout_request = LogoutRequest( 129 id="test-request-id", 130 issuer="test-sp", 131 relay_state="request-relay-state", 132 ) 133 134 processor = LogoutResponseProcessor( 135 self.provider, 136 logout_request, 137 destination=self.provider.sls_url, 138 relay_state="explicit-relay-state", 139 ) 140 self.assertEqual(processor.relay_state, "explicit-relay-state")
Test that explicit relay_state overrides logout_request relay_state