Skip to content

Commit a908d9d

Browse files
committed
Fix user handling for django ASGI
1 parent f5c51fc commit a908d9d

File tree

4 files changed

+118
-12
lines changed

4 files changed

+118
-12
lines changed

sentry_sdk/integrations/django/asgi.py

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,45 @@
3535
_F = TypeVar("_F", bound=Callable[..., Any])
3636

3737

38+
async def _get_user_info(request: "ASGIRequest") -> "dict[str, str]":
39+
user_info = {} # type: dict[str, str]
40+
41+
if not hasattr(request, "auser"):
42+
return user_info
43+
44+
try:
45+
user = await request.auser()
46+
except Exception:
47+
return user_info
48+
49+
from sentry_sdk.integrations.django import is_authenticated
50+
51+
if user is None or not is_authenticated(user):
52+
return user_info
53+
54+
try:
55+
user_info["id"] = str(user.pk)
56+
except Exception:
57+
pass
58+
59+
try:
60+
user_info["email"] = user.email
61+
except Exception:
62+
pass
63+
64+
try:
65+
user_info["username"] = user.get_username()
66+
except Exception:
67+
pass
68+
69+
return user_info
70+
71+
72+
def _set_user_info(request: "ASGIRequest", event: "Event") -> None:
73+
user_info = getattr(request, "_sentry_user_info", {})
74+
event.setdefault("user", user_info)
75+
76+
3877
# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for
3978
# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker.
4079
# The latter is replaced with the inspect.markcoroutinefunction decorator.
@@ -56,10 +95,7 @@ def asgi_request_event_processor(event: "Event", hint: "dict[str, Any]") -> "Eve
5695
# if the request is gone we are fine not logging the data from
5796
# it. This might happen if the processor is pushed away to
5897
# another thread.
59-
from sentry_sdk.integrations.django import (
60-
DjangoRequestExtractor,
61-
_set_user_info,
62-
)
98+
from sentry_sdk.integrations.django import DjangoRequestExtractor
6399

64100
if request is None:
65101
return event
@@ -178,16 +214,22 @@ async def sentry_wrapped_callback(
178214
if sentry_scope.profile is not None:
179215
sentry_scope.profile.update_active_thread_id()
180216

217+
if should_send_default_pii():
218+
with capture_internal_exceptions():
219+
request._sentry_user_info = await _get_user_info(request)
220+
181221
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
182222
if not integration or not integration.middleware_spans:
183-
return await callback(request, *args, **kwargs)
184-
185-
with sentry_sdk.start_span(
186-
op=OP.VIEW_RENDER,
187-
name=request.resolver_match.view_name,
188-
origin=DjangoIntegration.origin,
189-
):
190-
return await callback(request, *args, **kwargs)
223+
result = await callback(request, *args, **kwargs)
224+
else:
225+
with sentry_sdk.start_span(
226+
op=OP.VIEW_RENDER,
227+
name=request.resolver_match.view_name,
228+
origin=DjangoIntegration.origin,
229+
):
230+
result = await callback(request, *args, **kwargs)
231+
232+
return result
191233

192234
return sentry_wrapped_callback
193235

tests/integrations/django/asgi/test_asgi.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
import django
1010
import pytest
1111
from channels.testing import HttpCommunicator
12+
1213
from sentry_sdk import capture_message
1314
from sentry_sdk.integrations.django import DjangoIntegration
1415
from sentry_sdk.integrations.django.asgi import _asgi_middleware_mixin_factory
1516
from tests.integrations.django.myapp.asgi import channels_application
17+
from tests.integrations.django.utils import pytest_mark_django_db_decorator
1618

1719
try:
1820
from django.urls import reverse
@@ -737,3 +739,49 @@ async def test_transaction_http_method_custom(sentry_init, capture_events, appli
737739
(event1, event2) = events
738740
assert event1["request"]["method"] == "OPTIONS"
739741
assert event2["request"]["method"] == "HEAD"
742+
743+
744+
@pytest.mark.asyncio
745+
@pytest.mark.forked
746+
@pytest_mark_django_db_decorator()
747+
@pytest.mark.skipif(
748+
django.VERSION < (3, 0), reason="Django ASGI support shipped in 3.0"
749+
)
750+
async def test_user_pii_in_asgi_with_auth(sentry_init, capture_events, settings):
751+
settings.MIDDLEWARE = [
752+
"django.contrib.sessions.middleware.SessionMiddleware",
753+
"django.contrib.auth.middleware.AuthenticationMiddleware",
754+
]
755+
756+
asgi_application.load_middleware(is_async=True)
757+
758+
sentry_init(
759+
integrations=[DjangoIntegration()],
760+
send_default_pii=True,
761+
)
762+
763+
events = capture_events()
764+
765+
comm = HttpCommunicator(asgi_application, "GET", "/async_mylogin")
766+
response = await comm.get_response()
767+
await comm.wait()
768+
769+
assert response["status"] == 200
770+
771+
# Get session cookie from login response
772+
set_cookie = next(v for k, v in response["headers"] if k.lower() == b"set-cookie")
773+
headers = [(b"cookie", set_cookie)]
774+
775+
comm = HttpCommunicator(asgi_application, "GET", "/async_message", headers=headers)
776+
response = await comm.get_response()
777+
await comm.wait()
778+
779+
assert response["status"] == 200
780+
781+
(event,) = events
782+
assert event["message"] == "hi"
783+
assert event["user"] == {
784+
"email": "lennon@thebeatles.com",
785+
"username": "john_async",
786+
"id": "1",
787+
}

tests/integrations/django/myapp/urls.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ def path(path, *args, **kwargs):
110110
]
111111

112112
# async views
113+
if views.async_mylogin is not None:
114+
urlpatterns.append(path("async_mylogin", views.async_mylogin, name="async_mylogin"))
115+
113116
if views.async_message is not None:
114117
urlpatterns.append(path("async_message", views.async_message, name="async_message"))
115118

tests/integrations/django/myapp/views.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,19 @@ def mylogin(request):
136136
return HttpResponse("ok")
137137

138138

139+
@csrf_exempt
140+
async def async_mylogin(request):
141+
user = await User.objects.acreate_user(
142+
"john_async", "lennon@thebeatles.com", "johnpassword"
143+
)
144+
user.backend = "django.contrib.auth.backends.ModelBackend"
145+
146+
from django.contrib.auth import alogin
147+
148+
await alogin(request, user)
149+
return HttpResponse("ok")
150+
151+
139152
@csrf_exempt
140153
def handler500(request):
141154
return HttpResponseServerError("Sentry error.")

0 commit comments

Comments
 (0)