diff --git a/authentik/flows/views/executor.py b/authentik/flows/views/executor.py index a1a4691ae..82707e9fd 100644 --- a/authentik/flows/views/executor.py +++ b/authentik/flows/views/executor.py @@ -53,6 +53,7 @@ NEXT_ARG_NAME = "next" SESSION_KEY_PLAN = "authentik_flows_plan" SESSION_KEY_APPLICATION_PRE = "authentik_flows_application_pre" SESSION_KEY_GET = "authentik_flows_get" +SESSION_KEY_POST = "authentik_flows_post" SESSION_KEY_HISTORY = "authentik_flows_history" diff --git a/authentik/policies/views.py b/authentik/policies/views.py index c45cc9943..192183017 100644 --- a/authentik/policies/views.py +++ b/authentik/policies/views.py @@ -10,7 +10,7 @@ from django.views.generic.base import View from structlog.stdlib import get_logger from authentik.core.models import Application, Provider, User -from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE +from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_POST from authentik.lib.sentry import SentryIgnoredException from authentik.policies.denied import AccessDeniedResponse from authentik.policies.engine import PolicyEngine @@ -84,6 +84,10 @@ class PolicyAccessView(AccessMixin, View): a hint on the Identification Stage what the user should login for.""" if self.application: self.request.session[SESSION_KEY_APPLICATION_PRE] = self.application + # Because this view might get hit with a POST request, we need to preserve that data + # since later views might need it (mostly SAML) + if self.request.method.lower() == "post": + self.request.session[SESSION_KEY_POST] = self.request.POST return redirect_to_login( self.request.get_full_path(), self.get_login_url(), diff --git a/authentik/providers/saml/views/sso.py b/authentik/providers/saml/views/sso.py index 3e470b087..4a534c09d 100644 --- a/authentik/providers/saml/views/sso.py +++ b/authentik/providers/saml/views/sso.py @@ -13,7 +13,7 @@ from authentik.core.models import Application from authentik.events.models import Event, EventAction from authentik.flows.models import in_memory_stage from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner -from authentik.flows.views.executor import SESSION_KEY_PLAN +from authentik.flows.views.executor import SESSION_KEY_PLAN, SESSION_KEY_POST from authentik.lib.utils.urls import redirect_with_qs from authentik.lib.views import bad_request_message from authentik.policies.views import PolicyAccessView @@ -37,7 +37,7 @@ LOGGER = get_logger() class SAMLSSOView(PolicyAccessView): - """ "SAML SSO Base View, which plans a flow and injects our final stage. + """SAML SSO Base View, which plans a flow and injects our final stage. Calls get/post handler.""" def resolve_provider_application(self): @@ -120,14 +120,20 @@ class SAMLSSOBindingPOSTView(SAMLSSOView): def check_saml_request(self) -> Optional[HttpRequest]: """Handle POST bindings""" - if REQUEST_KEY_SAML_REQUEST not in self.request.POST: + payload = self.request.POST + # Restore the post body from the session + # This happens when using POST bindings but the user isn't logged in + # (user gets redirected and POST body is 'lost') + if SESSION_KEY_POST in self.request.session: + payload = self.request.session[SESSION_KEY_POST] + if REQUEST_KEY_SAML_REQUEST not in payload: LOGGER.info("check_saml_request: SAML payload missing") return bad_request_message(self.request, "The SAML request payload is missing.") try: auth_n_request = AuthNRequestParser(self.provider).parse( - self.request.POST[REQUEST_KEY_SAML_REQUEST], - self.request.POST.get(REQUEST_KEY_RELAY_STATE), + payload[REQUEST_KEY_SAML_REQUEST], + payload.get(REQUEST_KEY_RELAY_STATE), ) self.request.session[SESSION_KEY_AUTH_N_REQUEST] = auth_n_request except CannotHandleAssertion as exc: