diff --git a/src/backend/InvenTree/users/api.py b/src/backend/InvenTree/users/api.py index 8113b8780a..75b28fbbb1 100644 --- a/src/backend/InvenTree/users/api.py +++ b/src/backend/InvenTree/users/api.py @@ -553,6 +553,10 @@ class TokenDetailView(CleanBase, TokenMixin, DestroyAPIView, RetrieveAPI): def perform_destroy(self, instance): """Revoke token.""" + # Do not overwrite the record of an earlier revocation + if instance.revoked: + return + instance.revoked = True instance.revoked_by = self.request.user request_data = getattr(self.request, 'data', {}) diff --git a/src/backend/InvenTree/users/test_api.py b/src/backend/InvenTree/users/test_api.py index 429226d1cb..3345438063 100644 --- a/src/backend/InvenTree/users/test_api.py +++ b/src/backend/InvenTree/users/test_api.py @@ -677,6 +677,23 @@ class UserTokenTests(InvenTreeAPITestCase): self.assertEqual(token.revoked_by, self.user) self.assertEqual(token.revocation_reason, 'No longer needed') + # Revoking again must not overwrite the original revocation record + other_user = User.objects.create_user(username='other-revoker', password='x') + other_user.is_superuser = True + other_user.save() + self.client.force_authenticate(user=other_user) + self.delete( + reverse('api-token-detail', kwargs={'pk': token_id}) + '?all_users=true', + data={'revocation_reason': 'Overwritten reason'}, + expected_code=204, + ) + self.client.force_authenticate(user=None) + + token.refresh_from_db() + self.assertTrue(token.revoked) + self.assertEqual(token.revoked_by, self.user) + self.assertEqual(token.revocation_reason, 'No longer needed') + # Get token without auth (should fail) self.client.logout() self.get(reverse('api-token'), expected_code=401)