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:
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),

View file

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

View file

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