Skip to content

Commit 19dd848

Browse files
committed
Merge branch 'fix_auth' of github.com:bellini666/django-ninja into bellini666-fix_auth
2 parents ce38a08 + 907224c commit 19dd848

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

ninja/operation.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
)
1616

1717
import pydantic
18-
from asgiref.sync import async_to_sync
18+
from asgiref.sync import async_to_sync, sync_to_async
1919
from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed
2020
from django.http.response import HttpResponseBase
2121

@@ -387,7 +387,7 @@ async def _run_authentication(self, request: HttpRequest) -> Optional[HttpRespon
387387
else:
388388
result = await cor
389389
else:
390-
result = callback(request)
390+
result = await sync_to_async(callback)(request)
391391
except Exception as exc:
392392
return self.api.on_exception(request, exc)
393393

@@ -501,8 +501,6 @@ def _sync_view(self, request: HttpRequest, *a: Any, **kw: Any) -> HttpResponseBa
501501
async def _async_view(
502502
self, request: HttpRequest, *a: Any, **kw: Any
503503
) -> HttpResponseBase:
504-
from asgiref.sync import sync_to_async
505-
506504
operation = self._find_operation(request)
507505
if operation is None:
508506
return self._not_allowed()

tests/test_auth.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from unittest.mock import Mock
22

33
import pytest
4+
from django.utils.asyncio import async_unsafe
45

56
from ninja import NinjaAPI
67
from ninja.errors import AuthorizationError, ConfigError
@@ -16,6 +17,7 @@
1617
)
1718
from ninja.security.base import AuthBase
1819
from ninja.testing import TestClient
20+
from ninja.testing.client import TestAsyncClient
1921

2022

2123
def callable_auth(request):
@@ -315,3 +317,46 @@ class MyAuth2(AuthBase):
315317

316318
with pytest.raises(TypeError):
317319
HttpBasicAuth()(request)
320+
321+
322+
@pytest.mark.asyncio
323+
async def test_async_auth():
324+
_sync_auth_called = False
325+
_async_auth_called = False
326+
_async_unsafe_func_called = False
327+
328+
# This is the same decorator Django uses to mark its ORM functions as async unsafe,
329+
# which in turns raises a `SynchronousOnlyOperation` error if called
330+
# without `sync_to_async`.
331+
@async_unsafe("called without sync_to_async")
332+
def async_unsafe_function():
333+
nonlocal _async_unsafe_func_called
334+
_async_unsafe_func_called = True
335+
336+
class AsyncAuth(APIKeyQuery):
337+
async def authenticate(self, request, key):
338+
nonlocal _async_auth_called
339+
_async_auth_called = True
340+
return False
341+
342+
class SyncAuth(APIKeyQuery):
343+
def authenticate(self, request, key):
344+
async_unsafe_function()
345+
nonlocal _sync_auth_called
346+
_sync_auth_called = True
347+
return True
348+
349+
async def handle_request(request):
350+
return {"ok": True}
351+
352+
api = NinjaAPI(csrf=True)
353+
api.get("/foobar", auth=[AsyncAuth(), SyncAuth()])(handle_request)
354+
355+
client = TestAsyncClient(api)
356+
response = await client.get("/foobar")
357+
assert response.status_code == 200
358+
assert response.json() == {"ok": True}
359+
360+
assert _sync_auth_called is True
361+
assert _async_auth_called is True
362+
assert _async_unsafe_func_called is True

0 commit comments

Comments
 (0)