Skip to content

[pull] master from encode:master #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions rest_framework/authtoken/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,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