*: remove unused templates and code, move avatar to User model

This commit is contained in:
Jens Langhammer 2021-02-25 15:20:59 +01:00
parent cf7e7c44ff
commit 890e0e9054
21 changed files with 108 additions and 215 deletions

View file

@ -2,28 +2,20 @@
from drf_yasg2.utils import swagger_auto_schema from drf_yasg2.utils import swagger_auto_schema
from guardian.utils import get_anonymous_user from guardian.utils import get_anonymous_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ( from rest_framework.serializers import BooleanField, ModelSerializer
BooleanField,
ModelSerializer,
SerializerMethodField,
)
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.models import User from authentik.core.models import User
from authentik.lib.templatetags.authentik_utils import avatar
class UserSerializer(ModelSerializer): class UserSerializer(ModelSerializer):
"""User Serializer""" """User Serializer"""
is_superuser = BooleanField(read_only=True) is_superuser = BooleanField(read_only=True)
avatar = SerializerMethodField() avatar = CharField(read_only=True)
def get_avatar(self, user: User) -> str:
"""Add user's avatar as URL"""
return avatar(user)
class Meta: class Meta:

View file

@ -1,7 +1,8 @@
"""authentik core models""" """authentik core models"""
from datetime import timedelta from datetime import timedelta
from hashlib import sha256 from hashlib import md5, sha256
from typing import Any, Optional, Type from typing import Any, Optional, Type
from urllib.parse import urlencode
from uuid import uuid4 from uuid import uuid4
from django.conf import settings from django.conf import settings
@ -11,7 +12,9 @@ from django.db import models
from django.db.models import Q, QuerySet from django.db.models import Q, QuerySet
from django.forms import ModelForm from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.templatetags.static import static
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from guardian.mixins import GuardianUserMixin from guardian.mixins import GuardianUserMixin
@ -23,6 +26,7 @@ from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.core.signals import password_changed from authentik.core.signals import password_changed
from authentik.core.types import UILoginButton from authentik.core.types import UILoginButton
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.lib.config import CONFIG
from authentik.lib.models import CreatedUpdatedModel, SerializerModel from authentik.lib.models import CreatedUpdatedModel, SerializerModel
from authentik.managed.models import ManagedModel from authentik.managed.models import ManagedModel
from authentik.policies.models import PolicyBindingModel from authentik.policies.models import PolicyBindingModel
@ -31,6 +35,9 @@ LOGGER = get_logger()
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug" USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account" USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account"
GRAVATAR_URL = "https://secure.gravatar.com"
DEFAULT_AVATAR = static("authentik/user_default.png")
def default_token_duration(): def default_token_duration():
"""Default duration a Token is valid""" """Default duration a Token is valid"""
@ -126,6 +133,25 @@ class User(GuardianUserMixin, AbstractUser):
"""Generate a globall unique UID, based on the user ID and the hashed secret key""" """Generate a globall unique UID, based on the user ID and the hashed secret key"""
return sha256(f"{self.id}-{settings.SECRET_KEY}".encode("ascii")).hexdigest() return sha256(f"{self.id}-{settings.SECRET_KEY}".encode("ascii")).hexdigest()
@property
def avatar(self) -> str:
"""Get avatar, depending on authentik.avatar setting"""
mode = CONFIG.raw.get("authentik").get("avatars")
if mode == "none":
return DEFAULT_AVATAR
if mode == "gravatar":
parameters = [
("s", "158"),
("r", "g"),
]
# gravatar uses md5 for their URLs, so md5 can't be avoided
mail_hash = md5(self.email.encode("utf-8")).hexdigest() # nosec
gravatar_url = (
f"{GRAVATAR_URL}/avatar/{mail_hash}?{urlencode(parameters, doseq=True)}"
)
return escape(gravatar_url)
raise ValueError(f"Invalid avatar mode {mode}")
class Meta: class Meta:
permissions = ( permissions = (

View file

@ -1,19 +0,0 @@
{% extends 'login/base.html' %}
{% load static %}
{% load i18n %}
{% block card %}
<form method="POST" class="pf-c-form">
{% block above_form %}
{% endblock %}
{% include 'partials/form.html' %}
{% block beneath_form %}
{% endblock %}
<div class="pf-c-form__group pf-m-action">
<button class="pf-c-button pf-m-primary pf-m-block" type="submit">{% trans primary_action %}</button>
</div>
</form>
{% endblock %}

View file

@ -1,18 +0,0 @@
{% extends 'login/form.html' %}
{% load i18n %}
{% load authentik_utils %}
{% block above_form %}
<div class="pf-c-form__group">
<div class="form-control-static">
<div class="left">
<img class="pf-c-avatar" src="{% avatar user %}" alt="">
{{ user.username }}
</div>
<div class="right">
<a href="{% url 'authentik_flows:cancel' %}">{% trans 'Not you?' %}</a>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,24 +0,0 @@
{% extends 'login/base.html' %}
{% load static %}
{% load i18n %}
{% load authentik_utils %}
{% block title %}
{% trans title %}
{% endblock %}
{% block head %}
<meta http-equiv="refresh" content="0; url={{ target_url }}" />
{% endblock %}
{% block card %}
<header class="login-pf-header">
<h1>{% trans title %}</h1>
</header>
<form>
<div class="form-group">
<div class="spinner spinner-lg"></div>
</div>
</form>
{% endblock %}

View file

@ -1,14 +1,12 @@
"""authentik stage Base view""" """authentik stage Base view"""
from typing import Any, Optional from django.contrib.auth.models import AnonymousUser
from django.http import HttpRequest from django.http import HttpRequest
from django.http.request import QueryDict from django.http.request import QueryDict
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.utils.translation import gettext_lazy as _ from django.views.generic.base import View
from django.views.generic import TemplateView
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import User from authentik.core.models import DEFAULT_AVATAR, User
from authentik.flows.challenge import ( from authentik.flows.challenge import (
Challenge, Challenge,
ChallengeResponse, ChallengeResponse,
@ -17,53 +15,29 @@ from authentik.flows.challenge import (
) )
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.views import FlowExecutorView from authentik.flows.views import FlowExecutorView
from authentik.lib.templatetags.authentik_utils import avatar
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier" PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
LOGGER = get_logger() LOGGER = get_logger()
class StageView(TemplateView): class StageView(View):
"""Abstract Stage, inherits TemplateView but can be combined with FormView""" """Abstract Stage, inherits TemplateView but can be combined with FormView"""
template_name = "login/form_with_user.html"
executor: FlowExecutorView executor: FlowExecutorView
request: HttpRequest = None request: HttpRequest = None
def __init__(self, executor: FlowExecutorView): def __init__(self, executor: FlowExecutorView, **kwargs):
self.executor = executor self.executor = executor
super().__init__(**kwargs)
def get_context_data(self, **kwargs: Any) -> dict[str, Any]: def get_pending_user(self) -> User:
kwargs["title"] = self.executor.flow.title
# 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 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"] = User(
username=self.executor.plan.context.get(
PLAN_CONTEXT_PENDING_USER_IDENTIFIER
),
email="",
)
kwargs["primary_action"] = _("Continue")
return super().get_context_data(**kwargs)
class ChallengeStageView(StageView):
"""Stage view which response with a challenge"""
response_class = ChallengeResponse
def get_pending_user(self) -> Optional[User]:
"""Either show the matched User object or show what the user entered, """Either show the matched User object or show what the user entered,
based on what the earlier stage (mostly IdentificationStage) set. based on what the earlier stage (mostly IdentificationStage) set.
_USER_IDENTIFIER overrides the first User, as PENDING_USER is used for _USER_IDENTIFIER overrides the first User, as PENDING_USER is used for
other things besides the form display""" other things besides the form display.
If no user is pending, returns request.user"""
if PLAN_CONTEXT_PENDING_USER_IDENTIFIER in self.executor.plan.context: if PLAN_CONTEXT_PENDING_USER_IDENTIFIER in self.executor.plan.context:
return User( return User(
username=self.executor.plan.context.get( username=self.executor.plan.context.get(
@ -73,13 +47,20 @@ class ChallengeStageView(StageView):
) )
if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context: if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context:
return self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] return self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
return None return self.request.user
class ChallengeStageView(StageView):
"""Stage view which response with a challenge"""
response_class = ChallengeResponse
def get_response_instance(self, data: QueryDict) -> ChallengeResponse: def get_response_instance(self, data: QueryDict) -> ChallengeResponse:
"""Return the response class type""" """Return the response class type"""
return self.response_class(None, data=data, stage=self) return self.response_class(None, data=data, stage=self)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Return a challenge for the frontend to solve"""
challenge = self._get_challenge(*args, **kwargs) challenge = self._get_challenge(*args, **kwargs)
if not challenge.is_valid(): if not challenge.is_valid():
LOGGER.warning(challenge.errors) LOGGER.warning(challenge.errors)
@ -103,7 +84,9 @@ class ChallengeStageView(StageView):
# If there's no user set, an error is raised later. # If there's no user set, an error is raised later.
if user := self.get_pending_user(): if user := self.get_pending_user():
challenge.initial_data["pending_user"] = user.username challenge.initial_data["pending_user"] = user.username
challenge.initial_data["pending_user_avatar"] = avatar(user) challenge.initial_data["pending_user_avatar"] = DEFAULT_AVATAR
if not isinstance(user, AnonymousUser):
challenge.initial_data["pending_user_avatar"] = user.avatar
return challenge return challenge
def get_challenge(self, *args, **kwargs) -> Challenge: def get_challenge(self, *args, **kwargs) -> Challenge:

View file

@ -8,6 +8,7 @@ from django.urls import reverse
from django.utils.encoding import force_str from django.utils.encoding import force_str
from authentik.core.models import User from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.exceptions import FlowNonApplicableException 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 Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
@ -404,7 +405,14 @@ class TestFlowExecutor(TestCase):
# First request, run the planner # First request, run the planner
response = self.client.get(exec_url) response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("dummy1", force_str(response.content)) self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.native.value,
"component": "",
"title": binding.stage.name,
},
)
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
@ -427,7 +435,14 @@ class TestFlowExecutor(TestCase):
# but it won't save it, hence we cant' check the plan # but it won't save it, hence we cant' check the plan
response = self.client.get(exec_url) response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("dummy4", force_str(response.content)) self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.native.value,
"component": "",
"title": binding4.stage.name,
},
)
# fourth request, this confirms the last stage (dummy4) # fourth request, this confirms the last stage (dummy4)
# We do this request without the patch, so the policy results in false # We do this request without the patch, so the policy results in false
@ -466,4 +481,4 @@ class TestFlowExecutor(TestCase):
executor.flow = flow executor.flow = flow
stage_view = StageView(executor) stage_view = StageView(executor)
self.assertEqual(ident, stage_view.get_context_data()["user"].username) self.assertEqual(ident, stage_view.get_pending_user().username)

View file

@ -1,17 +0,0 @@
{% load authentik_utils %}
{% spaceless %}
<div class="dynamic-array-widget">
{% for widget in widget.subwidgets %}
<div class="array-item input-group">
{% include widget.template_name %}
<div class="input-group-btn">
<button class="array-remove btn btn-danger" type="button">
<span class="pficon-delete"></span>
</button>
</div>
</div>
{% endfor %}
<div><button type="button" class="add-array-item btn btn-default">Add another</button></div>
</div>
{% endspaceless %}

View file

@ -1,25 +1,15 @@
"""authentik lib Templatetags""" """authentik lib Templatetags"""
from hashlib import md5
from urllib.parse import urlencode
from django import template from django import template
from django.db.models import Model from django.db.models import Model
from django.http.request import HttpRequest
from django.template import Context from django.template import Context
from django.templatetags.static import static
from django.utils.html import escape, mark_safe
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.lib.config import CONFIG
from authentik.lib.utils.urls import is_url_absolute from authentik.lib.utils.urls import is_url_absolute
register = template.Library() register = template.Library()
LOGGER = get_logger() LOGGER = get_logger()
GRAVATAR_URL = "https://secure.gravatar.com"
DEFAULT_AVATAR = static("authentik/user_default.png")
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def back(context: Context) -> str: def back(context: Context) -> str:
@ -46,38 +36,12 @@ def fieldtype(field):
return field.__class__.__name__ return field.__class__.__name__
@register.simple_tag
def config(path, default=""):
"""Get a setting from the database. Returns default is setting doesn't exist."""
return CONFIG.y(path, default)
@register.filter(name="css_class") @register.filter(name="css_class")
def css_class(field, css): def css_class(field, css):
"""Add css class to form field""" """Add css class to form field"""
return field.as_widget(attrs={"class": css}) return field.as_widget(attrs={"class": css})
@register.simple_tag
def avatar(user: User) -> str:
"""Get avatar, depending on authentik.avatar setting"""
mode = CONFIG.raw.get("authentik").get("avatars")
if mode == "none":
return DEFAULT_AVATAR
if mode == "gravatar":
parameters = [
("s", "158"),
("r", "g"),
]
# gravatar uses md5 for their URLs, so md5 can't be avoided
mail_hash = md5(user.email.encode("utf-8")).hexdigest() # nosec
gravatar_url = (
f"{GRAVATAR_URL}/avatar/{mail_hash}?{urlencode(parameters, doseq=True)}"
)
return escape(gravatar_url)
raise ValueError(f"Invalid avatar mode {mode}")
@register.filter @register.filter
def verbose_name(obj) -> str: def verbose_name(obj) -> str:
"""Return Object's Verbose Name""" """Return Object's Verbose Name"""
@ -94,21 +58,3 @@ def form_verbose_name(obj) -> str:
if not obj: if not obj:
return "" return ""
return verbose_name(obj._meta.model) return verbose_name(obj._meta.model)
@register.filter
def doc(obj) -> str:
"""Return docstring of object"""
return mark_safe(obj.__doc__.replace("\n", "<br>"))
@register.simple_tag(takes_context=True)
def query_transform(context: Context, **kwargs) -> str:
"""Append objects to the current querystring"""
if "request" not in context:
return ""
request: HttpRequest = context["request"]
updated = request.GET.copy()
for key, value in kwargs.items():
updated[key] = value
return updated.urlencode()

View file

@ -1,11 +0,0 @@
"""authentik UI utils"""
from typing import Any
def human_list(_list: list[Any]) -> str:
"""Convert a list of items into 'a, b or c'"""
last_item = _list.pop()
if len(_list) < 1:
return last_item
result = ", ".join(_list)
return "%s or %s" % (result, last_item)

View file

@ -233,7 +233,9 @@ class OAuthFulfillmentStage(StageView):
params: OAuthAuthorizationParams params: OAuthAuthorizationParams
provider: OAuth2Provider provider: OAuth2Provider
# pylint: disable=unused-argument
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""final Stage of an OAuth2 Flow"""
self.params: OAuthAuthorizationParams = self.executor.plan.context.pop( self.params: OAuthAuthorizationParams = self.executor.plan.context.pop(
PLAN_CONTEXT_PARAMS PLAN_CONTEXT_PARAMS
) )

View file

@ -14,7 +14,9 @@ class PostUserEnrollmentStage(StageView):
"""Dynamically injected stage which saves the OAuth Connection after """Dynamically injected stage which saves the OAuth Connection after
the user has been enrolled.""" the user has been enrolled."""
# pylint: disable=unused-argument
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Stage used after the user has been enrolled"""
access: UserOAuthSourceConnection = self.executor.plan.context[ access: UserOAuthSourceConnection = self.executor.plan.context[
PLAN_CONTEXT_SOURCES_OAUTH_ACCESS PLAN_CONTEXT_SOURCES_OAUTH_ACCESS
] ]

View file

@ -16,7 +16,6 @@ from webauthn.webauthn import (
) )
from authentik.core.models import User from authentik.core.models import User
from authentik.lib.templatetags.authentik_utils import avatar
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.authenticator_webauthn.utils import generate_challenge from authentik.stages.authenticator_webauthn.utils import generate_challenge
@ -57,7 +56,7 @@ def get_webauthn_challenge(request: HttpRequest, device: WebAuthnDevice) -> dict
device.user.uid, device.user.uid,
device.user.username, device.user.username,
device.user.name, device.user.name,
avatar(device.user), device.user.avatar,
device.credential_id, device.credential_id,
device.public_key, device.public_key,
device.sign_count, device.sign_count,
@ -92,7 +91,7 @@ def validate_challenge_webauthn(data: dict, request: HttpRequest, user: User) ->
user.uid, user.uid,
user.username, user.username,
user.name, user.name,
avatar(user), user.avatar,
device.credential_id, device.credential_id,
device.public_key, device.public_key,
device.sign_count, device.sign_count,

View file

@ -16,7 +16,6 @@ from authentik.core.models import User
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.lib.templatetags.authentik_utils import avatar
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.authenticator_webauthn.utils import ( from authentik.stages.authenticator_webauthn.utils import (
generate_challenge, generate_challenge,
@ -118,7 +117,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
user.uid, user.uid,
user.username, user.username,
user.name, user.name,
avatar(user or User()), user.avatar,
) )
return AuthenticatorWebAuthnChallenge( return AuthenticatorWebAuthnChallenge(

View file

@ -12,7 +12,6 @@ from authentik.flows.challenge import (
) )
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.lib.templatetags.authentik_utils import avatar
from authentik.lib.utils.time import timedelta_from_string from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.consent.models import ConsentMode, ConsentStage, UserConsent from authentik.stages.consent.models import ConsentMode, ConsentStage, UserConsent
@ -56,7 +55,7 @@ class ConsentStageView(ChallengeStageView):
# If there's no user set, an error is raised later. # If there's no user set, an error is raised later.
if user := self.get_pending_user(): if user := self.get_pending_user():
challenge.initial_data["pending_user"] = user.username challenge.initial_data["pending_user"] = user.username
challenge.initial_data["pending_user_avatar"] = avatar(user) challenge.initial_data["pending_user_avatar"] = user.avatar
return challenge return challenge
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:

View file

@ -1,19 +1,31 @@
"""authentik multi-stage authentication engine""" """authentik multi-stage authentication engine"""
from typing import Any from django.http.response import HttpResponse
from django.http import HttpRequest from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.stage import ChallengeStageView
from authentik.flows.stage import StageView
class DummyStageView(StageView): class DummyChallenge(Challenge):
"""Dummy challenge"""
class DummyChallengeResponse(ChallengeResponse):
"""Dummy challenge response"""
class DummyStageView(ChallengeStageView):
"""Dummy stage for testing with multiple stages""" """Dummy stage for testing with multiple stages"""
def post(self, request: HttpRequest): response_class = DummyChallengeResponse
"""Just redirect to next stage"""
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return self.executor.stage_ok() return self.executor.stage_ok()
def get_context_data(self, **kwargs: dict[str, Any]) -> dict[str, Any]: def get_challenge(self, *args, **kwargs) -> Challenge:
kwargs = super().get_context_data(**kwargs) return DummyChallenge(
kwargs["title"] = self.executor.current_stage.name data={
return kwargs "type": ChallengeTypes.native,
"component": "",
"title": self.executor.current_stage.name,
}
)

View file

@ -15,6 +15,7 @@ class InvitationStageView(StageView):
"""Finalise Authentication flow by logging the user in""" """Finalise Authentication flow by logging the user in"""
def get(self, request: HttpRequest) -> HttpResponse: def get(self, request: HttpRequest) -> HttpResponse:
"""Apply data to the current flow based on a URL"""
stage: InvitationStage = self.executor.current_stage stage: InvitationStage = self.executor.current_stage
if INVITATION_TOKEN_KEY not in request.GET: if INVITATION_TOKEN_KEY not in request.GET:
# No Invitation was given, raise error or continue # No Invitation was given, raise error or continue

View file

@ -88,7 +88,9 @@ class PasswordStageView(ChallengeStageView):
"authentik_flows:flow-executor-shell", "authentik_flows:flow-executor-shell",
kwargs={"flow_slug": recovery_flow.first().slug}, kwargs={"flow_slug": recovery_flow.first().slug},
) )
challenge.initial_data["recovery_url"] = self.request.build_absolute_uri(recover_url) challenge.initial_data["recovery_url"] = self.request.build_absolute_uri(
recover_url
)
return challenge return challenge
def challenge_invalid(self, response: PasswordChallengeResponse) -> HttpResponse: def challenge_invalid(self, response: PasswordChallengeResponse) -> HttpResponse:

View file

@ -17,6 +17,7 @@ class UserLoginStageView(StageView):
"""Finalise Authentication flow by logging the user in""" """Finalise Authentication flow by logging the user in"""
def get(self, request: HttpRequest) -> HttpResponse: def get(self, request: HttpRequest) -> HttpResponse:
"""Attach the currently pending user to the current session"""
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
message = _("No Pending user to login.") message = _("No Pending user to login.")
messages.error(request, message) messages.error(request, message)

View file

@ -12,6 +12,7 @@ class UserLogoutStageView(StageView):
"""Finalise Authentication flow by logging the user in""" """Finalise Authentication flow by logging the user in"""
def get(self, request: HttpRequest) -> HttpResponse: def get(self, request: HttpRequest) -> HttpResponse:
"""Remove the user from the current session"""
LOGGER.debug( LOGGER.debug(
"Logged out", "Logged out",
user=request.user, user=request.user,

View file

@ -22,6 +22,8 @@ class UserWriteStageView(StageView):
"""Finalise Enrollment flow by creating a user object.""" """Finalise Enrollment flow by creating a user object."""
def get(self, request: HttpRequest) -> HttpResponse: def get(self, request: HttpRequest) -> HttpResponse:
"""Save data in the current flow to the currently pending user. If no user is pending,
a new user is created."""
if PLAN_CONTEXT_PROMPT not in self.executor.plan.context: if PLAN_CONTEXT_PROMPT not in self.executor.plan.context:
message = _("No Pending data.") message = _("No Pending data.")
messages.error(request, message) messages.error(request, message)