mirror of
https://github.com/inventree/InvenTree.git
synced 2026-08-10 15:36:17 +00:00
@@ -34,8 +34,10 @@ class ApiTokenAuthentication(TokenAuthentication):
|
|||||||
|
|
||||||
if token.last_seen != datetime.date.today():
|
if token.last_seen != datetime.date.today():
|
||||||
# Update the last-seen date
|
# Update the last-seen date
|
||||||
|
# Note: Use update_fields to avoid clobbering concurrent changes to
|
||||||
|
# other fields on this token (e.g. a concurrent revocation)
|
||||||
token.last_seen = datetime.date.today()
|
token.last_seen = datetime.date.today()
|
||||||
token.save()
|
token.save(update_fields=['last_seen'])
|
||||||
|
|
||||||
return (user, token)
|
return (user, token)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""API tests for various user / auth API endpoints."""
|
"""API tests for various user / auth API endpoints."""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -473,6 +474,44 @@ class UserTokenTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
self.client.get(me, expected_code=200)
|
self.client.get(me, expected_code=200)
|
||||||
|
|
||||||
|
def test_token_last_seen_no_clobber(self):
|
||||||
|
"""Regression test: updating token.last_seen must overwrite other fields.
|
||||||
|
|
||||||
|
Simulates a revoke landing in the window between this request's token
|
||||||
|
lookup and its last_seen save, by revoking the token (directly against
|
||||||
|
the database) from inside a patched ApiToken.save().
|
||||||
|
"""
|
||||||
|
token_key = self.get(
|
||||||
|
url=reverse('api-token'), data={'name': 'race'}, expected_code=200
|
||||||
|
).data['token']
|
||||||
|
|
||||||
|
token = ApiToken.objects.get(key=token_key)
|
||||||
|
|
||||||
|
# Force last_seen to be 'stale' so the auth backend attempts to update it
|
||||||
|
ApiToken.objects.filter(pk=token.pk).update(
|
||||||
|
last_seen=datetime.date.today() - datetime.timedelta(days=1)
|
||||||
|
)
|
||||||
|
|
||||||
|
original_save = ApiToken.save
|
||||||
|
|
||||||
|
def revoke_then_save(self, *args, **kwargs):
|
||||||
|
# Simulate a concurrent request revoking this token, via a direct
|
||||||
|
# DB write, right before this request's last_seen save lands
|
||||||
|
ApiToken.objects.filter(pk=self.pk).update(revoked=True)
|
||||||
|
return original_save(self, *args, **kwargs)
|
||||||
|
|
||||||
|
self.client.logout()
|
||||||
|
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token_key)
|
||||||
|
|
||||||
|
with mock.patch.object(ApiToken, 'save', revoke_then_save):
|
||||||
|
self.client.get(reverse('api-user-me'), expected_code=200)
|
||||||
|
|
||||||
|
token.refresh_from_db()
|
||||||
|
self.assertTrue(
|
||||||
|
token.revoked,
|
||||||
|
'Concurrent revoke must not be clobbered by the last_seen update',
|
||||||
|
)
|
||||||
|
|
||||||
def test_token_api(self):
|
def test_token_api(self):
|
||||||
"""Test the token API."""
|
"""Test the token API."""
|
||||||
url = reverse('api-token-list')
|
url = reverse('api-token-list')
|
||||||
|
|||||||
Reference in New Issue
Block a user