Corp/backend/api/v1/permissions.py

184 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# backend\api\v1\permissions.py
from __future__ import annotations
from rest_framework.permissions import (
SAFE_METHODS,
BasePermission,
DjangoModelPermissions,
)
from employees.models import Department, EmployeeDepartment
MANAGE_PERM = "manage_department"
CHANGE_HEAD_PERM = "change_department_head"
ASSIGN_ROLE_PERM = "assign_department_role"
def user_is_dept_head(user, dept: Department) -> bool:
return bool(
user and user.is_authenticated and dept.head_id == getattr(user, "id", None)
)
def user_is_staffish(user) -> bool:
return bool(user and user.is_authenticated and (user.is_superuser or user.is_staff))
def user_has_dept_perm(user, dept: Department, perm_code: str) -> bool:
if not (user and user.is_authenticated):
return False
# staff/superuser — сразу ок (оставим как было)
if user_is_staffish(user):
return True
# руководитель — сразу ок
if user_is_dept_head(user, dept):
return True
# основная проверка: активная связь + у роли есть нужный Permission
return EmployeeDepartment.objects.filter(
employee_id=user.id,
department_id=dept.id,
is_active=True,
role__permissions__codename=perm_code,
).exists()
class IsDeptManagerForWrite(BasePermission):
"""
Для DepartmentViewSet.
View может указать:
self.required_perm_code in {"change_department_head", "assign_department_role", "manage_department"}
По умолчанию — "manage_department".
"""
message = "Недостаточно прав для управления отделом."
def has_object_permission(self, request, view, obj):
dept = getattr(obj, "department", obj)
if request.method in SAFE_METHODS:
return True
user = request.user
if not user or not user.is_authenticated:
return False
if user_is_staffish(user) or user_is_dept_head(user, dept):
return True
perm_code = getattr(view, "required_perm_code", MANAGE_PERM)
# NEW: если у пользователя есть глобальный модельный пермишен (employees.<perm_code>) — пускаем
# (сбросим кэш прав — в тестах перм выдают после первого запроса)
for attr in ("_perm_cache", "_user_perm_cache", "_group_perm_cache"):
if hasattr(user, attr):
try:
delattr(user, attr)
except Exception:
pass
app_label = Department._meta.app_label # "employees"
if user.has_perm(f"{app_label}.{perm_code}"):
return True
# иначе — проверяем наличие нужного права через роль в данном отделе
return user_has_dept_perm(user, dept, perm_code)
class IsSelfOrStaff(BasePermission):
# На class-level пускаем любого аутентифицированного.
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
# На object-level: сам объект, либо staff/superuser.
def has_object_permission(self, request, view, obj):
user = request.user
if not user or not user.is_authenticated:
return False
if getattr(user, "is_staff", False) or getattr(user, "is_superuser", False):
return True
# Основной случай — объект сотрудника.
try:
from employees.models import (
Employee,
) # локальный импорт, чтобы избежать циклов
except Exception:
Employee = None
if Employee is not None and isinstance(obj, Employee):
return obj.pk == user.pk
# Fallback: сравнить id из URL, если obj не Employee (на всякий случай)
lookup = getattr(view, "lookup_field", "pk")
return str(view.kwargs.get(lookup)) == str(user.pk)
class IsAuthorOrStaffForComments(BasePermission):
"""
Создание/чтение — любой аутентифицированный (проверяет IsAuthenticated во view).
Изменение/удаление — автор комментария или staff/superuser.
"""
message = "Можно редактировать/удалять только свои комментарии."
def has_permission(self, request, view):
# IsAuthenticated уже стоит во вьюхе; дублируем True для небезопасных методов,
# чтобы проверка прошла до object-level.
return bool(request.user and request.user.is_authenticated)
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
user = request.user
if not (user and user.is_authenticated):
return False
if user_is_staffish(user):
return True
return getattr(obj, "author_id", None) == getattr(user, "id", None)
class AdminOrActionOrModelPerms(DjangoModelPermissions):
"""
Админ/суперпользователь → всегда True.
Если задан код права (view.required_perm_code или required_perms_by_action[action]),
проверяем его через user.has_perm(...). Иначе — стандартные DjangoModelPermissions.
"""
def has_permission(self, request, view):
user = request.user
if not user or not user.is_authenticated:
return False
if user.is_superuser or user.is_staff:
return True
mapping = getattr(view, "required_perms_by_action", {}) or {}
action = getattr(view, "action", None)
code = getattr(view, "required_perm_code", None) or mapping.get(action)
if code:
return user.has_perm(code)
return super().has_permission(request, view)
def has_object_permission(self, request, view, obj):
user = request.user
if user.is_superuser or user.is_staff:
return True
mapping = getattr(view, "required_perms_by_action", {}) or {}
action = getattr(view, "action", None)
code = getattr(view, "required_perm_code", None) or mapping.get(action)
if code:
return user.has_perm(code)
return super().has_object_permission(request, view, obj)
__all__ = [
"MANAGE_PERM",
"CHANGE_HEAD_PERM",
"ASSIGN_ROLE_PERM",
"IsDeptManagerForWrite",
"IsSelfOrStaff",
"IsAuthorOrStaffForComments",
"AdminOrActionOrModelPerms",
"user_is_dept_head",
"user_has_dept_perm",
]