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():
|
||||
# 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.save()
|
||||
token.save(update_fields=['last_seen'])
|
||||
|
||||
return (user, token)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""API tests for various user / auth API endpoints."""
|
||||
|
||||
import datetime
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.urls import reverse
|
||||
@@ -473,6 +474,44 @@ class UserTokenTests(InvenTreeAPITestCase):
|
||||
|
||||
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):
|
||||
"""Test the token API."""
|
||||
url = reverse('api-token-list')
|
||||
|
||||
Reference in New Issue
Block a user