events: improve handling creation of events with non-pickleable objects

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-12-01 15:56:28 +02:00
parent 93fee5f0e5
commit 3251bdc220
3 changed files with 13 additions and 5 deletions

View file

@ -1,7 +1,6 @@
"""authentik events models""" """authentik events models"""
import time import time
from collections import Counter from collections import Counter
from copy import deepcopy
from datetime import timedelta from datetime import timedelta
from inspect import currentframe from inspect import currentframe
from smtplib import SMTPException from smtplib import SMTPException
@ -211,7 +210,7 @@ class Event(SerializerModel, ExpiringModel):
current = currentframe() current = currentframe()
parent = current.f_back parent = current.f_back
app = parent.f_globals["__name__"] app = parent.f_globals["__name__"]
cleaned_kwargs = cleanse_dict(sanitize_dict(deepcopy(kwargs))) cleaned_kwargs = cleanse_dict(sanitize_dict(kwargs))
event = Event(action=action, app=app, context=cleaned_kwargs) event = Event(action=action, app=app, context=cleaned_kwargs)
return event return event

View file

@ -1,5 +1,6 @@
"""event utilities""" """event utilities"""
import re import re
from copy import copy
from dataclasses import asdict, is_dataclass from dataclasses import asdict, is_dataclass
from pathlib import Path from pathlib import Path
from types import GeneratorType from types import GeneratorType
@ -87,9 +88,15 @@ def sanitize_item(value: Any) -> Any:
"""Sanitize a single item, ensure it is JSON parsable""" """Sanitize a single item, ensure it is JSON parsable"""
if is_dataclass(value): if is_dataclass(value):
# Because asdict calls `copy.deepcopy(obj)` on everything that's not tuple/dict, # Because asdict calls `copy.deepcopy(obj)` on everything that's not tuple/dict,
# and deepcopy doesn't work with HttpRequests (neither django nor rest_framework). # and deepcopy doesn't work with HttpRequest (neither django nor rest_framework).
# (more specifically doesn't work with ResolverMatch)
# rest_framework's custom Request class makes this more complicated as it also holds a
# thread lock.
# Since this class is mainly used for Events which already hold the http request context
# we just remove the http_request from the shallow policy request
# Currently, the only dataclass that actually holds an http request is a PolicyRequest # Currently, the only dataclass that actually holds an http request is a PolicyRequest
if isinstance(value, PolicyRequest): if isinstance(value, PolicyRequest) and value.http_request is not None:
value: PolicyRequest = copy(value)
value.http_request = None value.http_request = None
value = asdict(value) value = asdict(value)
if isinstance(value, dict): if isinstance(value, dict):

View file

@ -2,6 +2,7 @@
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.core.cache import cache from django.core.cache import cache
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from django.urls import resolve, reverse
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from authentik.core.models import Application, Group, User from authentik.core.models import Application, Group, User
@ -129,8 +130,9 @@ class TestPolicyProcess(TestCase):
) )
binding = PolicyBinding(policy=policy, target=Application.objects.create(name="test")) binding = PolicyBinding(policy=policy, target=Application.objects.create(name="test"))
http_request = self.factory.get("/") http_request = self.factory.get(reverse("authentik_core:impersonate-end"))
http_request.user = self.user http_request.user = self.user
http_request.resolver_match = resolve(reverse("authentik_core:impersonate-end"))
request = PolicyRequest(self.user) request = PolicyRequest(self.user)
request.set_http_request(http_request) request.set_http_request(http_request)