events: fix race condition (#10602)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2024-07-24 16:49:02 +02:00
committed by GitHub
parent 4101168552
commit 49a2a3ba05
3 changed files with 14 additions and 1 deletions

View File

@ -35,6 +35,7 @@ IGNORED_MODELS = tuple(
_CTX_OVERWRITE_USER = ContextVar[User | None]("authentik_events_log_overwrite_user", default=None) _CTX_OVERWRITE_USER = ContextVar[User | None]("authentik_events_log_overwrite_user", default=None)
_CTX_IGNORE = ContextVar[bool]("authentik_events_log_ignore", default=False) _CTX_IGNORE = ContextVar[bool]("authentik_events_log_ignore", default=False)
_CTX_REQUEST = ContextVar[HttpRequest | None]("authentik_events_log_request", default=None)
def should_log_model(model: Model) -> bool: def should_log_model(model: Model) -> bool:
@ -149,11 +150,13 @@ class AuditMiddleware:
m2m_changed.disconnect(dispatch_uid=request.request_id) m2m_changed.disconnect(dispatch_uid=request.request_id)
def __call__(self, request: HttpRequest) -> HttpResponse: def __call__(self, request: HttpRequest) -> HttpResponse:
_CTX_REQUEST.set(request)
self.connect(request) self.connect(request)
response = self.get_response(request) response = self.get_response(request)
self.disconnect(request) self.disconnect(request)
_CTX_REQUEST.set(None)
return response return response
def process_exception(self, request: HttpRequest, exception: Exception): def process_exception(self, request: HttpRequest, exception: Exception):
@ -167,7 +170,7 @@ class AuditMiddleware:
thread = EventNewThread( thread = EventNewThread(
EventAction.SUSPICIOUS_REQUEST, EventAction.SUSPICIOUS_REQUEST,
request, request,
message=str(exception), message=exception_to_string(exception),
) )
thread.run() thread.run()
elif before_send({}, {"exc_info": (None, exception, None)}) is not None: elif before_send({}, {"exc_info": (None, exception, None)}) is not None:
@ -192,6 +195,8 @@ class AuditMiddleware:
return return
if _CTX_IGNORE.get(): if _CTX_IGNORE.get():
return return
if request.request_id != _CTX_REQUEST.get().request_id:
return
user = self.get_user(request) user = self.get_user(request)
action = EventAction.MODEL_CREATED if created else EventAction.MODEL_UPDATED action = EventAction.MODEL_CREATED if created else EventAction.MODEL_UPDATED
@ -205,6 +210,8 @@ class AuditMiddleware:
return return
if _CTX_IGNORE.get(): if _CTX_IGNORE.get():
return return
if request.request_id != _CTX_REQUEST.get().request_id:
return
user = self.get_user(request) user = self.get_user(request)
EventNewThread( EventNewThread(
@ -230,6 +237,8 @@ class AuditMiddleware:
return return
if _CTX_IGNORE.get(): if _CTX_IGNORE.get():
return return
if request.request_id != _CTX_REQUEST.get().request_id:
return
user = self.get_user(request) user = self.get_user(request)
EventNewThread( EventNewThread(

View File

@ -238,6 +238,8 @@ class Event(SerializerModel, ExpiringModel):
"args": cleanse_dict(QueryDict(request.META.get("QUERY_STRING", ""))), "args": cleanse_dict(QueryDict(request.META.get("QUERY_STRING", ""))),
"user_agent": request.META.get("HTTP_USER_AGENT", ""), "user_agent": request.META.get("HTTP_USER_AGENT", ""),
} }
if hasattr(request, "request_id"):
self.context["http_request"]["request_id"] = request.request_id
# Special case for events created during flow execution # Special case for events created during flow execution
# since they keep the http query within a wrapped query # since they keep the http query within a wrapped query
if QS_QUERY in self.context["http_request"]["args"]: if QS_QUERY in self.context["http_request"]["args"]:

View File

@ -8,6 +8,7 @@ from django.urls import reverse
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from authentik.brands.utils import get_brand_for_request from authentik.brands.utils import get_brand_for_request
from authentik.core.middleware import RESPONSE_HEADER_ID
from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.flows.models import FlowDesignation, FlowStageBinding from authentik.flows.models import FlowDesignation, FlowStageBinding
@ -186,6 +187,7 @@ class AuthenticatorValidateStageDuoTests(FlowTestCase):
"method": "GET", "method": "GET",
"path": f"/api/v3/flows/executor/{flow.slug}/", "path": f"/api/v3/flows/executor/{flow.slug}/",
"user_agent": "", "user_agent": "",
"request_id": response[RESPONSE_HEADER_ID],
}, },
}, },
) )