719d0b737b
- AcoreDiscoverRunner собирает tests/tests_* всех локальных приложений (manage.py test находил 38 тестов из ~466 — теперь все). - CACHES на Redis (отдельная DB от Celery) + обязательный requirepass в production, SECURE_HSTS_PRELOAD. - Фикс stored-XSS в админке: format_html вместо mark_safe + f-string. - Утилиты безопасности/тестов, шифрование полей. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
39 lines
1.7 KiB
Python
39 lines
1.7 KiB
Python
import logging
|
|
|
|
from django.db import models
|
|
|
|
from core.utils.crypto import encrypt_secret, decrypt_secret
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class EncryptedCharField(models.CharField):
|
|
"""CharField, который хранит значение в БД в зашифрованном виде (Fernet).
|
|
|
|
Для приложения поле ведёт себя как обычная строка: чтение отдаёт открытый
|
|
текст, запись сохраняет шифртекст. Шифрование выполняется на границе с БД,
|
|
поэтому читатели (`cfg.password`, `cfg.token`) и админка изменений не требуют.
|
|
|
|
Шифртекст длиннее исходного текста (~1.4×) плюс постоянный оверхед Fernet,
|
|
поэтому колонку нужно объявлять с запасом по `max_length`.
|
|
"""
|
|
|
|
def get_db_prep_save(self, value, connection):
|
|
value = super().get_db_prep_save(value, connection)
|
|
if value in (None, ''):
|
|
return value
|
|
return encrypt_secret(value)
|
|
|
|
def from_db_value(self, value, expression, connection):
|
|
if value in (None, ''):
|
|
return value
|
|
try:
|
|
return decrypt_secret(value)
|
|
except Exception:
|
|
# Значение записано до включения шифрования — вернуть как есть.
|
|
logger.warning(
|
|
'EncryptedCharField: decrypt failed, returning raw DB value '
|
|
'(check FIELD_ENCRYPTION_KEY rotation)'
|
|
)
|
|
return value
|