From 97a771c4053922d891e363ea63879963ffd7fe29 Mon Sep 17 00:00:00 2001 From: Mahdi Rahimi <31624047+mahdirahimi1999@users.noreply.github.com> Date: Sun, 10 Aug 2025 07:12:52 +0330 Subject: [PATCH 1/2] 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 --- rest_framework/authtoken/models.py | 5 ++- tests/authentication/test_authentication.py | 39 +++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 6a17c24525..80a4dad693 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -1,5 +1,4 @@ -import binascii -import os +import secrets from django.conf import settings from django.db import models @@ -34,7 +33,7 @@ def save(self, *args, **kwargs): @classmethod def generate_key(cls): - return binascii.hexlify(os.urandom(20)).decode() + return secrets.token_hex(20) def __str__(self): return self.key diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index 2f05ce7d19..3b6c633ee0 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -81,6 +81,7 @@ def put(self, request): @override_settings(ROOT_URLCONF=__name__) class BasicAuthTests(TestCase): """Basic authentication""" + def setUp(self): self.csrf_client = APIClient(enforce_csrf_checks=True) self.username = 'john' @@ -198,6 +199,7 @@ def test_decoding_of_utf8_credentials(self): @override_settings(ROOT_URLCONF=__name__) class SessionAuthTests(TestCase): """User session authentication""" + def setUp(self): self.csrf_client = APIClient(enforce_csrf_checks=True) self.non_csrf_client = APIClient(enforce_csrf_checks=False) @@ -418,6 +420,41 @@ def test_generate_key_accessible_as_classmethod(self): key = self.model.generate_key() 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): """Ensure token login view using JSON POST works.""" client = APIClient(enforce_csrf_checks=True) @@ -480,6 +517,7 @@ def test_incorrect_credentials(self): authentication should run and error, even if no permissions are set on the view. """ + class IncorrectCredentialsAuth(BaseAuthentication): def authenticate(self, request): raise exceptions.AuthenticationFailed('Bad credentials') @@ -571,6 +609,7 @@ def test_basic_authentication_raises_error_if_user_not_active(self): class MockUser: is_active = False + old_authenticate = authentication.authenticate authentication.authenticate = lambda **kwargs: MockUser() try: From 92a2c4d3cbff9dc5878941e47e534718d967cb0f Mon Sep 17 00:00:00 2001 From: Khaled Sukkar Date: Sun, 10 Aug 2025 07:12:11 +0300 Subject: [PATCH 2/2] add a new third-party package in serializers.md (#9717) * Update serializers.md add a new third-party package in serializers section * Update third-party-packages.md add drf-shapeless-serializers to the serializers section. * Update docs/community/third-party-packages.md Co-authored-by: Bruno Alla --------- Co-authored-by: Bruno Alla --- docs/api-guide/serializers.md | 5 +++++ docs/community/third-party-packages.md | 2 ++ 2 files changed, 7 insertions(+) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 8d56d36f5a..3ce8f887f4 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -1189,6 +1189,10 @@ The [drf-writable-nested][drf-writable-nested] package provides writable nested The [drf-encrypt-content][drf-encrypt-content] package helps you encrypt your data, serialized through ModelSerializer. It also contains some helper functions. Which helps you to encrypt your data. +## Shapeless Serializers + +The [drf-shapeless-serializers][drf-shapeless-serializers] package provides dynamic serializer configuration capabilities, allowing runtime field selection, renaming, attribute modification, and nested relationship configuration without creating multiple serializer classes. It helps eliminate serializer boilerplate while providing flexible API responses. + [cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion [relations]: relations.md @@ -1212,3 +1216,4 @@ The [drf-encrypt-content][drf-encrypt-content] package helps you encrypt your da [djangorestframework-queryfields]: https://djangorestframework-queryfields.readthedocs.io/ [drf-writable-nested]: https://github.com/beda-software/drf-writable-nested [drf-encrypt-content]: https://github.com/oguzhancelikarslan/drf-encrypt-content +[drf-shapeless-serializers]: https://github.com/khaledsukkar2/drf-shapeless-serializers diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 6d4791b70d..96e7033ad1 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -88,6 +88,7 @@ To submit new content, [create a pull request][drf-create-pr]. * [djangorestframework-dataclasses][djangorestframework-dataclasses] - Serializer providing automatic field generation for Python dataclasses, like the built-in ModelSerializer does for models. * [django-restql][django-restql] - Turn your REST API into a GraphQL like API(It allows clients to control which fields will be sent in a response, uses GraphQL like syntax, supports read and write on both flat and nested fields). * [graphwrap][graphwrap] - Transform your REST API into a fully compliant GraphQL API with just two lines of code. Leverages [Graphene-Django](https://docs.graphene-python.org/projects/django/en/latest/) to dynamically build, at runtime, a GraphQL ObjectType for each view in your API. +* [drf-shapeless-serializers][drf-shapeless-serializers] - Dynamically assemble, configure, and shape your Django Rest Framework serializers at runtime, much like connecting Lego bricks. ### Serializer fields @@ -259,3 +260,4 @@ To submit new content, [create a pull request][drf-create-pr]. [drf-redesign]: https://github.com/youzarsiph/drf-redesign [drf-material]: https://github.com/youzarsiph/drf-material [django-pyoidc]: https://github.com/makinacorpus/django_pyoidc +[drf-shapeless-serializers]: https://github.com/khaledsukkar2/drf-shapeless-serializers