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>
103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
"""Централизованные константы и проверки безопасности ACORE."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
# Группы, membership в которых даёт право создавать сотрудников / управлять AD.
|
|
SENSITIVE_GROUPS = frozenset({
|
|
'HR',
|
|
'IT',
|
|
'Администратор сети',
|
|
})
|
|
|
|
# Разделы портала с повышенными привилегиями (HR, IT, БД).
|
|
SENSITIVE_PORTAL_PERM_CODENAMES = frozenset({
|
|
'access_hr_portal',
|
|
'access_recruiting',
|
|
'access_ad_mapping',
|
|
'access_it_manager',
|
|
'access_databases',
|
|
})
|
|
|
|
# Обязательная 2FA: staff и IT-разделы (AD). HR — отдельная роль, без принудительного 2FA.
|
|
MANDATORY_2FA_PERM_CODENAMES = frozenset({
|
|
'access_ad_mapping',
|
|
'access_it_manager',
|
|
})
|
|
|
|
_audit_logger = logging.getLogger('security.audit')
|
|
|
|
|
|
def is_sensitive_portal_perm(permission) -> bool:
|
|
codename = getattr(permission, 'codename', permission)
|
|
return codename in SENSITIVE_PORTAL_PERM_CODENAMES
|
|
|
|
|
|
def is_sensitive_group(group) -> bool:
|
|
return group.name in SENSITIVE_GROUPS
|
|
|
|
|
|
def user_account_mandatory_2fa(user) -> bool:
|
|
"""Staff и IT обязаны включить 2FA; не зависит от totp_enabled."""
|
|
if not user or not getattr(user, 'is_authenticated', False):
|
|
return False
|
|
if getattr(user, 'is_staff', False):
|
|
return True
|
|
for codename in MANDATORY_2FA_PERM_CODENAMES:
|
|
if user.has_perm(f'core.{codename}'):
|
|
return True
|
|
return False
|
|
|
|
|
|
def user_requires_mandatory_2fa(user) -> bool:
|
|
"""Staff и IT обязаны пройти 2FA; остальные — только если включили TOTP."""
|
|
if user_account_mandatory_2fa(user):
|
|
return True
|
|
return bool(getattr(user, 'totp_enabled', False))
|
|
|
|
|
|
def user_can_disable_2fa(user) -> bool:
|
|
"""Отключить 2FA могут только те, кто включил её добровольно (не staff/IT)."""
|
|
if not user or not getattr(user, 'is_authenticated', False):
|
|
return False
|
|
if not getattr(user, 'totp_enabled', False):
|
|
return False
|
|
return not user_account_mandatory_2fa(user)
|
|
|
|
|
|
def filter_portal_permissions_for_user(queryset, user):
|
|
"""Non-superuser не видит чувствительные portal-permissions в формах."""
|
|
if user.is_superuser:
|
|
return queryset
|
|
return queryset.exclude(codename__in=SENSITIVE_PORTAL_PERM_CODENAMES)
|
|
|
|
|
|
def log_security_event(
|
|
action: str,
|
|
*,
|
|
actor=None,
|
|
target: str = '',
|
|
details: str = '',
|
|
extra: dict[str, Any] | None = None,
|
|
) -> None:
|
|
actor_id = getattr(actor, 'pk', None)
|
|
actor_name = getattr(actor, 'username', '') if actor else ''
|
|
payload = {
|
|
'action': action,
|
|
'actor_id': actor_id,
|
|
'actor': actor_name,
|
|
'target': target,
|
|
'details': details,
|
|
}
|
|
if extra:
|
|
payload.update(extra)
|
|
_audit_logger.info(
|
|
'security_event action=%s actor=%s(%s) target=%s details=%s',
|
|
action,
|
|
actor_name,
|
|
actor_id,
|
|
target,
|
|
details or (extra or ''),
|
|
)
|