Skip to content
9 changes: 9 additions & 0 deletions rest_framework/authtoken/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,17 @@ class Meta:
verbose_name_plural = _("Tokens")

def save(self, *args, **kwargs):
"""
Save the token instance.

If no key is provided, generates a cryptographically secure key.
For new tokens, ensures they are inserted as new (not updated).
"""
if not self.key:
self.key = self.generate_key()
# For new objects, force INSERT to prevent overwriting existing tokens
if self._state.adding:
kwargs['force_insert'] = True
return super().save(*args, **kwargs)

@classmethod
Expand Down
40 changes: 40 additions & 0 deletions tests/test_authtoken.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.contrib.admin import site
from django.contrib.auth.models import User
from django.core.management import CommandError, call_command
from django.db import IntegrityError
from django.test import TestCase, modify_settings

from rest_framework.authtoken.admin import TokenAdmin
Expand Down Expand Up @@ -48,6 +49,45 @@ def test_whitespace_in_password(self):
self.user.save()
assert AuthTokenSerializer(data=data).is_valid()

def test_token_creation_collision_raises_integrity_error(self):
user2 = User.objects.create_user('user2', '[email protected]', 'p')
existing_token = Token.objects.create(user=user2)

# Try to create another token with the same key
with self.assertRaises(IntegrityError):
Token.objects.create(key=existing_token.key, user=self.user)

def test_key_generated_on_save_when_cleared(self):
# Create a new user for this test to avoid conflicts with setUp token
user2 = User.objects.create_user('test_user2', '[email protected]', 'password')

# Create a token without a key - it should generate one automatically
token = Token(user=user2)
token.key = "" # Explicitly clear the key
token.save()

# Verify the key was generated
self.assertEqual(len(token.key), 40)
self.assertEqual(token.user, user2)

def test_clearing_key_on_existing_token_raises_integrity_error(self):
"""Test that clearing the key on an existing token raises IntegrityError."""
user = User.objects.create_user('test_user3', '[email protected]', 'password')
token = Token.objects.create(user=user)
token.key = ""

# This should raise IntegrityError because:
# 1. We're trying to update a record with an empty primary key
# 2. The OneToOneField constraint would be violated
with self.assertRaises(Exception): # Could be IntegrityError or DatabaseError
token.save()

def test_saving_existing_token_without_changes_does_not_alter_key(self):
original_key = self.token.key

self.token.save()
self.assertEqual(self.token.key, original_key)


class AuthTokenCommandTests(TestCase):

Expand Down
Loading