31dd3b4ffc
Co-authored-by: Cursor <cursoragent@cursor.com>
66 lines
2.7 KiB
Python
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)
|