175 lines
6.4 KiB
Python
175 lines
6.4 KiB
Python
# backend/api/v1/documents/permissions.py
|
|
from __future__ import annotations
|
|
from typing import Any
|
|
from copy import deepcopy
|
|
from rest_framework.permissions import SAFE_METHODS
|
|
from rest_framework.permissions import DjangoModelPermissions
|
|
from rest_framework.request import Request
|
|
from rest_framework.views import View
|
|
from ..permissions import AdminOrActionOrModelPerms
|
|
|
|
|
|
def _is_admin(user) -> bool:
|
|
return bool(
|
|
user
|
|
and user.is_authenticated
|
|
and (getattr(user, "is_staff", False) or getattr(user, "is_superuser", False))
|
|
)
|
|
|
|
|
|
def _request_sets_regulation(request: Request) -> bool:
|
|
raw_value = request.data.get("is_regulation")
|
|
if isinstance(raw_value, bool):
|
|
return raw_value
|
|
return str(raw_value or "").strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
def _has_any_model_perm(
|
|
user,
|
|
app_label: str,
|
|
model_name: str,
|
|
actions=("view", "change", "add", "delete"),
|
|
) -> bool:
|
|
"""Проверяет наличие любого модельного права из набора."""
|
|
for act in actions:
|
|
code = f"{act}_{model_name}"
|
|
if user.has_perm(f"{app_label}.{code}"):
|
|
return True
|
|
return False
|
|
|
|
|
|
def _user_has_document_access(user, obj) -> bool:
|
|
"""Проверяет пользовательский доступ к конкретному документу."""
|
|
if getattr(obj, "sent_to_all", False) and getattr(user, "is_active", False):
|
|
return True
|
|
|
|
if getattr(obj, "uploaded_by_id", None) == getattr(user, "pk", None):
|
|
return True
|
|
|
|
if obj.recipients.filter(pk=user.pk, is_active=True).exists():
|
|
return True
|
|
|
|
if obj.departments.filter(
|
|
employeedepartment__employee=user,
|
|
employeedepartment__is_active=True,
|
|
).exists():
|
|
return True
|
|
|
|
return obj.departments.filter(
|
|
roles__assignments__employee=user,
|
|
roles__assignments__is_active=True,
|
|
).exists()
|
|
|
|
|
|
class DocumentReadOrModelPerms(AdminOrActionOrModelPerms):
|
|
"""Чтение/скачивание/ознакомление: любой аутентифицированный,
|
|
но доступ только к 'своим' документам (или если есть модельные права/админ).
|
|
Создание/редактирование/удаление: только админы и обладатели модельных прав.
|
|
|
|
Raises:
|
|
PermissionDenied: если нет доступа к конкретному документу.
|
|
"""
|
|
|
|
perms_map = deepcopy(DjangoModelPermissions.perms_map)
|
|
perms_map["GET"] = []
|
|
perms_map["HEAD"] = []
|
|
perms_map["OPTIONS"] = []
|
|
|
|
def has_permission(self, request: Request, view: View) -> bool:
|
|
action = getattr(view, "action", None)
|
|
|
|
# SAFE + acknowledge/download → достаточно быть аутентифицированным
|
|
if request.method in SAFE_METHODS or action in (
|
|
"acknowledge",
|
|
"download",
|
|
"archive",
|
|
"comments",
|
|
"document_comment",
|
|
"set_maximally_hidden",
|
|
):
|
|
return bool(request.user and request.user.is_authenticated)
|
|
|
|
# Обычные документы могут создавать все аутентифицированные сотрудники,
|
|
# регламенты — только администраторы.
|
|
if action == "create":
|
|
if _request_sets_regulation(request):
|
|
return _is_admin(request.user)
|
|
return bool(request.user and request.user.is_authenticated)
|
|
|
|
# update/destroy и related documents - проверяем на уровне объекта
|
|
if action in (
|
|
"update",
|
|
"partial_update",
|
|
"destroy",
|
|
"add_related",
|
|
"remove_related",
|
|
"revert",
|
|
):
|
|
return bool(request.user and request.user.is_authenticated)
|
|
|
|
# небезопасные → staff ИЛИ стандартные модельные права
|
|
# (add/change/delete)
|
|
if request.user and request.user.is_staff:
|
|
return True
|
|
return super().has_permission(request, view)
|
|
|
|
def has_object_permission(
|
|
self, request: Request, view: View, obj: Any
|
|
) -> bool:
|
|
user = request.user
|
|
if not (user and user.is_authenticated):
|
|
return False
|
|
|
|
action = getattr(view, "action", None)
|
|
|
|
if action in ("update", "partial_update") and (
|
|
getattr(obj, "is_regulation", False)
|
|
or _request_sets_regulation(request)
|
|
):
|
|
return _is_admin(user)
|
|
|
|
if getattr(obj, "is_regulation", False) and action in (
|
|
"add_related",
|
|
"remove_related",
|
|
"revert",
|
|
):
|
|
return _is_admin(user)
|
|
|
|
# Полный доступ при наличии ЛЮБОГО модельного права или staff
|
|
if user.is_staff or _has_any_model_perm(user, "documents", "document"):
|
|
return True
|
|
|
|
# revert - откатить версию может автор или менеджер
|
|
if action == "revert":
|
|
return user.has_perm("documents.change_document", obj)
|
|
|
|
# add_related/remove_related → проверяем django-rules change_document
|
|
if action in ("add_related", "remove_related"):
|
|
return user.has_perm("documents.change_document", obj)
|
|
|
|
if action in ("comments", "document_comment"):
|
|
return _user_has_document_access(user, obj)
|
|
|
|
# PUT/PATCH → проверяем django-rules change_document
|
|
if request.method in ("PUT", "PATCH"):
|
|
return user.has_perm("documents.change_document", obj)
|
|
|
|
# DELETE → проверяем django-rules delete_document
|
|
if request.method == "DELETE":
|
|
return user.has_perm("documents.delete_document", obj)
|
|
|
|
# SAFE/ack/download без модельных прав → только пользователи,
|
|
# которым документ доступен.
|
|
if request.method in SAFE_METHODS or action in (
|
|
"acknowledge",
|
|
"download",
|
|
"archive",
|
|
"comments",
|
|
"document_comment",
|
|
"set_maximally_hidden",
|
|
):
|
|
return _user_has_document_access(user, obj)
|
|
|
|
# Небезопасные без прав — нельзя (POST custom actions)
|
|
return False
|