stages/*: use bound logger (#3012)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
5a81ae956f
commit
fc1c1a849a
|
@ -9,7 +9,7 @@ from django.urls import reverse
|
||||||
from django.views.generic.base import View
|
from django.views.generic.base import View
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from sentry_sdk.hub import Hub
|
from sentry_sdk.hub import Hub
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import BoundLogger, get_logger
|
||||||
|
|
||||||
from authentik.core.models import DEFAULT_AVATAR, User
|
from authentik.core.models import DEFAULT_AVATAR, User
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
|
@ -28,7 +28,6 @@ if TYPE_CHECKING:
|
||||||
from authentik.flows.views.executor import FlowExecutorView
|
from authentik.flows.views.executor import FlowExecutorView
|
||||||
|
|
||||||
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
|
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class StageView(View):
|
class StageView(View):
|
||||||
|
@ -38,8 +37,15 @@ class StageView(View):
|
||||||
|
|
||||||
request: HttpRequest = None
|
request: HttpRequest = None
|
||||||
|
|
||||||
|
logger: BoundLogger
|
||||||
|
|
||||||
def __init__(self, executor: "FlowExecutorView", **kwargs):
|
def __init__(self, executor: "FlowExecutorView", **kwargs):
|
||||||
self.executor = executor
|
self.executor = executor
|
||||||
|
current_stage = getattr(self.executor, "current_stage", None)
|
||||||
|
self.logger = get_logger().bind(
|
||||||
|
stage=getattr(current_stage, "name", None),
|
||||||
|
stage_view=self,
|
||||||
|
)
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def get_pending_user(self, for_display=False) -> User:
|
def get_pending_user(self, for_display=False) -> User:
|
||||||
|
@ -77,12 +83,9 @@ class ChallengeStageView(StageView):
|
||||||
"""Return a challenge for the frontend to solve"""
|
"""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(
|
self.logger.warning(
|
||||||
"f(ch): Invalid challenge",
|
"f(ch): Invalid challenge",
|
||||||
binding=self.executor.current_binding,
|
|
||||||
errors=challenge.errors,
|
errors=challenge.errors,
|
||||||
stage_view=self,
|
|
||||||
challenge=challenge,
|
|
||||||
)
|
)
|
||||||
return HttpChallengeResponse(challenge)
|
return HttpChallengeResponse(challenge)
|
||||||
|
|
||||||
|
@ -99,10 +102,8 @@ class ChallengeStageView(StageView):
|
||||||
self.executor.current_binding.invalid_response_action
|
self.executor.current_binding.invalid_response_action
|
||||||
== InvalidResponseAction.RESTART_WITH_CONTEXT
|
== InvalidResponseAction.RESTART_WITH_CONTEXT
|
||||||
)
|
)
|
||||||
LOGGER.debug(
|
self.logger.debug(
|
||||||
"f(ch): Invalid response, restarting flow",
|
"f(ch): Invalid response, restarting flow",
|
||||||
binding=self.executor.current_binding,
|
|
||||||
stage_view=self,
|
|
||||||
keep_context=keep_context,
|
keep_context=keep_context,
|
||||||
)
|
)
|
||||||
return self.executor.restart_flow(keep_context)
|
return self.executor.restart_flow(keep_context)
|
||||||
|
@ -128,7 +129,7 @@ class ChallengeStageView(StageView):
|
||||||
}
|
}
|
||||||
# pylint: disable=broad-except
|
# pylint: disable=broad-except
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOGGER.warning("failed to template title", exc=exc)
|
self.logger.warning("failed to template title", exc=exc)
|
||||||
return self.executor.flow.title
|
return self.executor.flow.title
|
||||||
|
|
||||||
def _get_challenge(self, *args, **kwargs) -> Challenge:
|
def _get_challenge(self, *args, **kwargs) -> Challenge:
|
||||||
|
@ -188,11 +189,9 @@ class ChallengeStageView(StageView):
|
||||||
)
|
)
|
||||||
challenge_response.initial_data["response_errors"] = full_errors
|
challenge_response.initial_data["response_errors"] = full_errors
|
||||||
if not challenge_response.is_valid():
|
if not challenge_response.is_valid():
|
||||||
LOGGER.error(
|
self.logger.error(
|
||||||
"f(ch): invalid challenge response",
|
"f(ch): invalid challenge response",
|
||||||
binding=self.executor.current_binding,
|
|
||||||
errors=challenge_response.errors,
|
errors=challenge_response.errors,
|
||||||
stage_view=self,
|
|
||||||
)
|
)
|
||||||
return HttpChallengeResponse(challenge_response)
|
return HttpChallengeResponse(challenge_response)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
|
@ -16,8 +15,6 @@ from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.flows.views.executor import InvalidStageError
|
from authentik.flows.views.executor import InvalidStageError
|
||||||
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
|
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
SESSION_KEY_DUO_USER_ID = "authentik/stages/authenticator_duo/user_id"
|
SESSION_KEY_DUO_USER_ID = "authentik/stages/authenticator_duo/user_id"
|
||||||
SESSION_KEY_DUO_ACTIVATION_CODE = "authentik/stages/authenticator_duo/activation_code"
|
SESSION_KEY_DUO_ACTIVATION_CODE = "authentik/stages/authenticator_duo/activation_code"
|
||||||
|
|
||||||
|
@ -69,7 +66,7 @@ class AuthenticatorDuoStageView(ChallengeStageView):
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
||||||
if not user:
|
if not user:
|
||||||
LOGGER.debug("No pending user, continuing")
|
self.logger.debug("No pending user, continuing")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ from django.http.request import QueryDict
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.fields import BooleanField, CharField, IntegerField
|
from rest_framework.fields import BooleanField, CharField, IntegerField
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
|
@ -19,7 +18,6 @@ from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSDevice
|
from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSDevice
|
||||||
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
SESSION_KEY_SMS_DEVICE = "authentik/stages/authenticator_sms/sms_device"
|
SESSION_KEY_SMS_DEVICE = "authentik/stages/authenticator_sms/sms_device"
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,10 +62,10 @@ class AuthenticatorSMSStageView(ChallengeStageView):
|
||||||
def _has_phone_number(self) -> Optional[str]:
|
def _has_phone_number(self) -> Optional[str]:
|
||||||
context = self.executor.plan.context
|
context = self.executor.plan.context
|
||||||
if "phone" in context.get(PLAN_CONTEXT_PROMPT, {}):
|
if "phone" in context.get(PLAN_CONTEXT_PROMPT, {}):
|
||||||
LOGGER.debug("got phone number from plan context")
|
self.logger.debug("got phone number from plan context")
|
||||||
return context.get(PLAN_CONTEXT_PROMPT, {}).get("phone")
|
return context.get(PLAN_CONTEXT_PROMPT, {}).get("phone")
|
||||||
if SESSION_KEY_SMS_DEVICE in self.request.session:
|
if SESSION_KEY_SMS_DEVICE in self.request.session:
|
||||||
LOGGER.debug("got phone number from device in session")
|
self.logger.debug("got phone number from device in session")
|
||||||
device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE]
|
device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE]
|
||||||
if device.phone_number == "":
|
if device.phone_number == "":
|
||||||
return None
|
return None
|
||||||
|
@ -90,7 +88,7 @@ class AuthenticatorSMSStageView(ChallengeStageView):
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
||||||
if not user:
|
if not user:
|
||||||
LOGGER.debug("No pending user, continuing")
|
self.logger.debug("No pending user, continuing")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
# Currently, this stage only supports one device per user. If the user already
|
# Currently, this stage only supports one device per user. If the user already
|
||||||
|
|
|
@ -2,14 +2,11 @@
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
||||||
from rest_framework.fields import CharField, ListField
|
from rest_framework.fields import CharField, ListField
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
|
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
|
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticatorStaticChallenge(WithUserInfoChallenge):
|
class AuthenticatorStaticChallenge(WithUserInfoChallenge):
|
||||||
"""Static authenticator challenge"""
|
"""Static authenticator challenge"""
|
||||||
|
@ -42,7 +39,7 @@ class AuthenticatorStaticStageView(ChallengeStageView):
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
user = self.get_pending_user()
|
user = self.get_pending_user()
|
||||||
if not user.is_authenticated:
|
if not user.is_authenticated:
|
||||||
LOGGER.debug("No pending user, continuing")
|
self.logger.debug("No pending user, continuing")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
stage: AuthenticatorStaticStage = self.executor.current_stage
|
stage: AuthenticatorStaticStage = self.executor.current_stage
|
||||||
|
|
|
@ -6,7 +6,6 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||||
from rest_framework.fields import CharField, IntegerField
|
from rest_framework.fields import CharField, IntegerField
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
|
@ -18,8 +17,6 @@ from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
|
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
|
||||||
from authentik.stages.authenticator_totp.settings import OTP_TOTP_ISSUER
|
from authentik.stages.authenticator_totp.settings import OTP_TOTP_ISSUER
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticatorTOTPChallenge(WithUserInfoChallenge):
|
class AuthenticatorTOTPChallenge(WithUserInfoChallenge):
|
||||||
"""TOTP Setup challenge"""
|
"""TOTP Setup challenge"""
|
||||||
|
@ -72,7 +69,7 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
user = self.get_pending_user()
|
user = self.get_pending_user()
|
||||||
if not user.is_authenticated:
|
if not user.is_authenticated:
|
||||||
LOGGER.debug("No pending user, continuing")
|
self.logger.debug("No pending user, continuing")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
stage: AuthenticatorTOTPStage = self.executor.current_stage
|
stage: AuthenticatorTOTPStage = self.executor.current_stage
|
||||||
|
|
|
@ -37,6 +37,7 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||||
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
|
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
COOKIE_NAME_MFA = "authentik_mfa"
|
COOKIE_NAME_MFA = "authentik_mfa"
|
||||||
|
|
||||||
SESSION_KEY_STAGES = "authentik/stages/authenticator_validate/stages"
|
SESSION_KEY_STAGES = "authentik/stages/authenticator_validate/stages"
|
||||||
|
@ -151,7 +152,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
challenges = []
|
challenges = []
|
||||||
# Convert to a list to have usable log output instead of just <generator ...>
|
# Convert to a list to have usable log output instead of just <generator ...>
|
||||||
user_devices = list(devices_for_user(self.get_pending_user()))
|
user_devices = list(devices_for_user(self.get_pending_user()))
|
||||||
LOGGER.debug("Got devices for user", devices=user_devices)
|
self.logger.debug("Got devices for user", devices=user_devices)
|
||||||
|
|
||||||
# static and totp are only shown once
|
# static and totp are only shown once
|
||||||
# since their challenges are device-independent
|
# since their challenges are device-independent
|
||||||
|
@ -165,7 +166,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
for device in user_devices:
|
for device in user_devices:
|
||||||
device_class = device.__class__.__name__.lower().replace("device", "")
|
device_class = device.__class__.__name__.lower().replace("device", "")
|
||||||
if device_class not in stage.device_classes:
|
if device_class not in stage.device_classes:
|
||||||
LOGGER.debug("device class not allowed", device_class=device_class)
|
self.logger.debug("device class not allowed", device_class=device_class)
|
||||||
continue
|
continue
|
||||||
allowed_devices.append(device)
|
allowed_devices.append(device)
|
||||||
# Ensure only one challenge per device class
|
# Ensure only one challenge per device class
|
||||||
|
@ -183,7 +184,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
)
|
)
|
||||||
challenge.is_valid()
|
challenge.is_valid()
|
||||||
challenges.append(challenge.data)
|
challenges.append(challenge.data)
|
||||||
LOGGER.debug("adding challenge for device", challenge=challenge)
|
self.logger.debug("adding challenge for device", challenge=challenge)
|
||||||
# check if we have an MFA cookie and if it's valid
|
# check if we have an MFA cookie and if it's valid
|
||||||
if threshold.total_seconds() > 0:
|
if threshold.total_seconds() > 0:
|
||||||
self.check_mfa_cookie(allowed_devices)
|
self.check_mfa_cookie(allowed_devices)
|
||||||
|
@ -214,27 +215,27 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
else:
|
else:
|
||||||
if self.executor.flow.designation != FlowDesignation.AUTHENTICATION:
|
if self.executor.flow.designation != FlowDesignation.AUTHENTICATION:
|
||||||
LOGGER.debug("Refusing passwordless flow in non-authentication flow")
|
self.logger.debug("Refusing passwordless flow in non-authentication flow")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
# Passwordless auth, with just webauthn
|
# Passwordless auth, with just webauthn
|
||||||
if DeviceClasses.WEBAUTHN in stage.device_classes:
|
if DeviceClasses.WEBAUTHN in stage.device_classes:
|
||||||
LOGGER.debug("Flow without user, getting generic webauthn challenge")
|
self.logger.debug("Flow without user, getting generic webauthn challenge")
|
||||||
challenges = self.get_webauthn_challenge_without_user()
|
challenges = self.get_webauthn_challenge_without_user()
|
||||||
else:
|
else:
|
||||||
LOGGER.debug("No pending user, continuing")
|
self.logger.debug("No pending user, continuing")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
self.request.session[SESSION_KEY_DEVICE_CHALLENGES] = challenges
|
self.request.session[SESSION_KEY_DEVICE_CHALLENGES] = challenges
|
||||||
|
|
||||||
# No allowed devices
|
# No allowed devices
|
||||||
if len(challenges) < 1:
|
if len(challenges) < 1:
|
||||||
if stage.not_configured_action == NotConfiguredAction.SKIP:
|
if stage.not_configured_action == NotConfiguredAction.SKIP:
|
||||||
LOGGER.debug("Authenticator not configured, skipping stage")
|
self.logger.debug("Authenticator not configured, skipping stage")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
if stage.not_configured_action == NotConfiguredAction.DENY:
|
if stage.not_configured_action == NotConfiguredAction.DENY:
|
||||||
LOGGER.debug("Authenticator not configured, denying")
|
self.logger.debug("Authenticator not configured, denying")
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
if stage.not_configured_action == NotConfiguredAction.CONFIGURE:
|
if stage.not_configured_action == NotConfiguredAction.CONFIGURE:
|
||||||
LOGGER.debug("Authenticator not configured, forcing configure")
|
self.logger.debug("Authenticator not configured, forcing configure")
|
||||||
return self.prepare_stages(user)
|
return self.prepare_stages(user)
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -255,7 +256,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
if stage.configuration_stages.count() == 1:
|
if stage.configuration_stages.count() == 1:
|
||||||
next_stage = Stage.objects.get_subclass(pk=stage.configuration_stages.first().pk)
|
next_stage = Stage.objects.get_subclass(pk=stage.configuration_stages.first().pk)
|
||||||
LOGGER.debug("Single stage configured, auto-selecting", stage=next_stage)
|
self.logger.debug("Single stage configured, auto-selecting", stage=next_stage)
|
||||||
self.request.session[SESSION_KEY_SELECTED_STAGE] = next_stage
|
self.request.session[SESSION_KEY_SELECTED_STAGE] = next_stage
|
||||||
# Because that normal execution only happens on post, we directly inject it here and
|
# Because that normal execution only happens on post, we directly inject it here and
|
||||||
# return it
|
# return it
|
||||||
|
@ -271,7 +272,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
SESSION_KEY_SELECTED_STAGE in self.request.session
|
SESSION_KEY_SELECTED_STAGE in self.request.session
|
||||||
and self.executor.current_stage.not_configured_action == NotConfiguredAction.CONFIGURE
|
and self.executor.current_stage.not_configured_action == NotConfiguredAction.CONFIGURE
|
||||||
):
|
):
|
||||||
LOGGER.debug("Got selected stage in session, running that")
|
self.logger.debug("Got selected stage in session, running that")
|
||||||
stage_pk = self.request.session.get(SESSION_KEY_SELECTED_STAGE)
|
stage_pk = self.request.session.get(SESSION_KEY_SELECTED_STAGE)
|
||||||
# Because the foreign key to stage.configuration_stage points to
|
# Because the foreign key to stage.configuration_stage points to
|
||||||
# a base stage class, we need to do another lookup
|
# a base stage class, we need to do another lookup
|
||||||
|
@ -326,18 +327,18 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
try:
|
try:
|
||||||
payload = decode(self.request.COOKIES[COOKIE_NAME_MFA], self.cookie_jwt_key, ["HS256"])
|
payload = decode(self.request.COOKIES[COOKIE_NAME_MFA], self.cookie_jwt_key, ["HS256"])
|
||||||
if payload["stage"] != stage.pk.hex:
|
if payload["stage"] != stage.pk.hex:
|
||||||
LOGGER.warning("Invalid stage PK")
|
self.logger.warning("Invalid stage PK")
|
||||||
return
|
return
|
||||||
if datetime.fromtimestamp(payload["exp"]) > latest_allowed:
|
if datetime.fromtimestamp(payload["exp"]) > latest_allowed:
|
||||||
LOGGER.warning("Expired MFA cookie")
|
self.logger.warning("Expired MFA cookie")
|
||||||
return
|
return
|
||||||
if not any(device.pk == payload["device"] for device in allowed_devices):
|
if not any(device.pk == payload["device"] for device in allowed_devices):
|
||||||
LOGGER.warning("Invalid device PK")
|
self.logger.warning("Invalid device PK")
|
||||||
return
|
return
|
||||||
LOGGER.info("MFA has been used within threshold")
|
self.logger.info("MFA has been used within threshold")
|
||||||
raise FlowSkipStageException()
|
raise FlowSkipStageException()
|
||||||
except (PyJWTError, ValueError, TypeError) as exc:
|
except (PyJWTError, ValueError, TypeError) as exc:
|
||||||
LOGGER.info("Invalid mfa cookie for device", exc=exc)
|
self.logger.info("Invalid mfa cookie for device", exc=exc)
|
||||||
|
|
||||||
def set_valid_mfa_cookie(self, device: Device) -> HttpResponse:
|
def set_valid_mfa_cookie(self, device: Device) -> HttpResponse:
|
||||||
"""Set an MFA cookie to allow users to skip MFA validation in this context (browser)
|
"""Set an MFA cookie to allow users to skip MFA validation in this context (browser)
|
||||||
|
@ -346,7 +347,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
stage: AuthenticatorValidateStage = self.executor.current_stage
|
stage: AuthenticatorValidateStage = self.executor.current_stage
|
||||||
delta = timedelta_from_string(stage.last_auth_threshold)
|
delta = timedelta_from_string(stage.last_auth_threshold)
|
||||||
if delta.total_seconds() < 1:
|
if delta.total_seconds() < 1:
|
||||||
LOGGER.info("Not setting MFA cookie since threshold is not set.")
|
self.logger.info("Not setting MFA cookie since threshold is not set.")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
expiry = datetime.now() + delta
|
expiry = datetime.now() + delta
|
||||||
cookie_payload = {
|
cookie_payload = {
|
||||||
|
@ -375,7 +376,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||||
webauthn_device: WebAuthnDevice = response.data.get("webauthn", None)
|
webauthn_device: WebAuthnDevice = response.data.get("webauthn", None)
|
||||||
if not webauthn_device:
|
if not webauthn_device:
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
LOGGER.debug("Set user from user-less flow", user=webauthn_device.user)
|
self.logger.debug("Set user from user-less flow", user=webauthn_device.user)
|
||||||
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = webauthn_device.user
|
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = webauthn_device.user
|
||||||
self.executor.plan.context[PLAN_CONTEXT_METHOD] = "auth_webauthn_pwl"
|
self.executor.plan.context[PLAN_CONTEXT_METHOD] = "auth_webauthn_pwl"
|
||||||
self.executor.plan.context[PLAN_CONTEXT_METHOD_ARGS] = cleanse_dict(
|
self.executor.plan.context[PLAN_CONTEXT_METHOD_ARGS] = cleanse_dict(
|
||||||
|
|
|
@ -29,7 +29,6 @@ from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnS
|
||||||
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
|
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
SESSION_KEY_WEBAUTHN_CHALLENGE = "authentik/stages/authenticator_webauthn/challenge"
|
SESSION_KEY_WEBAUTHN_CHALLENGE = "authentik/stages/authenticator_webauthn/challenge"
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,7 +114,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
||||||
if not user:
|
if not user:
|
||||||
LOGGER.debug("No pending user, continuing")
|
self.logger.debug("No pending user, continuing")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
"""Deny stage logic"""
|
"""Deny stage logic"""
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.flows.stage import StageView
|
from authentik.flows.stage import StageView
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class DenyStageView(StageView):
|
class DenyStageView(StageView):
|
||||||
"""Cancells the current flow"""
|
"""Cancells the current flow"""
|
||||||
|
|
|
@ -10,7 +10,6 @@ from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
||||||
from authentik.flows.models import FlowToken
|
from authentik.flows.models import FlowToken
|
||||||
|
@ -21,7 +20,6 @@ from authentik.stages.email.models import EmailStage
|
||||||
from authentik.stages.email.tasks import send_mails
|
from authentik.stages.email.tasks import send_mails
|
||||||
from authentik.stages.email.utils import TemplateEmailMessage
|
from authentik.stages.email.utils import TemplateEmailMessage
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
PLAN_CONTEXT_EMAIL_SENT = "email_sent"
|
PLAN_CONTEXT_EMAIL_SENT = "email_sent"
|
||||||
PLAN_CONTEXT_EMAIL_OVERRIDE = "email"
|
PLAN_CONTEXT_EMAIL_OVERRIDE = "email"
|
||||||
|
|
||||||
|
@ -113,7 +111,7 @@ class EmailStageView(ChallengeStageView):
|
||||||
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER].save()
|
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER].save()
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
|
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
|
||||||
LOGGER.debug("No pending user")
|
self.logger.debug("No pending user")
|
||||||
messages.error(self.request, _("No pending user."))
|
messages.error(self.request, _("No pending user."))
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
# Check if we've already sent the initial e-mail
|
# Check if we've already sent the initial e-mail
|
||||||
|
|
|
@ -159,11 +159,11 @@ class IdentificationStageView(ChallengeStageView):
|
||||||
model_field += "__exact"
|
model_field += "__exact"
|
||||||
query |= Q(**{model_field: uid_value})
|
query |= Q(**{model_field: uid_value})
|
||||||
if not query:
|
if not query:
|
||||||
LOGGER.debug("Empty user query", query=query)
|
self.logger.debug("Empty user query", query=query)
|
||||||
return None
|
return None
|
||||||
users = User.objects.filter(query, is_active=True)
|
users = User.objects.filter(query, is_active=True)
|
||||||
if users.exists():
|
if users.exists():
|
||||||
LOGGER.debug("Found user", user=users.first(), query=query)
|
self.logger.debug("Found user", user=users.first(), query=query)
|
||||||
return users.first()
|
return users.first()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ from typing import Optional
|
||||||
from deepmerge import always_merger
|
from deepmerge import always_merger
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.http.response import HttpResponseBadRequest
|
from django.http.response import HttpResponseBadRequest
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.flows.models import in_memory_stage
|
from authentik.flows.models import in_memory_stage
|
||||||
from authentik.flows.stage import StageView
|
from authentik.flows.stage import StageView
|
||||||
|
@ -13,7 +12,6 @@ from authentik.stages.invitation.models import Invitation, InvitationStage
|
||||||
from authentik.stages.invitation.signals import invitation_used
|
from authentik.stages.invitation.signals import invitation_used
|
||||||
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
INVITATION_TOKEN_KEY_CONTEXT = "token" # nosec
|
INVITATION_TOKEN_KEY_CONTEXT = "token" # nosec
|
||||||
INVITATION_TOKEN_KEY = "itoken" # nosec
|
INVITATION_TOKEN_KEY = "itoken" # nosec
|
||||||
INVITATION_IN_EFFECT = "invitation_in_effect"
|
INVITATION_IN_EFFECT = "invitation_in_effect"
|
||||||
|
@ -51,7 +49,7 @@ class InvitationStageView(StageView):
|
||||||
|
|
||||||
invite: Invitation = Invitation.objects.filter(pk=token).first()
|
invite: Invitation = Invitation.objects.filter(pk=token).first()
|
||||||
if not invite:
|
if not invite:
|
||||||
LOGGER.debug("invalid invitation", token=token)
|
self.logger.debug("invalid invitation", token=token)
|
||||||
if stage.continue_flow_without_invitation:
|
if stage.continue_flow_without_invitation:
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
|
@ -81,11 +79,11 @@ class InvitationFinalStageView(StageView):
|
||||||
"""Delete invitation if single_use is active"""
|
"""Delete invitation if single_use is active"""
|
||||||
invitation: Invitation = self.executor.plan.context.get(INVITATION, None)
|
invitation: Invitation = self.executor.plan.context.get(INVITATION, None)
|
||||||
if not invitation:
|
if not invitation:
|
||||||
LOGGER.warning("InvitationFinalStageView stage called without invitation")
|
self.logger.warning("InvitationFinalStageView stage called without invitation")
|
||||||
return HttpResponseBadRequest
|
return HttpResponseBadRequest
|
||||||
token = invitation.invite_uuid.hex
|
token = invitation.invite_uuid.hex
|
||||||
if invitation.single_use:
|
if invitation.single_use:
|
||||||
invitation.delete()
|
invitation.delete()
|
||||||
LOGGER.debug("Deleted invitation", token=token)
|
self.logger.debug("Deleted invitation", token=token)
|
||||||
del self.executor.plan.context[INVITATION]
|
del self.executor.plan.context[INVITATION]
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|
|
@ -108,7 +108,7 @@ class PasswordStageView(ChallengeStageView):
|
||||||
self.request.session[SESSION_KEY_INVALID_TRIES]
|
self.request.session[SESSION_KEY_INVALID_TRIES]
|
||||||
> current_stage.failed_attempts_before_cancel
|
> current_stage.failed_attempts_before_cancel
|
||||||
):
|
):
|
||||||
LOGGER.debug("User has exceeded maximum tries")
|
self.logger.debug("User has exceeded maximum tries")
|
||||||
del self.request.session[SESSION_KEY_INVALID_TRIES]
|
del self.request.session[SESSION_KEY_INVALID_TRIES]
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
return super().challenge_invalid(response)
|
return super().challenge_invalid(response)
|
||||||
|
@ -135,18 +135,18 @@ class PasswordStageView(ChallengeStageView):
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
del auth_kwargs["password"]
|
del auth_kwargs["password"]
|
||||||
# User was found, but permission was denied (i.e. user is not active)
|
# User was found, but permission was denied (i.e. user is not active)
|
||||||
LOGGER.debug("Denied access", **auth_kwargs)
|
self.logger.debug("Denied access", **auth_kwargs)
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
del auth_kwargs["password"]
|
del auth_kwargs["password"]
|
||||||
# User was found, authentication succeeded, but another signal raised an error
|
# User was found, authentication succeeded, but another signal raised an error
|
||||||
# (most likely LDAP)
|
# (most likely LDAP)
|
||||||
LOGGER.debug("Validation error from signal", exc=exc, **auth_kwargs)
|
self.logger.debug("Validation error from signal", exc=exc, **auth_kwargs)
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
else:
|
else:
|
||||||
if not user:
|
if not user:
|
||||||
# No user was found -> invalid credentials
|
# No user was found -> invalid credentials
|
||||||
LOGGER.debug("Invalid credentials")
|
self.logger.debug("Invalid credentials")
|
||||||
# Manually inject error into form
|
# Manually inject error into form
|
||||||
response._errors.setdefault("password", [])
|
response._errors.setdefault("password", [])
|
||||||
response._errors["password"].append(ErrorDetail(_("Invalid password"), "invalid"))
|
response._errors["password"].append(ErrorDetail(_("Invalid password"), "invalid"))
|
||||||
|
|
|
@ -10,7 +10,6 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
from rest_framework.fields import BooleanField, CharField, ChoiceField, IntegerField, empty
|
from rest_framework.fields import BooleanField, CharField, ChoiceField, IntegerField, empty
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
|
@ -22,7 +21,6 @@ from authentik.policies.models import PolicyBinding, PolicyBindingModel, PolicyE
|
||||||
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
||||||
from authentik.stages.prompt.signals import password_validate
|
from authentik.stages.prompt.signals import password_validate
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
PLAN_CONTEXT_PROMPT = "prompt_data"
|
PLAN_CONTEXT_PROMPT = "prompt_data"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,10 @@ from django.contrib import messages
|
||||||
from django.contrib.auth import logout
|
from django.contrib.auth import logout
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
from authentik.flows.stage import StageView
|
from authentik.flows.stage import StageView
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class UserDeleteStageView(StageView):
|
class UserDeleteStageView(StageView):
|
||||||
"""Finalise unenrollment flow by deleting the user object."""
|
"""Finalise unenrollment flow by deleting the user object."""
|
||||||
|
@ -24,11 +21,11 @@ class UserDeleteStageView(StageView):
|
||||||
if not user.is_authenticated:
|
if not user.is_authenticated:
|
||||||
message = _("No Pending User.")
|
message = _("No Pending User.")
|
||||||
messages.error(request, message)
|
messages.error(request, message)
|
||||||
LOGGER.debug(message)
|
self.logger.debug(message)
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
logout(self.request)
|
logout(self.request)
|
||||||
user.delete()
|
user.delete()
|
||||||
LOGGER.debug("Deleted user", user=user)
|
self.logger.debug("Deleted user", user=user)
|
||||||
if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context:
|
if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context:
|
||||||
del self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
|
del self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|
|
@ -3,7 +3,6 @@ from django.contrib import messages
|
||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
|
@ -12,7 +11,6 @@ from authentik.lib.utils.time import timedelta_from_string
|
||||||
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
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
USER_LOGIN_AUTHENTICATED = "user_login_authenticated"
|
USER_LOGIN_AUTHENTICATED = "user_login_authenticated"
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,14 +26,14 @@ class UserLoginStageView(StageView):
|
||||||
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)
|
||||||
LOGGER.debug(message)
|
self.logger.debug(message)
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
backend = self.executor.plan.context.get(
|
backend = self.executor.plan.context.get(
|
||||||
PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_INBUILT
|
PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_INBUILT
|
||||||
)
|
)
|
||||||
user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
|
user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
|
||||||
if not user.is_active:
|
if not user.is_active:
|
||||||
LOGGER.warning("User is not active, login will not work.")
|
self.logger.warning("User is not active, login will not work.")
|
||||||
login(
|
login(
|
||||||
self.request,
|
self.request,
|
||||||
user,
|
user,
|
||||||
|
@ -46,7 +44,7 @@ class UserLoginStageView(StageView):
|
||||||
self.request.session.set_expiry(0)
|
self.request.session.set_expiry(0)
|
||||||
else:
|
else:
|
||||||
self.request.session.set_expiry(delta)
|
self.request.session.set_expiry(delta)
|
||||||
LOGGER.debug(
|
self.logger.debug(
|
||||||
"Logged in",
|
"Logged in",
|
||||||
backend=backend,
|
backend=backend,
|
||||||
user=user,
|
user=user,
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
"""Logout stage logic"""
|
"""Logout stage logic"""
|
||||||
from django.contrib.auth import logout
|
from django.contrib.auth import logout
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.flows.stage import StageView
|
from authentik.flows.stage import StageView
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class UserLogoutStageView(StageView):
|
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"""
|
"""Remove the user from the current session"""
|
||||||
LOGGER.debug(
|
self.logger.debug(
|
||||||
"Logged out",
|
"Logged out",
|
||||||
user=request.user,
|
user=request.user,
|
||||||
flow_slug=self.executor.flow.slug,
|
flow_slug=self.executor.flow.slug,
|
||||||
|
|
|
@ -7,7 +7,6 @@ from django.db import transaction
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.core.middleware import SESSION_KEY_IMPERSONATE_USER
|
from authentik.core.middleware import SESSION_KEY_IMPERSONATE_USER
|
||||||
from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection
|
from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection
|
||||||
|
@ -19,7 +18,6 @@ from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||||
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
from authentik.stages.user_write.signals import user_write
|
from authentik.stages.user_write.signals import user_write
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
PLAN_CONTEXT_GROUPS = "groups"
|
PLAN_CONTEXT_GROUPS = "groups"
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +54,7 @@ class UserWriteStageView(StageView):
|
||||||
is_active=not self.executor.current_stage.create_users_as_inactive
|
is_active=not self.executor.current_stage.create_users_as_inactive
|
||||||
)
|
)
|
||||||
self.executor.plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
|
self.executor.plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
|
||||||
LOGGER.debug(
|
self.logger.debug(
|
||||||
"Created new user",
|
"Created new user",
|
||||||
flow_slug=self.executor.flow.slug,
|
flow_slug=self.executor.flow.slug,
|
||||||
)
|
)
|
||||||
|
@ -86,7 +84,7 @@ class UserWriteStageView(StageView):
|
||||||
# `attribute_`, to prevent accidentally saving values
|
# `attribute_`, to prevent accidentally saving values
|
||||||
else:
|
else:
|
||||||
if not key.startswith("attributes.") and not key.startswith("attributes_"):
|
if not key.startswith("attributes.") and not key.startswith("attributes_"):
|
||||||
LOGGER.debug("discarding key", key=key)
|
self.logger.debug("discarding key", key=key)
|
||||||
continue
|
continue
|
||||||
UserWriteStageView.write_attribute(user, key, value)
|
UserWriteStageView.write_attribute(user, key, value)
|
||||||
# Check if we're writing from a source, and save the source to the attributes
|
# Check if we're writing from a source, and save the source to the attributes
|
||||||
|
@ -106,7 +104,7 @@ class UserWriteStageView(StageView):
|
||||||
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)
|
||||||
LOGGER.debug(message)
|
self.logger.debug(message)
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
data = self.executor.plan.context[PLAN_CONTEXT_PROMPT]
|
data = self.executor.plan.context[PLAN_CONTEXT_PROMPT]
|
||||||
user, user_created = self.ensure_user()
|
user, user_created = self.ensure_user()
|
||||||
|
@ -123,7 +121,7 @@ class UserWriteStageView(StageView):
|
||||||
self.update_user(user)
|
self.update_user(user)
|
||||||
# Extra check to prevent flows from saving a user with a blank username
|
# Extra check to prevent flows from saving a user with a blank username
|
||||||
if user.username == "":
|
if user.username == "":
|
||||||
LOGGER.warning("Aborting write to empty username", user=user)
|
self.logger.warning("Aborting write to empty username", user=user)
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
@ -133,14 +131,14 @@ class UserWriteStageView(StageView):
|
||||||
if PLAN_CONTEXT_GROUPS in self.executor.plan.context:
|
if PLAN_CONTEXT_GROUPS in self.executor.plan.context:
|
||||||
user.ak_groups.add(*self.executor.plan.context[PLAN_CONTEXT_GROUPS])
|
user.ak_groups.add(*self.executor.plan.context[PLAN_CONTEXT_GROUPS])
|
||||||
except (IntegrityError, ValueError, TypeError) as exc:
|
except (IntegrityError, ValueError, TypeError) as exc:
|
||||||
LOGGER.warning("Failed to save user", exc=exc)
|
self.logger.warning("Failed to save user", exc=exc)
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
user_write.send(sender=self, request=request, user=user, data=data, created=user_created)
|
user_write.send(sender=self, request=request, user=user, data=data, created=user_created)
|
||||||
# Check if the password has been updated, and update the session auth hash
|
# Check if the password has been updated, and update the session auth hash
|
||||||
if should_update_session:
|
if should_update_session:
|
||||||
update_session_auth_hash(self.request, user)
|
update_session_auth_hash(self.request, user)
|
||||||
LOGGER.debug("Updated session hash", user=user)
|
self.logger.debug("Updated session hash", user=user)
|
||||||
LOGGER.debug(
|
self.logger.debug(
|
||||||
"Updated existing user",
|
"Updated existing user",
|
||||||
user=user,
|
user=user,
|
||||||
flow_slug=self.executor.flow.slug,
|
flow_slug=self.executor.flow.slug,
|
||||||
|
|
Reference in New Issue