Files
ACORE/apps/employees/middleware.py
T

66 lines
2.7 KiB
Python

"""Принуждение 2FA на портале и в /admin/.
Staff и IT-разделы (AD, IT-отдел) — обязательно. HR и остальные — по желанию
(opt-in после включения TOTP).
"""
from django.shortcuts import redirect
from django.urls import reverse
from core.security import user_requires_mandatory_2fa
def _twofa_required(user) -> bool:
return user_requires_mandatory_2fa(user)
def _own_profile_settings_allowed(request, user) -> bool:
"""Доступ к своим настройкам до включения 2FA (вкладка «Настройки» профиля)."""
if not user or not getattr(user, 'is_authenticated', False):
return False
own_path = f'/employees/{user.pk}'
if request.path.rstrip('/') != own_path:
return False
if request.method == 'GET' and request.GET.get('tab') == 'settings':
return True
if request.method == 'POST':
if request.POST.get('profile_tab') == 'settings':
return True
if request.POST.get('return_to') == 'profile':
return True
return False
class TwoFactorMiddleware:
# Пути, доступные без пройденной 2FA: сам процесс входа/настройки и статика.
# /api/ и /media/ НЕ включены — staff-сессия без 2FA не должна дотянуться до
# API вложений/посещаемости и защищённых медиа (устройства ходят в /api/ без
# staff-сессии, поэтому их этот гейт не трогает).
ALLOWED_PREFIXES = (
'/2fa/', '/logout/', '/accounts/login/', '/login/',
'/set-password/', '/admin/login/', '/admin/logout/',
'/acore-a-panel/login/', '/acore-a-panel/logout/', '/i18n/',
'/static/',
'/documents/onlyoffice/', # Document Server забирает файл без сессии портала
)
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
user = getattr(request, 'user', None)
path = request.path
if (_twofa_required(user)
and not path.startswith(self.ALLOWED_PREFIXES)):
if path.rstrip('/') == '/settings':
return self.get_response(request)
if not user.totp_enabled:
if _own_profile_settings_allowed(request, user):
return self.get_response(request)
return redirect('twofa_setup')
if not request.session.get('2fa_verified'):
return redirect(f"{reverse('twofa_verify')}?next={path}")
return self.get_response(request)