From 1fa9b3a996ebcfaeb78b5b8dcc0c610bdbb31095 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Fri, 25 Nov 2022 11:21:45 +0100 Subject: [PATCH] providers/saml: set AuthnContextClassRef based on login event Signed-off-by: Jens Langhammer #4070 --- .../providers/saml/processors/assertion.py | 22 ++++++++++++++++--- .../stages/authenticator_validate/stage.py | 2 +- .../authenticator_validate/tests/test_duo.py | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/authentik/providers/saml/processors/assertion.py b/authentik/providers/saml/processors/assertion.py index c97f30e87..05f337ec4 100644 --- a/authentik/providers/saml/processors/assertion.py +++ b/authentik/providers/saml/processors/assertion.py @@ -10,6 +10,7 @@ from structlog.stdlib import get_logger from authentik.core.exceptions import PropertyMappingExpressionException from authentik.events.models import Event, EventAction +from authentik.events.signals import SESSION_LOGIN_EVENT from authentik.lib.utils.time import timedelta_from_string from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.processors.request_parser import AuthNRequest @@ -30,6 +31,7 @@ from authentik.sources.saml.processors.constants import ( SAML_NAME_ID_FORMAT_X509, SIGN_ALGORITHM_TRANSFORM_MAP, ) +from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS LOGGER = get_logger() @@ -129,9 +131,23 @@ class AssertionProcessor: auth_n_context_class_ref = SubElement( auth_n_context, f"{{{NS_SAML_ASSERTION}}}AuthnContextClassRef" ) - auth_n_context_class_ref.text = ( - "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" - ) + auth_n_context_class_ref.text = "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified" + if SESSION_LOGIN_EVENT in self.http_request.session: + event: Event = self.http_request.session[SESSION_LOGIN_EVENT] + method = event.context.get(PLAN_CONTEXT_METHOD, "") + method_args = event.context.get(PLAN_CONTEXT_METHOD_ARGS, {}) + if method == "password": + auth_n_context_class_ref.text = ( + "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" + ) + if "mfa_devices" in method_args: + auth_n_context_class_ref.text = ( + "urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract" + ) + if method in ["auth_mfa", "auth_webauthn_pwl"]: + auth_n_context_class_ref.text = ( + "urn:oasis:names:tc:SAML:2.0:ac:classes:MobileOneFactorContract" + ) return auth_n_statement def get_assertion_conditions(self) -> Element: diff --git a/authentik/stages/authenticator_validate/stage.py b/authentik/stages/authenticator_validate/stage.py index ca07e0303..29e6d2125 100644 --- a/authentik/stages/authenticator_validate/stage.py +++ b/authentik/stages/authenticator_validate/stage.py @@ -134,7 +134,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse): # Here we only check if the any data was sent at all if "code" not in attrs and "webauthn" not in attrs and "duo" not in attrs: raise ValidationError("Empty response") - self.stage.executor.plan.context.setdefault(PLAN_CONTEXT_METHOD, "mfa") + self.stage.executor.plan.context.setdefault(PLAN_CONTEXT_METHOD, "auth_mfa") self.stage.executor.plan.context.setdefault(PLAN_CONTEXT_METHOD_ARGS, {}) self.stage.executor.plan.context[PLAN_CONTEXT_METHOD_ARGS].setdefault("mfa_devices", []) self.stage.executor.plan.context[PLAN_CONTEXT_METHOD_ARGS]["mfa_devices"].append( diff --git a/authentik/stages/authenticator_validate/tests/test_duo.py b/authentik/stages/authenticator_validate/tests/test_duo.py index 3a18892a9..22d1fe746 100644 --- a/authentik/stages/authenticator_validate/tests/test_duo.py +++ b/authentik/stages/authenticator_validate/tests/test_duo.py @@ -169,7 +169,7 @@ class AuthenticatorValidateStageDuoTests(FlowTestCase): self.assertEqual( event.context, { - "auth_method": "mfa", + "auth_method": "auth_mfa", "auth_method_args": { "mfa_devices": [ {