core: show success message when authenticating/enrolling after flow is finished

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-10-25 22:46:15 +02:00
parent 2206b71f6f
commit dd65862bf2
1 changed files with 80 additions and 20 deletions

View File

@ -5,7 +5,7 @@ from typing import Any, Optional
from django.contrib import messages from django.contrib import messages
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models.query_utils import Q from django.db.models.query_utils import Q
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -23,8 +23,10 @@ from authentik.flows.planner import (
PLAN_CONTEXT_SSO, PLAN_CONTEXT_SSO,
FlowPlanner, FlowPlanner,
) )
from authentik.flows.stage import StageView
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.lib.views import bad_request_message
from authentik.policies.denied import AccessDeniedResponse from authentik.policies.denied import AccessDeniedResponse
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
@ -43,6 +45,34 @@ class Action(Enum):
DENY = "deny" DENY = "deny"
def message_stage(message: str, level: int) -> StageView:
"""Show a pre-configured message after the flow is done"""
class MessageStage(StageView):
"""Show a pre-configured message after the flow is done"""
message: str
level: int
# pylint: disable=unused-argument
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Show a pre-configured message after the flow is done"""
messages.add_message(
self.request,
self.level,
self.message,
)
return self.executor.stage_ok()
def post(self, request: HttpRequest) -> HttpResponse:
"""Wrapper for post requests"""
return self.get(request)
MessageStage.message = message
MessageStage.level = level
return MessageStage
class SourceFlowManager: class SourceFlowManager:
"""Help sources decide what they should do after authorization. Based on source settings and """Help sources decide what they should do after authorization. Based on source settings and
previous connections, authenticate the user, enroll a new user, link to an existing user previous connections, authenticate the user, enroll a new user, link to an existing user
@ -156,10 +186,10 @@ class SourceFlowManager:
if connection: if connection:
if action == Action.LINK: if action == Action.LINK:
self._logger.debug("Linking existing user") self._logger.debug("Linking existing user")
return self.handle_existing_user_link(connection) return self.handle_existing_link(connection)
if action == Action.AUTH: if action == Action.AUTH:
self._logger.debug("Handling auth user") self._logger.debug("Handling auth user")
return self.handle_auth_user(connection) return self.handle_auth(connection)
if action == Action.ENROLL: if action == Action.ENROLL:
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)
@ -199,7 +229,11 @@ class SourceFlowManager:
return [] return []
def _handle_login_flow( def _handle_login_flow(
self, flow: Flow, connection: UserSourceConnection, **kwargs self,
flow: Flow,
connection: UserSourceConnection,
stages: Optional[list[StageView]] = None,
**kwargs,
) -> HttpResponse: ) -> HttpResponse:
"""Prepare Authentication Plan, redirect user FlowExecutor""" """Prepare Authentication Plan, redirect user FlowExecutor"""
# Ensure redirect is carried through when user was trying to # Ensure redirect is carried through when user was trying to
@ -219,12 +253,18 @@ class SourceFlowManager:
) )
kwargs.update(self.policy_context) kwargs.update(self.policy_context)
if not flow: if not flow:
return HttpResponseBadRequest() return bad_request_message(
self.request,
_("Configured flow does not exist."),
)
# 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
planner = FlowPlanner(flow) planner = FlowPlanner(flow)
plan = planner.plan(self.request, kwargs) plan = planner.plan(self.request, kwargs)
for stage in self.get_stages_to_append(flow): for stage in self.get_stages_to_append(flow):
plan.append_stage(stage=stage) plan.append_stage(stage)
if stages:
for stage in stages:
plan.append_stage(stage)
self.request.session[SESSION_KEY_PLAN] = plan self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs( return redirect_with_qs(
"authentik_core:if-flow", "authentik_core:if-flow",
@ -233,19 +273,30 @@ class SourceFlowManager:
) )
# pylint: disable=unused-argument # pylint: disable=unused-argument
def handle_auth_user( def handle_auth(
self, self,
connection: UserSourceConnection, connection: UserSourceConnection,
) -> HttpResponse: ) -> HttpResponse:
"""Login user and redirect.""" """Login user and redirect."""
messages.success(
self.request,
_("Successfully authenticated with %(source)s!" % {"source": self.source.name}),
)
flow_kwargs = {PLAN_CONTEXT_PENDING_USER: connection.user} flow_kwargs = {PLAN_CONTEXT_PENDING_USER: connection.user}
return self._handle_login_flow(self.source.authentication_flow, connection, **flow_kwargs) return self._handle_login_flow(
self.source.authentication_flow,
connection,
stages=[
in_memory_stage(
message_stage(
messages.SUCCESS,
_(
"Successfully authenticated with %(source)s!"
% {"source": self.source.name}
),
)
)
],
**flow_kwargs,
)
def handle_existing_user_link( def handle_existing_link(
self, self,
connection: UserSourceConnection, connection: UserSourceConnection,
) -> HttpResponse: ) -> HttpResponse:
@ -263,7 +314,7 @@ class SourceFlowManager:
) )
# When request isn't authenticated we jump straight to auth # When request isn't authenticated we jump straight to auth
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
return self.handle_auth_user(connection) return self.handle_auth(connection)
return redirect( return redirect(
reverse( reverse(
"authentik_core:if-user", "authentik_core:if-user",
@ -276,18 +327,27 @@ class SourceFlowManager:
connection: UserSourceConnection, connection: UserSourceConnection,
) -> HttpResponse: ) -> HttpResponse:
"""User was not authenticated and previous request was not authenticated.""" """User was not authenticated and previous request was not authenticated."""
messages.success(
self.request,
_("Successfully authenticated with %(source)s!" % {"source": self.source.name}),
)
# 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
if not self.source.enrollment_flow: if not self.source.enrollment_flow:
self._logger.warning("source has no enrollment flow") self._logger.warning("source has no enrollment flow")
return HttpResponseBadRequest() return bad_request_message(
self.request,
_("Source is not configured for enrollment."),
)
return self._handle_login_flow( return self._handle_login_flow(
self.source.enrollment_flow, self.source.enrollment_flow,
connection, connection,
stages=[
in_memory_stage(
message_stage(
messages.SUCCESS,
_(
"Successfully authenticated with %(source)s!"
% {"source": self.source.name}
),
)
)
],
**{ **{
PLAN_CONTEXT_PROMPT: delete_none_keys(self.enroll_info), PLAN_CONTEXT_PROMPT: delete_none_keys(self.enroll_info),
PLAN_CONTEXT_USER_PATH: self.source.get_user_path(), PLAN_CONTEXT_USER_PATH: self.source.get_user_path(),