Refactor token generation to use secrets module (#9760)

* Refactor token generation to use secrets module

* test: Add focused tests for Token.generate_key() method

- Add test for valid token format (40 hex characters)
- Add collision resistance test with 500 sample size
- Add basic randomness quality validation
- Ensure generated keys are unique and properly formatted
This commit is contained in:
Mahdi Rahimi 2025-08-10 07:12:52 +03:30 committed by GitHub
parent edc055da78
commit 97a771c405
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 3 deletions

View File

@ -1,5 +1,4 @@
import binascii import secrets
import os
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
@ -34,7 +33,7 @@ class Token(models.Model):
@classmethod @classmethod
def generate_key(cls): def generate_key(cls):
return binascii.hexlify(os.urandom(20)).decode() return secrets.token_hex(20)
def __str__(self): def __str__(self):
return self.key return self.key

View File

@ -81,6 +81,7 @@ urlpatterns = [
@override_settings(ROOT_URLCONF=__name__) @override_settings(ROOT_URLCONF=__name__)
class BasicAuthTests(TestCase): class BasicAuthTests(TestCase):
"""Basic authentication""" """Basic authentication"""
def setUp(self): def setUp(self):
self.csrf_client = APIClient(enforce_csrf_checks=True) self.csrf_client = APIClient(enforce_csrf_checks=True)
self.username = 'john' self.username = 'john'
@ -198,6 +199,7 @@ class BasicAuthTests(TestCase):
@override_settings(ROOT_URLCONF=__name__) @override_settings(ROOT_URLCONF=__name__)
class SessionAuthTests(TestCase): class SessionAuthTests(TestCase):
"""User session authentication""" """User session authentication"""
def setUp(self): def setUp(self):
self.csrf_client = APIClient(enforce_csrf_checks=True) self.csrf_client = APIClient(enforce_csrf_checks=True)
self.non_csrf_client = APIClient(enforce_csrf_checks=False) self.non_csrf_client = APIClient(enforce_csrf_checks=False)
@ -418,6 +420,41 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase):
key = self.model.generate_key() key = self.model.generate_key()
assert isinstance(key, str) assert isinstance(key, str)
def test_generate_key_returns_valid_format(self):
"""Ensure generate_key returns a valid token format"""
key = self.model.generate_key()
assert len(key) == 40
# Should contain only valid hexadecimal characters
assert all(c in '0123456789abcdef' for c in key)
def test_generate_key_produces_unique_values(self):
"""Ensure generate_key produces unique values across multiple calls"""
keys = set()
for _ in range(100):
key = self.model.generate_key()
assert key not in keys, f"Duplicate key generated: {key}"
keys.add(key)
def test_generate_key_collision_resistance(self):
"""Test collision resistance with reasonable sample size"""
keys = set()
for _ in range(500):
key = self.model.generate_key()
assert key not in keys, f"Collision found: {key}"
keys.add(key)
assert len(keys) == 500, f"Expected 500 unique keys, got {len(keys)}"
def test_generate_key_randomness_quality(self):
"""Test basic randomness properties of generated keys"""
keys = [self.model.generate_key() for _ in range(10)]
# Consecutive keys should be different
for i in range(len(keys) - 1):
assert keys[i] != keys[i + 1], "Consecutive keys should be different"
# Keys should not follow obvious patterns
for key in keys:
# Should not be all same character
assert not all(c == key[0] for c in key), f"Key has all same characters: {key}"
def test_token_login_json(self): def test_token_login_json(self):
"""Ensure token login view using JSON POST works.""" """Ensure token login view using JSON POST works."""
client = APIClient(enforce_csrf_checks=True) client = APIClient(enforce_csrf_checks=True)
@ -480,6 +517,7 @@ class IncorrectCredentialsTests(TestCase):
authentication should run and error, even if no permissions authentication should run and error, even if no permissions
are set on the view. are set on the view.
""" """
class IncorrectCredentialsAuth(BaseAuthentication): class IncorrectCredentialsAuth(BaseAuthentication):
def authenticate(self, request): def authenticate(self, request):
raise exceptions.AuthenticationFailed('Bad credentials') raise exceptions.AuthenticationFailed('Bad credentials')
@ -571,6 +609,7 @@ class BasicAuthenticationUnitTests(TestCase):
class MockUser: class MockUser:
is_active = False is_active = False
old_authenticate = authentication.authenticate old_authenticate = authentication.authenticate
authentication.authenticate = lambda **kwargs: MockUser() authentication.authenticate = lambda **kwargs: MockUser()
try: try: