"""RBAC helpers and DRF permission classes. Authorization is always enforced on the backend. The frontend may hide UI elements, but that is never treated as a security boundary. """ from rest_framework.permissions import BasePermission from apps.rbac.models import Permission def has_perm(user, code: str) -> bool: if not user or not user.is_authenticated: return False if user.is_superuser: return True return Permission.objects.filter(roles__user_roles__user=user, code=code).exists() def has_any_perm(user, codes) -> bool: return any(has_perm(user, c) for c in codes) def user_role_slugs(user): if not user or not user.is_authenticated: return [] return list(user.user_roles.values_list("role__slug", flat=True)) class HasPermission(BasePermission): """Global permission check. Set `perm_code` (or `perm_codes`) on the view.""" perm_code = None perm_codes = None def has_permission(self, request, view): codes = self.perm_codes or ([self.perm_code] if self.perm_code else None) if not codes: return True return has_any_perm(request.user, codes) class ActionPermission(BasePermission): """Maps a DRF viewset action name to a permission code.""" code_map = {} def has_permission(self, request, view): code = self.code_map.get(getattr(view, "action", None)) if code is None: return True return has_perm(request.user, code) class IsOwner(BasePermission): """Object-level ownership for customers accessing their own resources.""" owner_field = "user" def has_object_permission(self, request, view, obj): return getattr(obj, self.owner_field, None) == request.user