flows: fix redirect when un-authenticated user uses external authentication (#416)

* flows: add PLAN_CONTEXT_REDIRECT so final redirect can be set from within flow

* sources/*: use PLAN_CONTEXT_REDIRECT

* flows: fallback when flow plan is empty
This commit is contained in:
Jens L 2020-12-19 16:42:39 +01:00 committed by GitHub
parent 98a58b74e3
commit 6e24856d45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 6 deletions

View file

@ -19,6 +19,7 @@ LOGGER = get_logger()
PLAN_CONTEXT_PENDING_USER = "pending_user" PLAN_CONTEXT_PENDING_USER = "pending_user"
PLAN_CONTEXT_SSO = "is_sso" PLAN_CONTEXT_SSO = "is_sso"
PLAN_CONTEXT_REDIRECT = "redirect"
PLAN_CONTEXT_APPLICATION = "application" PLAN_CONTEXT_APPLICATION = "application"

View file

@ -21,7 +21,12 @@ from authentik.audit.models import cleanse_dict
from authentik.core.models import USER_ATTRIBUTE_DEBUG from authentik.core.models import USER_ATTRIBUTE_DEBUG
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.models import ConfigurableStage, Flow, FlowDesignation, Stage from authentik.flows.models import ConfigurableStage, Flow, FlowDesignation, Stage
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan, FlowPlanner from authentik.flows.planner import (
PLAN_CONTEXT_PENDING_USER,
PLAN_CONTEXT_REDIRECT,
FlowPlan,
FlowPlanner,
)
from authentik.lib.utils.reflection import class_to_path from authentik.lib.utils.reflection import class_to_path
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
from authentik.policies.http import AccessDeniedResponse from authentik.policies.http import AccessDeniedResponse
@ -145,6 +150,10 @@ class FlowExecutorView(View):
"""User Successfully passed all stages""" """User Successfully passed all stages"""
# Since this is wrapped by the ExecutorShell, the next argument is saved in the session # Since this is wrapped by the ExecutorShell, the next argument is saved in the session
# extract the next param before cancel as that cleans it # extract the next param before cancel as that cleans it
next_param = None
if self.plan:
next_param = self.plan.context.get(PLAN_CONTEXT_REDIRECT)
if not next_param:
next_param = self.request.session.get(SESSION_KEY_GET, {}).get( next_param = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:shell" NEXT_ARG_NAME, "authentik_core:shell"
) )

View file

@ -15,10 +15,11 @@ from authentik.core.models import User
from authentik.flows.models import Flow, in_memory_stage from authentik.flows.models import Flow, in_memory_stage
from authentik.flows.planner import ( from authentik.flows.planner import (
PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_PENDING_USER,
PLAN_CONTEXT_REDIRECT,
PLAN_CONTEXT_SSO, PLAN_CONTEXT_SSO,
FlowPlanner, FlowPlanner,
) )
from authentik.flows.views import SESSION_KEY_PLAN from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.utils import delete_none_keys from authentik.policies.utils import delete_none_keys
from authentik.sources.oauth.auth import AuthorizedServiceBackend from authentik.sources.oauth.auth import AuthorizedServiceBackend
@ -135,11 +136,17 @@ class OAuthCallback(OAuthClientMixin, View):
def handle_login_flow(self, flow: Flow, **kwargs) -> HttpResponse: def handle_login_flow(self, flow: Flow, **kwargs) -> HttpResponse:
"""Prepare Authentication Plan, redirect user FlowExecutor""" """Prepare Authentication Plan, redirect user FlowExecutor"""
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:shell"
)
kwargs.update( kwargs.update(
{ {
# Since we authenticate the user by their token, they have no backend set # Since we authenticate the user by their token, they have no backend set
PLAN_CONTEXT_AUTHENTICATION_BACKEND: "django.contrib.auth.backends.ModelBackend", PLAN_CONTEXT_AUTHENTICATION_BACKEND: "django.contrib.auth.backends.ModelBackend",
PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_SSO: True,
PLAN_CONTEXT_REDIRECT: final_redirect,
} }
) )
# We run the Flow planner here so we can pass the Pending user in the context # We run the Flow planner here so we can pass the Pending user in the context

View file

@ -13,10 +13,11 @@ from authentik.core.models import User
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.flows.planner import ( from authentik.flows.planner import (
PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_PENDING_USER,
PLAN_CONTEXT_REDIRECT,
PLAN_CONTEXT_SSO, PLAN_CONTEXT_SSO,
FlowPlanner, FlowPlanner,
) )
from authentik.flows.views import SESSION_KEY_PLAN from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.utils import delete_none_keys from authentik.policies.utils import delete_none_keys
from authentik.sources.saml.exceptions import ( from authentik.sources.saml.exceptions import (
@ -54,11 +55,14 @@ class ResponseProcessor:
_root: Any _root: Any
_root_xml: str _root_xml: str
_http_request: HttpRequest
def __init__(self, source: SAMLSource): def __init__(self, source: SAMLSource):
self._source = source self._source = source
def parse(self, request: HttpRequest): def parse(self, request: HttpRequest):
"""Check if `request` contains SAML Response data, parse and validate it.""" """Check if `request` contains SAML Response data, parse and validate it."""
self._http_request = request
# First off, check if we have any SAML Data at all. # First off, check if we have any SAML Data at all.
raw_response = request.POST.get("SAMLResponse", None) raw_response = request.POST.get("SAMLResponse", None)
if not raw_response: if not raw_response:
@ -187,6 +191,11 @@ class ResponseProcessor:
name_id_filter = self._get_name_id_filter() name_id_filter = self._get_name_id_filter()
matching_users = User.objects.filter(**name_id_filter) matching_users = User.objects.filter(**name_id_filter)
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self._http_request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:shell"
)
if matching_users.exists(): if matching_users.exists():
# User exists already, switch to authentication flow # User exists already, switch to authentication flow
return self._flow_response( return self._flow_response(
@ -195,6 +204,7 @@ class ResponseProcessor:
**{ **{
PLAN_CONTEXT_PENDING_USER: matching_users.first(), PLAN_CONTEXT_PENDING_USER: matching_users.first(),
PLAN_CONTEXT_AUTHENTICATION_BACKEND: DEFAULT_BACKEND, PLAN_CONTEXT_AUTHENTICATION_BACKEND: DEFAULT_BACKEND,
PLAN_CONTEXT_REDIRECT: final_redirect,
}, },
) )
return self._flow_response( return self._flow_response(