"""Централизованные константы и проверки безопасности 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 ''), )