From 5019346ab665db3a4791acdfbd5c090e0b5bb001 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Fri, 25 Nov 2022 00:01:57 +0100 Subject: [PATCH] events: save login event in session after login Signed-off-by: Jens Langhammer #4070 --- authentik/core/api/applications.py | 2 +- authentik/events/middleware.py | 23 +++++++++++-- authentik/events/signals.py | 55 ++++++++---------------------- 3 files changed, 36 insertions(+), 44 deletions(-) diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index 4a52c847e..7cc8dfbdf 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -196,9 +196,9 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): if not should_cache: allowed_applications = self._get_allowed_applications(queryset) if should_cache: - LOGGER.debug("Caching allowed application list") allowed_applications = cache.get(user_app_cache_key(self.request.user.pk)) if not allowed_applications: + LOGGER.debug("Caching allowed application list") allowed_applications = self._get_allowed_applications(queryset) cache.set( user_app_cache_key(self.request.user.pk), diff --git a/authentik/events/middleware.py b/authentik/events/middleware.py index 9a70775bc..fd0537d25 100644 --- a/authentik/events/middleware.py +++ b/authentik/events/middleware.py @@ -1,6 +1,7 @@ """Events middleware""" from functools import partial -from typing import Callable +from threading import Thread +from typing import Any, Callable, Optional from django.conf import settings from django.contrib.sessions.models import Session @@ -13,7 +14,6 @@ from guardian.models import UserObjectPermission from authentik.core.models import AuthenticatedSession, User from authentik.events.models import Event, EventAction, Notification -from authentik.events.signals import EventNewThread from authentik.events.utils import model_to_dict from authentik.flows.models import FlowToken from authentik.lib.sentry import before_send @@ -37,6 +37,25 @@ def should_log_model(model: Model) -> bool: return not isinstance(model, IGNORED_MODELS) +class EventNewThread(Thread): + """Create Event in background thread""" + + action: str + request: HttpRequest + kwargs: dict[str, Any] + user: Optional[User] = None + + def __init__(self, action: str, request: HttpRequest, user: Optional[User] = None, **kwargs): + super().__init__() + self.action = action + self.request = request + self.user = user + self.kwargs = kwargs + + def run(self): + Event.new(self.action, **self.kwargs).from_http(self.request, user=self.user) + + class AuditMiddleware: """Register handlers for duration of request-response that log creation/update/deletion of models""" diff --git a/authentik/events/signals.py b/authentik/events/signals.py index bc730edfb..d959d3d7a 100644 --- a/authentik/events/signals.py +++ b/authentik/events/signals.py @@ -1,5 +1,4 @@ """authentik events signal listener""" -from threading import Thread from typing import Any, Optional from django.contrib.auth.signals import user_logged_in, user_logged_out @@ -19,63 +18,40 @@ from authentik.stages.invitation.signals import invitation_used from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS from authentik.stages.user_write.signals import user_write - -class EventNewThread(Thread): - """Create Event in background thread""" - - action: str - request: HttpRequest - kwargs: dict[str, Any] - user: Optional[User] = None - - def __init__(self, action: str, request: HttpRequest, user: Optional[User] = None, **kwargs): - super().__init__() - self.action = action - self.request = request - self.user = user - self.kwargs = kwargs - - def run(self): - Event.new(self.action, **self.kwargs).from_http(self.request, user=self.user) +SESSION_LOGIN_EVENT = "login_event" @receiver(user_logged_in) # pylint: disable=unused-argument def on_user_logged_in(sender, request: HttpRequest, user: User, **_): """Log successful login""" - thread = EventNewThread(EventAction.LOGIN, request) + kwargs = {} if SESSION_KEY_PLAN in request.session: flow_plan: FlowPlan = request.session[SESSION_KEY_PLAN] if PLAN_CONTEXT_SOURCE in flow_plan.context: # Login request came from an external source, save it in the context - thread.kwargs[PLAN_CONTEXT_SOURCE] = flow_plan.context[PLAN_CONTEXT_SOURCE] + kwargs[PLAN_CONTEXT_SOURCE] = flow_plan.context[PLAN_CONTEXT_SOURCE] if PLAN_CONTEXT_METHOD in flow_plan.context: - thread.kwargs[PLAN_CONTEXT_METHOD] = flow_plan.context[PLAN_CONTEXT_METHOD] # Save the login method used - thread.kwargs[PLAN_CONTEXT_METHOD_ARGS] = flow_plan.context.get( - PLAN_CONTEXT_METHOD_ARGS, {} - ) - thread.user = user - thread.run() + kwargs[PLAN_CONTEXT_METHOD] = flow_plan.context[PLAN_CONTEXT_METHOD] + kwargs[PLAN_CONTEXT_METHOD_ARGS] = flow_plan.context.get(PLAN_CONTEXT_METHOD_ARGS, {}) + event = Event.new(EventAction.LOGIN, **kwargs).from_http(request, user=user) + request.session[SESSION_LOGIN_EVENT] = event @receiver(user_logged_out) # pylint: disable=unused-argument def on_user_logged_out(sender, request: HttpRequest, user: User, **_): """Log successfully logout""" - thread = EventNewThread(EventAction.LOGOUT, request) - thread.user = user - thread.run() + Event.new(EventAction.LOGOUT).from_http(request, user=user) @receiver(user_write) # pylint: disable=unused-argument def on_user_write(sender, request: HttpRequest, user: User, data: dict[str, Any], **kwargs): """Log User write""" - thread = EventNewThread(EventAction.USER_WRITE, request, **data) - thread.kwargs["created"] = kwargs.get("created", False) - thread.user = user - thread.run() + data["created"] = kwargs.get("created", False) + Event.new(EventAction.USER_WRITE, **data).from_http(request, user=user) @receiver(login_failed) @@ -89,26 +65,23 @@ def on_login_failed( **kwargs, ): """Failed Login, authentik custom event""" - thread = EventNewThread(EventAction.LOGIN_FAILED, request, **credentials, stage=stage, **kwargs) - thread.run() + Event.new(EventAction.USER_WRITE, **credentials, stage=stage, **kwargs).from_http(request) @receiver(invitation_used) # pylint: disable=unused-argument def on_invitation_used(sender, request: HttpRequest, invitation: Invitation, **_): """Log Invitation usage""" - thread = EventNewThread( - EventAction.INVITE_USED, request, invitation_uuid=invitation.invite_uuid.hex + Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.invite_uuid.hex).from_http( + request ) - thread.run() @receiver(password_changed) # pylint: disable=unused-argument def on_password_changed(sender, user: User, password: str, **_): """Log password change""" - thread = EventNewThread(EventAction.PASSWORD_SET, None, user=user) - thread.run() + Event.new(EventAction.PASSWORD_SET).from_http(None, user=user) @receiver(post_save, sender=Event)