stages/*: use bound logger (#3012)

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2022-06-01 23:01:58 +02:00 committed by GitHub
parent 5a81ae956f
commit fc1c1a849a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 61 additions and 92 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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)

View File

@ -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"""

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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"))

View File

@ -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"

View File

@ -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()

View File

@ -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,

View File

@ -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,

View File

@ -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,