events: save login event in session after login

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#4070
This commit is contained in:
Jens Langhammer 2022-11-25 00:01:57 +01:00
parent f22f1ebcde
commit 5019346ab6
3 changed files with 36 additions and 44 deletions

View File

@ -196,9 +196,9 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
if not should_cache: if not should_cache:
allowed_applications = self._get_allowed_applications(queryset) allowed_applications = self._get_allowed_applications(queryset)
if should_cache: if should_cache:
LOGGER.debug("Caching allowed application list")
allowed_applications = cache.get(user_app_cache_key(self.request.user.pk)) allowed_applications = cache.get(user_app_cache_key(self.request.user.pk))
if not allowed_applications: if not allowed_applications:
LOGGER.debug("Caching allowed application list")
allowed_applications = self._get_allowed_applications(queryset) allowed_applications = self._get_allowed_applications(queryset)
cache.set( cache.set(
user_app_cache_key(self.request.user.pk), user_app_cache_key(self.request.user.pk),

View File

@ -1,6 +1,7 @@
"""Events middleware""" """Events middleware"""
from functools import partial 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.conf import settings
from django.contrib.sessions.models import Session 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.core.models import AuthenticatedSession, User
from authentik.events.models import Event, EventAction, Notification from authentik.events.models import Event, EventAction, Notification
from authentik.events.signals import EventNewThread
from authentik.events.utils import model_to_dict from authentik.events.utils import model_to_dict
from authentik.flows.models import FlowToken from authentik.flows.models import FlowToken
from authentik.lib.sentry import before_send from authentik.lib.sentry import before_send
@ -37,6 +37,25 @@ def should_log_model(model: Model) -> bool:
return not isinstance(model, IGNORED_MODELS) 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: class AuditMiddleware:
"""Register handlers for duration of request-response that log creation/update/deletion """Register handlers for duration of request-response that log creation/update/deletion
of models""" of models"""

View File

@ -1,5 +1,4 @@
"""authentik events signal listener""" """authentik events signal listener"""
from threading import Thread
from typing import Any, Optional from typing import Any, Optional
from django.contrib.auth.signals import user_logged_in, user_logged_out 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.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
from authentik.stages.user_write.signals import user_write from authentik.stages.user_write.signals import user_write
SESSION_LOGIN_EVENT = "login_event"
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)
@receiver(user_logged_in) @receiver(user_logged_in)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def on_user_logged_in(sender, request: HttpRequest, user: User, **_): def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
"""Log successful login""" """Log successful login"""
thread = EventNewThread(EventAction.LOGIN, request) kwargs = {}
if SESSION_KEY_PLAN in request.session: if SESSION_KEY_PLAN in request.session:
flow_plan: FlowPlan = request.session[SESSION_KEY_PLAN] flow_plan: FlowPlan = request.session[SESSION_KEY_PLAN]
if PLAN_CONTEXT_SOURCE in flow_plan.context: if PLAN_CONTEXT_SOURCE in flow_plan.context:
# Login request came from an external source, save it in the 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: if PLAN_CONTEXT_METHOD in flow_plan.context:
thread.kwargs[PLAN_CONTEXT_METHOD] = flow_plan.context[PLAN_CONTEXT_METHOD]
# Save the login method used # Save the login method used
thread.kwargs[PLAN_CONTEXT_METHOD_ARGS] = flow_plan.context.get( kwargs[PLAN_CONTEXT_METHOD] = flow_plan.context[PLAN_CONTEXT_METHOD]
PLAN_CONTEXT_METHOD_ARGS, {} kwargs[PLAN_CONTEXT_METHOD_ARGS] = flow_plan.context.get(PLAN_CONTEXT_METHOD_ARGS, {})
) event = Event.new(EventAction.LOGIN, **kwargs).from_http(request, user=user)
thread.user = user request.session[SESSION_LOGIN_EVENT] = event
thread.run()
@receiver(user_logged_out) @receiver(user_logged_out)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def on_user_logged_out(sender, request: HttpRequest, user: User, **_): def on_user_logged_out(sender, request: HttpRequest, user: User, **_):
"""Log successfully logout""" """Log successfully logout"""
thread = EventNewThread(EventAction.LOGOUT, request) Event.new(EventAction.LOGOUT).from_http(request, user=user)
thread.user = user
thread.run()
@receiver(user_write) @receiver(user_write)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def on_user_write(sender, request: HttpRequest, user: User, data: dict[str, Any], **kwargs): def on_user_write(sender, request: HttpRequest, user: User, data: dict[str, Any], **kwargs):
"""Log User write""" """Log User write"""
thread = EventNewThread(EventAction.USER_WRITE, request, **data) data["created"] = kwargs.get("created", False)
thread.kwargs["created"] = kwargs.get("created", False) Event.new(EventAction.USER_WRITE, **data).from_http(request, user=user)
thread.user = user
thread.run()
@receiver(login_failed) @receiver(login_failed)
@ -89,26 +65,23 @@ def on_login_failed(
**kwargs, **kwargs,
): ):
"""Failed Login, authentik custom event""" """Failed Login, authentik custom event"""
thread = EventNewThread(EventAction.LOGIN_FAILED, request, **credentials, stage=stage, **kwargs) Event.new(EventAction.USER_WRITE, **credentials, stage=stage, **kwargs).from_http(request)
thread.run()
@receiver(invitation_used) @receiver(invitation_used)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def on_invitation_used(sender, request: HttpRequest, invitation: Invitation, **_): def on_invitation_used(sender, request: HttpRequest, invitation: Invitation, **_):
"""Log Invitation usage""" """Log Invitation usage"""
thread = EventNewThread( Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.invite_uuid.hex).from_http(
EventAction.INVITE_USED, request, invitation_uuid=invitation.invite_uuid.hex request
) )
thread.run()
@receiver(password_changed) @receiver(password_changed)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def on_password_changed(sender, user: User, password: str, **_): def on_password_changed(sender, user: User, password: str, **_):
"""Log password change""" """Log password change"""
thread = EventNewThread(EventAction.PASSWORD_SET, None, user=user) Event.new(EventAction.PASSWORD_SET).from_http(None, user=user)
thread.run()
@receiver(post_save, sender=Event) @receiver(post_save, sender=Event)