diff --git a/authentik/flows/stage.py b/authentik/flows/stage.py index 6fd2d783e..78964fb47 100644 --- a/authentik/flows/stage.py +++ b/authentik/flows/stage.py @@ -1,6 +1,5 @@ """authentik stage Base view""" -from collections import namedtuple -from typing import Any +from typing import Any, Optional from django.http import HttpRequest from django.http.request import QueryDict @@ -9,6 +8,7 @@ from django.utils.translation import gettext_lazy as _ from django.views.generic import TemplateView from structlog.stdlib import get_logger +from authentik.core.models import User from authentik.flows.challenge import ( Challenge, ChallengeResponse, @@ -20,8 +20,6 @@ from authentik.flows.views import FlowExecutorView PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier" LOGGER = get_logger() -FakeUser = namedtuple("User", ["username", "email"]) - class StageView(TemplateView): """Abstract Stage, inherits TemplateView but can be combined with FormView""" @@ -44,7 +42,7 @@ class StageView(TemplateView): if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context: kwargs["user"] = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] if PLAN_CONTEXT_PENDING_USER_IDENTIFIER in self.executor.plan.context: - kwargs["user"] = FakeUser( + kwargs["user"] = User( username=self.executor.plan.context.get( PLAN_CONTEXT_PENDING_USER_IDENTIFIER ), @@ -59,6 +57,22 @@ class ChallengeStageView(StageView): response_class = ChallengeResponse + def get_pending_user(self) -> Optional[User]: + """Either show the matched User object or show what the user entered, + based on what the earlier stage (mostly IdentificationStage) set. + _USER_IDENTIFIER overrides the first User, as PENDING_USER is used for + other things besides the form display""" + if PLAN_CONTEXT_PENDING_USER_IDENTIFIER in self.executor.plan.context: + return User( + username=self.executor.plan.context.get( + PLAN_CONTEXT_PENDING_USER_IDENTIFIER + ), + email="", + ) + if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context: + return self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] + return None + def get_response_instance(self, data: QueryDict) -> ChallengeResponse: """Return the response class type""" return self.response_class(None, data=data, stage=self) diff --git a/authentik/stages/consent/stage.py b/authentik/stages/consent/stage.py index 9513ffaa0..e4502adfc 100644 --- a/authentik/stages/consent/stage.py +++ b/authentik/stages/consent/stage.py @@ -3,7 +3,6 @@ from django.http import HttpRequest, HttpResponse from django.utils.timezone import now from rest_framework.fields import CharField -from authentik.core.models import User from authentik.flows.challenge import ( Challenge, ChallengeResponse, @@ -55,10 +54,9 @@ class ConsentStageView(ChallengeStageView): # If there's a pending user, update the `username` field # this field is only used by password managers. # If there's no user set, an error is raised later. - if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context: - pending_user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] - challenge.initial_data["pending_user"] = pending_user.username - challenge.initial_data["pending_user_avatar"] = avatar(pending_user) + if user := self.get_pending_user(): + challenge.initial_data["pending_user"] = user.username + challenge.initial_data["pending_user_avatar"] = avatar(user) return challenge def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: diff --git a/authentik/stages/password/stage.py b/authentik/stages/password/stage.py index 3a8557f29..6311f332f 100644 --- a/authentik/stages/password/stage.py +++ b/authentik/stages/password/stage.py @@ -86,10 +86,9 @@ class PasswordStageView(ChallengeStageView): # If there's a pending user, update the `username` field # this field is only used by password managers. # If there's no user set, an error is raised later. - if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context: - pending_user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] - challenge.initial_data["pending_user"] = pending_user.username - challenge.initial_data["pending_user_avatar"] = avatar(pending_user) + if user := self.get_pending_user(): + challenge.initial_data["pending_user"] = user.username + challenge.initial_data["pending_user_avatar"] = avatar(user) recovery_flow = Flow.objects.filter(designation=FlowDesignation.RECOVERY) if recovery_flow.exists(): diff --git a/swagger.yaml b/swagger.yaml index b2942e888..38bd48b94 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -9657,22 +9657,22 @@ definitions: - authentik.sources.ldap - authentik.sources.oauth - authentik.sources.saml - - authentik.stages.captcha - - authentik.stages.consent - - authentik.stages.dummy - - authentik.stages.email - - authentik.stages.prompt - - authentik.stages.identification - - authentik.stages.invitation - - authentik.stages.user_delete - - authentik.stages.user_login - - authentik.stages.user_logout - - authentik.stages.user_write - authentik.stages.authenticator_static - authentik.stages.authenticator_totp - authentik.stages.authenticator_validate - authentik.stages.authenticator_webauthn + - authentik.stages.captcha + - authentik.stages.consent + - authentik.stages.dummy + - authentik.stages.email + - authentik.stages.identification + - authentik.stages.invitation - authentik.stages.password + - authentik.stages.prompt + - authentik.stages.user_delete + - authentik.stages.user_login + - authentik.stages.user_logout + - authentik.stages.user_write - authentik.managed - authentik.core ExpressionPolicy: @@ -11742,8 +11742,8 @@ definitions: - password - number - checkbox - - data - - data-time + - date + - date-time - separator - hidden - static