Skip to content

Commit 4e31ab4

Browse files
authored
Merge pull request #203 from eadwinCode/async_session_bug
fix: Async session auth
2 parents eb4cc89 + 5e0bb38 commit 4e31ab4

File tree

5 files changed

+80
-35
lines changed

5 files changed

+80
-35
lines changed

ninja_extra/permissions/base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ def __invert__( # type:ignore[misc]
4848
return SingleOperandHolder(NOT, self)
4949

5050

51-
class BasePermissionMetaclass(OperationHolderMixin, ABCMeta):
52-
...
51+
class BasePermissionMetaclass(OperationHolderMixin, ABCMeta): ...
5352

5453

5554
class BasePermission(ABC, metaclass=BasePermissionMetaclass): # pragma: no cover

ninja_extra/permissions/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class IsAdminUser(BasePermission):
3838
"""
3939
Allows access only to admin users.
4040
"""
41+
4142
message: str = "You must be an admin user to access this resource."
4243

4344
def has_permission(

ninja_extra/security/session.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.conf import settings
22
from django.http import HttpRequest
3+
from ninja.signature import is_async
34

45
from ninja_extra.security.api_key import AsyncAPIKeyCookie
56

@@ -16,6 +17,11 @@ class AsyncSessionAuth(AsyncAPIKeyCookie):
1617
async def authenticate(
1718
self, request: HttpRequest, key: Optional[str]
1819
) -> Optional[Any]:
19-
if request.user.is_authenticated:
20-
return request.user
20+
if hasattr(request, "auser") and is_async(request.auser):
21+
current_user = await request.auser()
22+
else:
23+
current_user = request.user
24+
25+
if current_user.is_authenticated:
26+
return current_user
2127
return None

tests/test_permissions.py

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313

1414
class TestPermissionsCompositions:
15-
1615
@classmethod
1716
def get_real_user_request(cls):
1817
_request = Mock()
@@ -43,8 +42,8 @@ def test_is_authenticated_and_read_only(self, method, auth, result):
4342
request = self.get_real_user_request()
4443
request.method = method
4544
assert (
46-
permissions.IsAuthenticatedOrReadOnly().has_permission(request, Mock())
47-
== result
45+
permissions.IsAuthenticatedOrReadOnly().has_permission(request, Mock())
46+
== result
4847
)
4948

5049
def test_and_false(self):
@@ -83,8 +82,8 @@ def test_not_false(self):
8382
composed_perm = ~permissions.IsAuthenticated
8483
assert composed_perm().has_permission(anonymous_request, None) is True
8584
assert (
86-
composed_perm().has_object_permission(anonymous_request, None, None)
87-
is False
85+
composed_perm().has_object_permission(anonymous_request, None, None)
86+
is False
8887
)
8988
# Message
9089
assert composed_perm().message == permissions.IsAuthenticated.message
@@ -99,10 +98,10 @@ def test_not_true(self):
9998
def test_several_levels_without_negation(self):
10099
request = self.get_real_user_request()
101100
composed_perm = (
102-
permissions.IsAuthenticated
103-
& permissions.IsAuthenticated
104-
& permissions.IsAuthenticated
105-
& permissions.IsAuthenticated
101+
permissions.IsAuthenticated
102+
& permissions.IsAuthenticated
103+
& permissions.IsAuthenticated
104+
& permissions.IsAuthenticated
106105
)
107106
assert composed_perm().has_permission(request, None) is True
108107
assert composed_perm().has_object_permission(request, None, None) is True
@@ -111,28 +110,28 @@ def test_several_levels_without_negation(self):
111110
def test_several_levels_and_precedence_with_negation(self):
112111
request = self.get_real_user_request()
113112
composed_perm = (
114-
permissions.IsAuthenticated
115-
& ~permissions.IsAdminUser
116-
& permissions.IsAuthenticated
117-
& ~(permissions.IsAdminUser & permissions.IsAdminUser)
113+
permissions.IsAuthenticated
114+
& ~permissions.IsAdminUser
115+
& permissions.IsAuthenticated
116+
& ~(permissions.IsAdminUser & permissions.IsAdminUser)
118117
)
119118
assert composed_perm().has_permission(request, None) is True
120119

121120
@pytest.mark.django_db
122121
def test_several_levels_and_precedence(self):
123122
request = self.get_real_user_request()
124123
composed_perm = (
125-
permissions.IsAuthenticated & permissions.IsAuthenticated
126-
| permissions.IsAuthenticated & permissions.IsAuthenticated
124+
permissions.IsAuthenticated & permissions.IsAuthenticated
125+
| permissions.IsAuthenticated & permissions.IsAuthenticated
127126
)
128127
assert composed_perm().has_permission(request, None) is True
129128

130129
def test_or_lazyness(self):
131130
with mock.patch.object(
132-
permissions.AllowAny, "has_permission", return_value=True
131+
permissions.AllowAny, "has_permission", return_value=True
133132
) as mock_allow:
134133
with mock.patch.object(
135-
permissions.IsAuthenticated, "has_permission", return_value=False
134+
permissions.IsAuthenticated, "has_permission", return_value=False
136135
) as mock_deny:
137136
composed_perm = permissions.AllowAny | permissions.IsAuthenticated
138137
hasperm = composed_perm().has_permission(anonymous_request, None)
@@ -141,10 +140,10 @@ def test_or_lazyness(self):
141140
mock_deny.assert_not_called()
142141

143142
with mock.patch.object(
144-
permissions.AllowAny, "has_permission", return_value=True
143+
permissions.AllowAny, "has_permission", return_value=True
145144
) as mock_allow:
146145
with mock.patch.object(
147-
permissions.IsAuthenticated, "has_permission", return_value=False
146+
permissions.IsAuthenticated, "has_permission", return_value=False
148147
) as mock_deny:
149148
composed_perm = permissions.IsAuthenticated | permissions.AllowAny
150149
hasperm = composed_perm().has_permission(anonymous_request, None)
@@ -154,10 +153,10 @@ def test_or_lazyness(self):
154153

155154
def test_object_or_lazyness(self):
156155
with mock.patch.object(
157-
permissions.AllowAny, "has_object_permission", return_value=True
156+
permissions.AllowAny, "has_object_permission", return_value=True
158157
) as mock_allow:
159158
with mock.patch.object(
160-
permissions.IsAuthenticated, "has_object_permission", return_value=False
159+
permissions.IsAuthenticated, "has_object_permission", return_value=False
161160
) as mock_deny:
162161
composed_perm = permissions.AllowAny | permissions.IsAuthenticated
163162
hasperm = composed_perm().has_object_permission(
@@ -168,10 +167,10 @@ def test_object_or_lazyness(self):
168167
mock_deny.assert_not_called()
169168

170169
with mock.patch.object(
171-
permissions.AllowAny, "has_object_permission", return_value=True
170+
permissions.AllowAny, "has_object_permission", return_value=True
172171
) as mock_allow:
173172
with mock.patch.object(
174-
permissions.IsAuthenticated, "has_object_permission", return_value=False
173+
permissions.IsAuthenticated, "has_object_permission", return_value=False
175174
) as mock_deny:
176175
composed_perm = permissions.IsAuthenticated | permissions.AllowAny
177176
hasperm = composed_perm().has_object_permission(
@@ -183,10 +182,10 @@ def test_object_or_lazyness(self):
183182

184183
def test_and_lazyness(self):
185184
with mock.patch.object(
186-
permissions.AllowAny, "has_permission", return_value=True
185+
permissions.AllowAny, "has_permission", return_value=True
187186
) as mock_allow:
188187
with mock.patch.object(
189-
permissions.IsAuthenticated, "has_permission", return_value=False
188+
permissions.IsAuthenticated, "has_permission", return_value=False
190189
) as mock_deny:
191190
composed_perm = permissions.AllowAny & permissions.IsAuthenticated
192191
hasperm = composed_perm().has_permission(anonymous_request, None)
@@ -195,10 +194,10 @@ def test_and_lazyness(self):
195194
assert mock_deny.call_count == 1
196195

197196
with mock.patch.object(
198-
permissions.AllowAny, "has_permission", return_value=True
197+
permissions.AllowAny, "has_permission", return_value=True
199198
) as mock_allow:
200199
with mock.patch.object(
201-
permissions.IsAuthenticated, "has_permission", return_value=False
200+
permissions.IsAuthenticated, "has_permission", return_value=False
202201
) as mock_deny:
203202
composed_perm = permissions.IsAuthenticated & permissions.AllowAny
204203
hasperm = composed_perm().has_permission(anonymous_request, None)
@@ -208,10 +207,10 @@ def test_and_lazyness(self):
208207

209208
def test_object_and_lazyness(self):
210209
with mock.patch.object(
211-
permissions.AllowAny, "has_object_permission", return_value=True
210+
permissions.AllowAny, "has_object_permission", return_value=True
212211
) as mock_allow:
213212
with mock.patch.object(
214-
permissions.IsAuthenticated, "has_object_permission", return_value=False
213+
permissions.IsAuthenticated, "has_object_permission", return_value=False
215214
) as mock_deny:
216215
composed_perm = permissions.AllowAny & permissions.IsAuthenticated
217216
hasperm = composed_perm().has_object_permission(
@@ -222,10 +221,10 @@ def test_object_and_lazyness(self):
222221
assert mock_deny.call_count == 1
223222

224223
with mock.patch.object(
225-
permissions.AllowAny, "has_object_permission", return_value=True
224+
permissions.AllowAny, "has_object_permission", return_value=True
226225
) as mock_allow:
227226
with mock.patch.object(
228-
permissions.IsAuthenticated, "has_object_permission", return_value=False
227+
permissions.IsAuthenticated, "has_object_permission", return_value=False
229228
) as mock_deny:
230229
composed_perm = permissions.IsAuthenticated & permissions.AllowAny
231230
hasperm = composed_perm().has_object_permission(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from unittest.mock import AsyncMock, Mock
2+
3+
import pytest
4+
from django.http import HttpRequest
5+
6+
from ninja_extra.security.session import AsyncSessionAuth
7+
8+
9+
@pytest.mark.asyncio
10+
async def test_async_session_auth():
11+
auth = AsyncSessionAuth()
12+
request = HttpRequest()
13+
14+
# Test async authenticated user
15+
async_user = AsyncMock()
16+
async_user.is_authenticated = True
17+
request.auser = AsyncMock(return_value=async_user)
18+
19+
authenticated_user = await auth.authenticate(request, None)
20+
assert authenticated_user == async_user
21+
request.auser.assert_called_once()
22+
23+
# Test async non-authenticated user
24+
async_user.is_authenticated = False
25+
authenticated_user = await auth.authenticate(request, None)
26+
assert authenticated_user is None
27+
28+
# Test non-async authenticated user
29+
delattr(request, "auser")
30+
sync_user = Mock()
31+
sync_user.is_authenticated = True
32+
request.user = sync_user
33+
34+
authenticated_user = await auth.authenticate(request, None)
35+
assert authenticated_user == sync_user
36+
37+
# Test non-async non-authenticated user
38+
sync_user.is_authenticated = False
39+
authenticated_user = await auth.authenticate(request, None)
40+
assert authenticated_user is None

0 commit comments

Comments
 (0)