events: save login event in session after login
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> #4070
This commit is contained in:
		@ -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),
 | 
			
		||||
 | 
			
		||||
@ -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"""
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user