Классы моделей исправлены
This commit is contained in:
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
|
from net.xeaf.rack.models import SessionModel
|
||||||
|
|
||||||
|
|
||||||
class CryptoManager:
|
class CryptoManager:
|
||||||
"""
|
"""
|
||||||
@@ -37,6 +39,10 @@ class CryptoManager:
|
|||||||
:return: Сгенерированный токен
|
: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
|
bytes_needed = (length + 1) // 2
|
||||||
token = secrets.token_hex(bytes_needed)
|
token = secrets.token_hex(bytes_needed)
|
||||||
return token[:length]
|
return token[:length]
|
||||||
|
|||||||
@@ -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.contrib.auth.models
|
||||||
import django.db.models.deletion
|
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')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='rack.mixin.created_at')),
|
||||||
('expires_at', models.DateTimeField(verbose_name='rack.mixin.expires_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')),
|
('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')),
|
('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')),
|
('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')),
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='rack.models.session_model.account')),
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from django.db import models
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from net.xeaf.rack.core import CoreModel
|
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 CreatedAtMixin
|
||||||
from net.xeaf.rack.models.mixins import ExpiresAtMixin
|
from net.xeaf.rack.models.mixins import ExpiresAtMixin
|
||||||
|
|
||||||
@@ -23,8 +22,11 @@ class SessionModel(CoreModel, CreatedAtMixin, ExpiresAtMixin):
|
|||||||
Модель сессии пользователя
|
Модель сессии пользователя
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
MAX_TOKEN_LENGTH = 255
|
||||||
|
""" Максимальная длина токена сессии """
|
||||||
|
|
||||||
token = models.CharField(
|
token = models.CharField(
|
||||||
max_length=CryptoManager.DEFAULT_TOKEN_LENGTH,
|
max_length=MAX_TOKEN_LENGTH,
|
||||||
null=False,
|
null=False,
|
||||||
blank=False,
|
blank=False,
|
||||||
verbose_name=_("rack.models.session_model.token"),
|
verbose_name=_("rack.models.session_model.token"),
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
"""
|
"""
|
||||||
Описание класса CryptoTests
|
Описание класса CryptoTests
|
||||||
"""
|
"""
|
||||||
|
from django.db import models
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
from net.xeaf.rack.managers import CryptoManager
|
from net.xeaf.rack.managers import CryptoManager
|
||||||
|
from net.xeaf.rack.models import SessionModel
|
||||||
|
|
||||||
|
|
||||||
class CryptoManagerTests(SimpleTestCase):
|
class CryptoManagerTests(SimpleTestCase):
|
||||||
@@ -127,3 +128,35 @@ class CryptoManagerTests(SimpleTestCase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self.assertRegex(value, r'^[0-9a-f]+$')
|
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))
|
||||||
|
|||||||
Reference in New Issue
Block a user