core: add error handling in source flow manager when flow isn't applicable

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-01-03 21:57:55 +01:00
parent e434321f7c
commit 7d3d17acb9
3 changed files with 35 additions and 14 deletions

View File

@ -14,6 +14,7 @@ from structlog.stdlib import get_logger
from authentik.core.models import Source, SourceUserMatchingModes, User, UserSourceConnection from authentik.core.models import Source, SourceUserMatchingModes, User, UserSourceConnection
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION, PostUserEnrollmentStage from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION, PostUserEnrollmentStage
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow, Stage, in_memory_stage from authentik.flows.models import Flow, Stage, in_memory_stage
from authentik.flows.planner import ( from authentik.flows.planner import (
PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_PENDING_USER,
@ -24,6 +25,8 @@ from authentik.flows.planner import (
) )
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN from authentik.flows.views.executor 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.denied import AccessDeniedResponse
from authentik.policies.types import PolicyResult
from authentik.policies.utils import delete_none_keys from authentik.policies.utils import delete_none_keys
from authentik.stages.password import BACKEND_INBUILT from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
@ -149,19 +152,22 @@ class SourceFlowManager:
self._logger.warning("failed to get action", exc=exc) self._logger.warning("failed to get action", exc=exc)
return redirect("/") return redirect("/")
self._logger.debug("get_action", action=action, connection=connection) self._logger.debug("get_action", action=action, connection=connection)
if connection: try:
if action == Action.LINK: if connection:
self._logger.debug("Linking existing user") if action == Action.LINK:
return self.handle_existing_user_link(connection) self._logger.debug("Linking existing user")
if action == Action.AUTH: return self.handle_existing_user_link(connection)
self._logger.debug("Handling auth user") if action == Action.AUTH:
return self.handle_auth_user(connection) self._logger.debug("Handling auth user")
if action == Action.ENROLL: return self.handle_auth_user(connection)
self._logger.debug("Handling enrollment of new user") if action == Action.ENROLL:
return self.handle_enroll(connection) self._logger.debug("Handling enrollment of new user")
return self.handle_enroll(connection)
except FlowNonApplicableException as exc:
self._logger.warning("Flow non applicable", exc=exc)
return self.error_handler(exc, exc.policy_result)
# Default case, assume deny # Default case, assume deny
messages.error( error = (
self.request,
_( _(
( (
"Request to authenticate with %(source)s has been denied. Please authenticate " "Request to authenticate with %(source)s has been denied. Please authenticate "
@ -170,7 +176,17 @@ class SourceFlowManager:
% {"source": self.source.name} % {"source": self.source.name}
), ),
) )
return redirect(reverse("authentik_core:root-redirect")) return self.error_handler(error)
def error_handler(
self, error: Exception, policy_result: Optional[PolicyResult] = None
) -> HttpResponse:
"""Handle any errors by returning an access denied stage"""
response = AccessDeniedResponse(self.request)
response.error_message = str(error)
if policy_result:
response.policy_result = policy_result
return response
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get_stages_to_append(self, flow: Flow) -> list[Stage]: def get_stages_to_append(self, flow: Flow) -> list[Stage]:

View File

@ -1,11 +1,14 @@
"""flow exceptions""" """flow exceptions"""
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
from authentik.policies.types import PolicyResult
class FlowNonApplicableException(SentryIgnoredException): class FlowNonApplicableException(SentryIgnoredException):
"""Flow does not apply to current user (denied by policy).""" """Flow does not apply to current user (denied by policy)."""
policy_result: PolicyResult
class EmptyFlowException(SentryIgnoredException): class EmptyFlowException(SentryIgnoredException):
"""Flow has no stages.""" """Flow has no stages."""

View File

@ -152,7 +152,9 @@ class FlowPlanner:
engine.build() engine.build()
result = engine.result result = engine.result
if not result.passing: if not result.passing:
raise FlowNonApplicableException(",".join(result.messages)) exc = FlowNonApplicableException(",".join(result.messages))
exc.policy_result = result
raise exc
# User is passing so far, check if we have a cached plan # User is passing so far, check if we have a cached plan
cached_plan_key = cache_key(self.flow, user) cached_plan_key = cache_key(self.flow, user)
cached_plan = cache.get(cached_plan_key, None) cached_plan = cache.get(cached_plan_key, None)