feat: проверка что только получатель заявки может принять решение
- Добавлен IsRecipientOfRequest permission для API - Обновлен get_allowed_request_qs: теперь включает заявки где пользователь - получатель - Добавлена проверка recipients в request_process view - Staff/superuser могут обрабатывать любые заявки (обход проверки)
This commit is contained in:
parent
66e5279850
commit
aa6fa29e3a
@ -10,6 +10,33 @@ from employees.constants import DeptPerm
|
||||
from employees.models import Department
|
||||
|
||||
|
||||
class IsRecipientOfRequest(BasePermission):
|
||||
"""Проверяет, что пользователь является получателем заявки.
|
||||
|
||||
Доступ разрешается только если пользователь указан в recipients
|
||||
данной заявки. Это базовая проверка которая должна комбинироваться
|
||||
с проверкой прав (AdminOrActionOrModelPerms или DeptCanProcess).
|
||||
|
||||
Для staff/superuser проверка пропускается (они могут обрабатывать любые заявки).
|
||||
"""
|
||||
|
||||
message = "Вы не являетесь получателем этой заявки."
|
||||
|
||||
def has_permission(self, request: Request, view: Any) -> bool:
|
||||
return bool(getattr(request.user, "is_authenticated", False))
|
||||
|
||||
def has_object_permission(self, request: Request, view: Any, obj: Any) -> bool:
|
||||
user = request.user
|
||||
|
||||
# Staff/superuser могут обрабатывать любые заявки
|
||||
if getattr(user, "is_staff", False) or getattr(user, "is_superuser", False):
|
||||
return True
|
||||
|
||||
# Проверяем, является ли пользователь получателем заявки
|
||||
# (только recipients, не cc_users - люди в копии не принимают решения)
|
||||
return obj.recipients.filter(id=user.id).exists()
|
||||
|
||||
|
||||
class CommentsPermission(BasePermission):
|
||||
"""Комментарии: модельные права, staff, head.
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ from .permissions import (
|
||||
DeptChangeRequest,
|
||||
DeptComments,
|
||||
DeptViewRequest,
|
||||
IsRecipientOfRequest,
|
||||
NotFinalOrStaff,
|
||||
)
|
||||
from .serializers import (
|
||||
@ -86,7 +87,12 @@ class RequestViewSet(viewsets.ModelViewSet):
|
||||
if self.action == "comments":
|
||||
return [(CommentsPermission | DeptComments)()]
|
||||
if self.action in {"approve", "reject"}:
|
||||
return [(AdminOrActionOrModelPerms | DeptCanProcess)()]
|
||||
# Права на обработку + обязательно должен быть получателем заявки
|
||||
# (staff/superuser пропускаются в IsRecipientOfRequest)
|
||||
return [
|
||||
(AdminOrActionOrModelPerms | DeptCanProcess)(),
|
||||
IsRecipientOfRequest(),
|
||||
]
|
||||
if self.action == "cancel":
|
||||
return [IsSelfOrStaff()]
|
||||
if self.action == "retrieve":
|
||||
|
||||
@ -144,13 +144,42 @@ def is_hr_or_head(user) -> bool:
|
||||
return is_hr(user) or is_department_head(user)
|
||||
|
||||
|
||||
def can_process_requests(user) -> bool:
|
||||
"""Проверяет, может ли пользователь обрабатывать заявки вообще.
|
||||
|
||||
True если: HR/staff, руководитель отдела, или есть модельное право.
|
||||
"""
|
||||
if is_hr(user):
|
||||
return True
|
||||
if is_department_head(user):
|
||||
return True
|
||||
if user.has_perm('requests_app.can_process_requests'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_allowed_request_qs(user):
|
||||
"""HR — все; руководитель — заявки сотрудников своих отделов."""
|
||||
"""Возвращает заявки, которые пользователь может обрабатывать.
|
||||
|
||||
HR/staff — все заявки;
|
||||
Руководитель отдела — заявки сотрудников своих отделов;
|
||||
Получатель — заявки, где он указан как recipient.
|
||||
|
||||
Финальная проверка в request_process дополнительно проверяет,
|
||||
что пользователь является получателем конкретной заявки.
|
||||
"""
|
||||
if is_hr(user):
|
||||
return BASE_REQUEST_QS
|
||||
return BASE_REQUEST_QS.filter(
|
||||
employee__departments_links__department__head=user
|
||||
).distinct()
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
# Заявки от сотрудников отделов, где пользователь руководитель
|
||||
dept_head_qs = Q(employee__departments_links__department__head=user)
|
||||
|
||||
# Заявки где пользователь указан как получатель
|
||||
recipient_qs = Q(recipients=user)
|
||||
|
||||
return BASE_REQUEST_QS.filter(dept_head_qs | recipient_qs).distinct()
|
||||
|
||||
|
||||
# ---------- Утилиты ----------
|
||||
@ -311,7 +340,7 @@ def all_requests(request):
|
||||
)
|
||||
|
||||
|
||||
@user_passes_test(is_hr_or_head)
|
||||
@user_passes_test(can_process_requests)
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def request_process(request, pk):
|
||||
allowed_qs = get_allowed_request_qs(request.user)
|
||||
@ -320,6 +349,10 @@ def request_process(request, pk):
|
||||
with transaction.atomic():
|
||||
req = get_object_or_404(allowed_qs.select_for_update(), pk=pk)
|
||||
req.refresh_from_db()
|
||||
|
||||
# Проверка: не-staff должен быть получателем заявки
|
||||
if not is_hr(request.user) and not req.recipients.filter(id=request.user.id).exists():
|
||||
raise PermissionDenied("Вы не являетесь получателем этой заявки.")
|
||||
|
||||
post = request.POST.copy()
|
||||
# Фоллбек: если status отсутствует (или кнопка без JS), используем имя кнопки
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user