flows: show messages from ak_message when flow is denied

fallback to same generic message

closes #3197

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-07-03 21:36:13 +02:00
parent 23c1e22a04
commit 14a4047bdd
7 changed files with 19 additions and 10 deletions

View file

@ -165,7 +165,7 @@ class SourceFlowManager:
self._logger.debug("Handling enrollment of new user") self._logger.debug("Handling enrollment of new user")
return self.handle_enroll(connection) return self.handle_enroll(connection)
except FlowNonApplicableException as exc: except FlowNonApplicableException as exc:
self._logger.warning("Flow non applicable", exc=exc) self._logger.warning("Flow non applicable", exc=exc, result=exc.policy_result)
return self.error_handler(exc, exc.policy_result) return self.error_handler(exc, exc.policy_result)
# Default case, assume deny # Default case, assume deny
error = ( error = (

View file

@ -372,7 +372,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
request, request,
_( _(
"Flow not applicable to current user/request: %(messages)s" "Flow not applicable to current user/request: %(messages)s"
% {"messages": str(exc)} % {"messages": exc.messages}
), ),
) )
return Response( return Response(

View file

@ -1,4 +1,5 @@
"""flow exceptions""" """flow exceptions"""
from django.utils.translation import gettext_lazy as _
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
from authentik.policies.types import PolicyResult from authentik.policies.types import PolicyResult
@ -9,6 +10,13 @@ class FlowNonApplicableException(SentryIgnoredException):
policy_result: PolicyResult policy_result: PolicyResult
@property
def messages(self) -> str:
"""Get messages from policy result, fallback to generic reason"""
if len(self.policy_result.messages) < 1:
return _("Flow does not apply to current user (denied by policy).")
return "\n".join(self.policy_result.messages)
class EmptyFlowException(SentryIgnoredException): class EmptyFlowException(SentryIgnoredException):
"""Flow has no stages.""" """Flow has no stages."""

View file

@ -147,7 +147,7 @@ class FlowPlanner:
engine.build() engine.build()
result = engine.result result = engine.result
if not result.passing: if not result.passing:
exc = FlowNonApplicableException(",".join(result.messages)) exc = FlowNonApplicableException()
exc.policy_result = result exc.policy_result = result
raise exc 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

View file

@ -7,7 +7,6 @@ from django.urls import reverse
from authentik.core.models import User from authentik.core.models import User
from authentik.core.tests.utils import create_test_flow from authentik.core.tests.utils import create_test_flow
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import ( from authentik.flows.models import (
FlowDeniedAction, FlowDeniedAction,
@ -29,7 +28,7 @@ from authentik.stages.deny.models import DenyStage
from authentik.stages.dummy.models import DummyStage from authentik.stages.dummy.models import DummyStage
from authentik.stages.identification.models import IdentificationStage, UserFields from authentik.stages.identification.models import IdentificationStage, UserFields
POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False)) POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False, "foo"))
POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True)) POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True))
@ -93,7 +92,7 @@ class TestFlowExecutor(FlowTestCase):
self.assertStageResponse( self.assertStageResponse(
response, response,
flow=flow, flow=flow,
error_message=FlowNonApplicableException.__doc__, error_message="foo",
component="ak-stage-access-denied", component="ak-stage-access-denied",
) )

View file

@ -132,7 +132,7 @@ class FlowExecutorView(APIView):
self._logger = get_logger().bind(flow_slug=flow_slug) self._logger = get_logger().bind(flow_slug=flow_slug)
set_tag("authentik.flow", self.flow.slug) set_tag("authentik.flow", self.flow.slug)
def handle_invalid_flow(self, exc: BaseException) -> HttpResponse: def handle_invalid_flow(self, exc: FlowNonApplicableException) -> HttpResponse:
"""When a flow is non-applicable check if user is on the correct domain""" """When a flow is non-applicable check if user is on the correct domain"""
if self.flow.denied_action in [ if self.flow.denied_action in [
FlowDeniedAction.CONTINUE, FlowDeniedAction.CONTINUE,
@ -146,8 +146,7 @@ class FlowExecutorView(APIView):
return to_stage_response( return to_stage_response(
self.request, redirect(reverse("authentik_core:root-redirect")) self.request, redirect(reverse("authentik_core:root-redirect"))
) )
message = exc.__doc__ if exc.__doc__ else str(exc) return to_stage_response(self.request, self.stage_invalid(error_message=exc.messages))
return to_stage_response(self.request, self.stage_invalid(error_message=message))
def _check_flow_token(self, key: str) -> Optional[FlowPlan]: def _check_flow_token(self, key: str) -> Optional[FlowPlan]:
"""Check if the user is using a flow token to restore a plan""" """Check if the user is using a flow token to restore a plan"""

View file

@ -143,7 +143,10 @@ class OAuth2Provider(Provider):
choices=ClientTypes.choices, choices=ClientTypes.choices,
default=ClientTypes.CONFIDENTIAL, default=ClientTypes.CONFIDENTIAL,
verbose_name=_("Client Type"), verbose_name=_("Client Type"),
help_text=_(ClientTypes.__doc__), help_text=_(
"Confidential clients are capable of maintaining the confidentiality "
"of their credentials. Public clients are incapable"
),
) )
client_id = models.CharField( client_id = models.CharField(
max_length=255, max_length=255,