providers/saml: fix parsing of POST bindings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
007838fcf2
commit
40428f5a82
|
@ -1,7 +1,7 @@
|
||||||
"""SAML AuthNRequest Parser and dataclass"""
|
"""SAML AuthNRequest Parser and dataclass"""
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
import xmlsec
|
import xmlsec
|
||||||
|
@ -54,7 +54,9 @@ class AuthNRequestParser:
|
||||||
def __init__(self, provider: SAMLProvider):
|
def __init__(self, provider: SAMLProvider):
|
||||||
self.provider = provider
|
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)
|
root = ElementTree.fromstring(decoded_xml)
|
||||||
|
|
||||||
request_acs_url = root.attrib["AssertionConsumerServiceURL"]
|
request_acs_url = root.attrib["AssertionConsumerServiceURL"]
|
||||||
|
@ -79,10 +81,12 @@ class AuthNRequestParser:
|
||||||
|
|
||||||
return auth_n_request
|
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."""
|
"""Validate and parse raw request with enveloped signautre."""
|
||||||
try:
|
try:
|
||||||
decoded_xml = b64decode(saml_request.encode()).decode()
|
decoded_xml = b64decode(saml_request.encode())
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST)
|
raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST)
|
||||||
|
|
||||||
|
@ -93,8 +97,9 @@ class AuthNRequestParser:
|
||||||
signature_nodes = root.xpath(
|
signature_nodes = root.xpath(
|
||||||
"/samlp:AuthnRequest/ds:Signature", namespaces=NS_MAP
|
"/samlp:AuthnRequest/ds:Signature", namespaces=NS_MAP
|
||||||
)
|
)
|
||||||
if len(signature_nodes) != 1:
|
# No signatures, no verifier configured -> decode xml directly
|
||||||
raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT)
|
if len(signature_nodes) < 1 and not verifier:
|
||||||
|
return self._parse_xml(decoded_xml, relay_state)
|
||||||
|
|
||||||
signature_node = signature_nodes[0]
|
signature_node = signature_nodes[0]
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,29 @@ from authentik.providers.saml.processors.assertion import AssertionProcessor
|
||||||
from authentik.providers.saml.processors.request_parser import AuthNRequestParser
|
from authentik.providers.saml.processors.request_parser import AuthNRequestParser
|
||||||
from authentik.sources.saml.exceptions import MismatchedRequestID
|
from authentik.sources.saml.exceptions import MismatchedRequestID
|
||||||
from authentik.sources.saml.models import SAMLSource
|
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 (
|
from authentik.sources.saml.processors.request import (
|
||||||
SESSION_REQUEST_ID,
|
SESSION_REQUEST_ID,
|
||||||
RequestProcessor,
|
RequestProcessor,
|
||||||
)
|
)
|
||||||
from authentik.sources.saml.processors.response import ResponseProcessor
|
from authentik.sources.saml.processors.response import ResponseProcessor
|
||||||
|
|
||||||
|
POST_REQUEST = (
|
||||||
|
"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1sMn"
|
||||||
|
"A9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgQXNzZXJ0aW9uQ29uc3VtZXJTZXJ2aWNlVVJMPSJo"
|
||||||
|
"dHRwczovL2V1LWNlbnRyYWwtMS5zaWduaW4uYXdzLmFtYXpvbi5jb20vcGxhdGZvcm0vc2FtbC9hY3MvMmQ3MzdmOTYtNT"
|
||||||
|
"VmYi00MDM1LTk1M2UtNWUyNDEzNGViNzc4IiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9pZC5iZXJ5anUub3JnL2FwcGxpY2F0"
|
||||||
|
"aW9uL3NhbWwvYXdzLXNzby9zc28vYmluZGluZy9wb3N0LyIgSUQ9ImF3c19MRHhMR2V1YnBjNWx4MTJneENnUzZ1UGJpeD"
|
||||||
|
"F5ZDVyZSIgSXNzdWVJbnN0YW50PSIyMDIxLTA3LTA2VDE0OjIzOjA2LjM4OFoiIFByb3RvY29sQmluZGluZz0idXJuOm9h"
|
||||||
|
"c2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIH"
|
||||||
|
"htbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwczovL2V1LWNlbnRyYWwt"
|
||||||
|
"MS5zaWduaW4uYXdzLmFtYXpvbi5jb20vcGxhdGZvcm0vc2FtbC9kLTk5NjcyZjgyNzg8L3NhbWwyOklzc3Vlcj48c2FtbD"
|
||||||
|
"JwOk5hbWVJRFBvbGljeSBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWls"
|
||||||
|
"QWRkcmVzcyIvPjwvc2FtbDJwOkF1dGhuUmVxdWVzdD4="
|
||||||
|
)
|
||||||
REDIRECT_REQUEST = (
|
REDIRECT_REQUEST = (
|
||||||
"fZLNbsIwEIRfJfIdbKeFgEUipXAoEm0jSHvopTLJplhK7NTr9Oft6yRUKhekPdk73+yOdoWyqVuRdu6k9/DRAbrgu6k1iu"
|
"fZLNbsIwEIRfJfIdbKeFgEUipXAoEm0jSHvopTLJplhK7NTr9Oft6yRUKhekPdk73+yOdoWyqVuRdu6k9/DRAbrgu6k1iu"
|
||||||
"EjJp3VwkhUKLRsAIUrxCF92IlwykRrjTOFqUmQIoJ1yui10dg1YA9gP1UBz/tdTE7OtSgo5WzKQzYditGeP8GW9rSQZk+H"
|
"EjJp3VwkhUKLRsAIUrxCF92IlwykRrjTOFqUmQIoJ1yui10dg1YA9gP1UBz/tdTE7OtSgo5WzKQzYditGeP8GW9rSQZk+H"
|
||||||
|
@ -208,3 +224,22 @@ class TestAuthNRequest(TestCase):
|
||||||
self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab")
|
self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab")
|
||||||
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_UNSPECIFIED)
|
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_UNSPECIFIED)
|
||||||
self.assertEqual(parsed_request.relay_state, REDIRECT_RELAY_STATE)
|
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)
|
||||||
|
|
Reference in New Issue