From f0db1ba9b516ca15ce655a13c04960b5dfa83f46 Mon Sep 17 00:00:00 2001 From: "Nick V. Anokhin" Date: Mon, 13 Jul 2026 13:30:11 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BB=D0=B0=D1=81=D1=81=D1=8B=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- net/xeaf/rack/managers/crypto_manager.py | 6 ++++ net/xeaf/rack/migrations/0001_initial.py | 4 +-- net/xeaf/rack/models/session_model.py | 6 ++-- .../tests/managers/crypto_manager_tests.py | 35 ++++++++++++++++++- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/net/xeaf/rack/managers/crypto_manager.py b/net/xeaf/rack/managers/crypto_manager.py index 378c293..6ddb9af 100644 --- a/net/xeaf/rack/managers/crypto_manager.py +++ b/net/xeaf/rack/managers/crypto_manager.py @@ -10,6 +10,8 @@ import secrets +from net.xeaf.rack.models import SessionModel + class CryptoManager: """ @@ -37,6 +39,10 @@ class CryptoManager: :return: Сгенерированный токен """ + # Ограничение длины токена + if length > SessionModel.MAX_TOKEN_LENGTH: + raise ValueError(f"Token length must be less than {SessionModel.MAX_TOKEN_LENGTH}") + bytes_needed = (length + 1) // 2 token = secrets.token_hex(bytes_needed) return token[:length] diff --git a/net/xeaf/rack/migrations/0001_initial.py b/net/xeaf/rack/migrations/0001_initial.py index 0680855..915813b 100644 --- a/net/xeaf/rack/migrations/0001_initial.py +++ b/net/xeaf/rack/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 6.0.7 on 2026-07-12 10:42 +# Generated by Django 6.0.7 on 2026-07-13 10:28 import django.contrib.auth.models import django.db.models.deletion @@ -53,7 +53,7 @@ class Migration(migrations.Migration): ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='rack.mixin.created_at')), ('expires_at', models.DateTimeField(verbose_name='rack.mixin.expires_at')), ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='rack.mixin.id')), - ('token', models.CharField(max_length=40, verbose_name='rack.models.session_model.token')), + ('token', models.CharField(max_length=255, verbose_name='rack.models.session_model.token')), ('device_id', models.CharField(blank=True, default='WEB', max_length=36, null=True, verbose_name='rack.models.session_model.device_id')), ('fcm_token', models.CharField(blank=True, default=None, max_length=256, null=True, verbose_name='rack.models.session_model.fcm_token')), ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='rack.models.session_model.account')), diff --git a/net/xeaf/rack/models/session_model.py b/net/xeaf/rack/models/session_model.py index f18fd55..81b2f9c 100644 --- a/net/xeaf/rack/models/session_model.py +++ b/net/xeaf/rack/models/session_model.py @@ -13,7 +13,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from net.xeaf.rack.core import CoreModel -from net.xeaf.rack.managers import CryptoManager from net.xeaf.rack.models.mixins import CreatedAtMixin from net.xeaf.rack.models.mixins import ExpiresAtMixin @@ -23,8 +22,11 @@ class SessionModel(CoreModel, CreatedAtMixin, ExpiresAtMixin): Модель сессии пользователя """ + MAX_TOKEN_LENGTH = 255 + """ Максимальная длина токена сессии """ + token = models.CharField( - max_length=CryptoManager.DEFAULT_TOKEN_LENGTH, + max_length=MAX_TOKEN_LENGTH, null=False, blank=False, verbose_name=_("rack.models.session_model.token"), diff --git a/net/xeaf/rack/tests/managers/crypto_manager_tests.py b/net/xeaf/rack/tests/managers/crypto_manager_tests.py index 3c6501b..539f594 100644 --- a/net/xeaf/rack/tests/managers/crypto_manager_tests.py +++ b/net/xeaf/rack/tests/managers/crypto_manager_tests.py @@ -7,10 +7,11 @@ """ Описание класса CryptoTests """ - +from django.db import models from django.test import SimpleTestCase from net.xeaf.rack.managers import CryptoManager +from net.xeaf.rack.models import SessionModel class CryptoManagerTests(SimpleTestCase): @@ -127,3 +128,35 @@ class CryptoManagerTests(SimpleTestCase): """ self.assertRegex(value, r'^[0-9a-f]+$') + + def test_crypto_manager_should_generate_default_length_by_default(self) -> None: + """ + Гарантируем, что генератор без параметров использует базовую длину + """ + + generated_token = CryptoManager.gen_token() + self.assertEqual(len(generated_token), CryptoManager.DEFAULT_TOKEN_LENGTH) + + def test_session_model_field_must_match_max_token_length_constant(self) -> None: + """ + Защита схемы БД: max_length в миграциях равен MAX_TOKEN_LENGTH + """ + + field = SessionModel._meta.get_field("token") + self.assertIsInstance(field, models.CharField) + db_max_length = field.max_length + self.assertIsInstance(db_max_length, int) + self.assertEqual(db_max_length, SessionModel.MAX_TOKEN_LENGTH) + + def test_crypto_manager_should_raise_value_error_if_length_exceeds_max(self) -> None: + """ + Проверка Fail-Fast контракта: при превышении лимита СУБД выбрасывается ValueError + """ + + excessive_length = SessionModel.MAX_TOKEN_LENGTH + 1 + + # Система обязана громко упасть с ValueError + with self.assertRaises(ValueError) as context: + CryptoManager.gen_token(length=excessive_length) + + self.assertIn(f"Token length must be less than {SessionModel.MAX_TOKEN_LENGTH}", str(context.exception))