fix refresh token
This commit is contained in:
1 parent
4dc6d98abf
commit
a28ac2167c
2 files changed
+45
-1
No files matched your search
@@ -0,0 +1,42 @@
|
|||||||
|
"""Refresh locking must work on PostgreSQL despite nullable role relations."""
|
||||||
|
from django.db import connection
|
||||||
|
from django.test import skipUnlessDBFeature
|
||||||
|
from django.test.utils import CaptureQueriesContext
|
||||||
|
|
||||||
|
from apps.cuser.models import CuserRole
|
||||||
|
from apps.jwt_auth.models import JwtToken
|
||||||
|
from apps.jwt_auth.test_regressions import RegressionFixtures
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshLockingTests(RegressionFixtures):
|
||||||
|
@skipUnlessDBFeature('has_select_for_update_of')
|
||||||
|
def test_refresh_locks_only_session_and_rejects_reuse(self):
|
||||||
|
tokens = self.use_role()
|
||||||
|
with CaptureQueriesContext(connection) as queries:
|
||||||
|
response = self.post('token/refresh/', {'refresh': tokens['refresh']})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
locking = [query['sql'] for query in queries if 'FOR UPDATE' in query['sql'].upper()]
|
||||||
|
self.assertEqual(len(locking), 1)
|
||||||
|
# The actual endpoint SQL retains LEFT JOINs for nullable relations but
|
||||||
|
# targets only the JWT row in its PostgreSQL locking clause.
|
||||||
|
self.assertIn('LEFT OUTER JOIN', locking[0].upper())
|
||||||
|
expected = 'FOR UPDATE OF ' + connection.ops.quote_name(JwtToken._meta.db_table)
|
||||||
|
self.assertEqual(locking[0].split('FOR UPDATE', 1)[1].strip(), expected.split('FOR UPDATE', 1)[1].strip())
|
||||||
|
self.assertNotEqual(response.data['refresh'], tokens['refresh'])
|
||||||
|
self.assertEqual(JwtToken.objects.count(), 1)
|
||||||
|
self.assertEqual(self.post('token/refresh/', {'refresh': tokens['refresh']}).status_code, 401)
|
||||||
|
|
||||||
|
def test_refresh_for_role_without_workspace_is_unauthorized(self):
|
||||||
|
tokens = self.use_role()
|
||||||
|
CuserRole.objects.filter(pk=self.role.pk).update(sys_ws=None)
|
||||||
|
response = self.post('token/refresh/', {'refresh': tokens['refresh']})
|
||||||
|
self.assertEqual(response.status_code, 401)
|
||||||
|
self.assertIn('فضای کاری', str(response.data))
|
||||||
|
self.assertEqual(JwtToken.objects.get().refresh, tokens['refresh'])
|
||||||
|
|
||||||
|
def test_refresh_for_role_without_user_is_unauthorized(self):
|
||||||
|
tokens = self.use_role()
|
||||||
|
CuserRole.objects.filter(pk=self.role.pk).update(cuser=None)
|
||||||
|
response = self.post('token/refresh/', {'refresh': tokens['refresh']})
|
||||||
|
self.assertEqual(response.status_code, 401)
|
||||||
|
self.assertEqual(JwtToken.objects.get().refresh, tokens['refresh'])
|
||||||
@@ -120,7 +120,9 @@ class CustomTokenRefreshView(PublicAuthView):
|
|||||||
refresh_value = serializer.validated_data['refresh']
|
refresh_value = serializer.validated_data['refresh']
|
||||||
try:
|
try:
|
||||||
refresh = RefreshToken(refresh_value)
|
refresh = RefreshToken(refresh_value)
|
||||||
session = JwtToken.objects.select_for_update().select_related(
|
# Nullable user/workspace joins must not participate in PostgreSQL's
|
||||||
|
# row lock. The session alone serializes refresh-token rotation.
|
||||||
|
session = JwtToken.objects.select_for_update(of=('self',)).select_related(
|
||||||
'cuser_role__cuser', 'cuser_role__role_type', 'cuser_role__sys_ws',
|
'cuser_role__cuser', 'cuser_role__role_type', 'cuser_role__sys_ws',
|
||||||
).get(refresh=refresh_value, is_blocked=False)
|
).get(refresh=refresh_value, is_blocked=False)
|
||||||
except (TokenError, JwtToken.DoesNotExist):
|
except (TokenError, JwtToken.DoesNotExist):
|
||||||
|
|||||||
Reference in new issue
Block a user