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:
parent
98a58b74e3
commit
6e24856d45
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,9 +150,13 @@ 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 = self.request.session.get(SESSION_KEY_GET, {}).get(
|
next_param = None
|
||||||
NEXT_ARG_NAME, "authentik_core:shell"
|
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_ARG_NAME, "authentik_core:shell"
|
||||||
|
)
|
||||||
self.cancel()
|
self.cancel()
|
||||||
return to_stage_response(self.request, redirect_with_qs(next_param))
|
return to_stage_response(self.request, redirect_with_qs(next_param))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Reference in a new issue