From 40428f5a825baa320d4b50427f86bb5e50cdc711 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 6 Jul 2021 16:54:58 +0200 Subject: [PATCH] providers/saml: fix parsing of POST bindings Signed-off-by: Jens Langhammer --- .../saml/processors/request_parser.py | 17 ++++++--- .../saml/tests/test_auth_n_request.py | 37 ++++++++++++++++++- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/authentik/providers/saml/processors/request_parser.py b/authentik/providers/saml/processors/request_parser.py index 71b7aa859..0746e6b5c 100644 --- a/authentik/providers/saml/processors/request_parser.py +++ b/authentik/providers/saml/processors/request_parser.py @@ -1,7 +1,7 @@ """SAML AuthNRequest Parser and dataclass""" from base64 import b64decode from dataclasses import dataclass -from typing import Optional +from typing import Optional, Union from urllib.parse import quote_plus import xmlsec @@ -54,7 +54,9 @@ class AuthNRequestParser: def __init__(self, provider: SAMLProvider): self.provider = provider - def _parse_xml(self, decoded_xml: str, relay_state: Optional[str]) -> AuthNRequest: + def _parse_xml( + self, decoded_xml: Union[str, bytes], relay_state: Optional[str] + ) -> AuthNRequest: root = ElementTree.fromstring(decoded_xml) request_acs_url = root.attrib["AssertionConsumerServiceURL"] @@ -79,10 +81,12 @@ class AuthNRequestParser: return auth_n_request - def parse(self, saml_request: str, relay_state: Optional[str]) -> AuthNRequest: + def parse( + self, saml_request: str, relay_state: Optional[str] = None + ) -> AuthNRequest: """Validate and parse raw request with enveloped signautre.""" try: - decoded_xml = b64decode(saml_request.encode()).decode() + decoded_xml = b64decode(saml_request.encode()) except UnicodeDecodeError: raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST) @@ -93,8 +97,9 @@ class AuthNRequestParser: signature_nodes = root.xpath( "/samlp:AuthnRequest/ds:Signature", namespaces=NS_MAP ) - if len(signature_nodes) != 1: - raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT) + # No signatures, no verifier configured -> decode xml directly + if len(signature_nodes) < 1 and not verifier: + return self._parse_xml(decoded_xml, relay_state) signature_node = signature_nodes[0] diff --git a/authentik/providers/saml/tests/test_auth_n_request.py b/authentik/providers/saml/tests/test_auth_n_request.py index ba5915a8e..f217da405 100644 --- a/authentik/providers/saml/tests/test_auth_n_request.py +++ b/authentik/providers/saml/tests/test_auth_n_request.py @@ -14,13 +14,29 @@ from authentik.providers.saml.processors.assertion import AssertionProcessor from authentik.providers.saml.processors.request_parser import AuthNRequestParser from authentik.sources.saml.exceptions import MismatchedRequestID from authentik.sources.saml.models import SAMLSource -from authentik.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_UNSPECIFIED +from authentik.sources.saml.processors.constants import ( + SAML_NAME_ID_FORMAT_EMAIL, + SAML_NAME_ID_FORMAT_UNSPECIFIED, +) from authentik.sources.saml.processors.request import ( SESSION_REQUEST_ID, RequestProcessor, ) from authentik.sources.saml.processors.response import ResponseProcessor +POST_REQUEST = ( + "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1sMn" + "A9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgQXNzZXJ0aW9uQ29uc3VtZXJTZXJ2aWNlVVJMPSJo" + "dHRwczovL2V1LWNlbnRyYWwtMS5zaWduaW4uYXdzLmFtYXpvbi5jb20vcGxhdGZvcm0vc2FtbC9hY3MvMmQ3MzdmOTYtNT" + "VmYi00MDM1LTk1M2UtNWUyNDEzNGViNzc4IiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9pZC5iZXJ5anUub3JnL2FwcGxpY2F0" + "aW9uL3NhbWwvYXdzLXNzby9zc28vYmluZGluZy9wb3N0LyIgSUQ9ImF3c19MRHhMR2V1YnBjNWx4MTJneENnUzZ1UGJpeD" + "F5ZDVyZSIgSXNzdWVJbnN0YW50PSIyMDIxLTA3LTA2VDE0OjIzOjA2LjM4OFoiIFByb3RvY29sQmluZGluZz0idXJuOm9h" + "c2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIH" + "htbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwczovL2V1LWNlbnRyYWwt" + "MS5zaWduaW4uYXdzLmFtYXpvbi5jb20vcGxhdGZvcm0vc2FtbC9kLTk5NjcyZjgyNzg8L3NhbWwyOklzc3Vlcj48c2FtbD" + "JwOk5hbWVJRFBvbGljeSBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWls" + "QWRkcmVzcyIvPjwvc2FtbDJwOkF1dGhuUmVxdWVzdD4=" +) REDIRECT_REQUEST = ( "fZLNbsIwEIRfJfIdbKeFgEUipXAoEm0jSHvopTLJplhK7NTr9Oft6yRUKhekPdk73+yOdoWyqVuRdu6k9/DRAbrgu6k1iu" "EjJp3VwkhUKLRsAIUrxCF92IlwykRrjTOFqUmQIoJ1yui10dg1YA9gP1UBz/tdTE7OtSgo5WzKQzYditGeP8GW9rSQZk+H" @@ -208,3 +224,22 @@ class TestAuthNRequest(TestCase): self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab") self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_UNSPECIFIED) self.assertEqual(parsed_request.relay_state, REDIRECT_RELAY_STATE) + + def test_signed_static(self): + """Test post request with static request""" + provider = SAMLProvider( + name="aws", + authorization_flow=Flow.objects.get( + slug="default-provider-authorization-implicit-consent" + ), + acs_url=( + "https://eu-central-1.signin.aws.amazon.com/platform/" + "saml/acs/2d737f96-55fb-4035-953e-5e24134eb778" + ), + audience="https://10.120.20.200/saml-sp/SAML2/POST", + issuer="https://10.120.20.200/saml-sp/SAML2/POST", + signing_kp=CertificateKeyPair.objects.first(), + ) + parsed_request = AuthNRequestParser(provider).parse(POST_REQUEST) + self.assertEqual(parsed_request.id, "aws_LDxLGeubpc5lx12gxCgS6uPbix1yd5re") + self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL)