Features: 1) Extend permissions to include user-based scoping for instances and querysets; 2) Enable admin override with appropriate model-level permissions; 3) Refine queryset filtering logic to enforce user ownership rules.

Fixes: 1) Address incorrect scoping for user-based permissions; 2) Correct queryset handling for actions like "list" and "retrieve."

Extra: Refactor permission checks for clarity and maintainability; simplify conditional logic in queryset filters.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-05-29 13:33:06 +03:00
parent 79c97b7e5a
commit bc12dcf35c

View file

@ -22,6 +22,8 @@ class EvibesPermission(permissions.BasePermission):
- Standard model perms ('add', 'view', 'change', 'delete') are enforced for all other actions, - Standard model perms ('add', 'view', 'change', 'delete') are enforced for all other actions,
including for staff users. including for staff users.
- Publicly visible models allow anonymous list/retrieve. - Publicly visible models allow anonymous list/retrieve.
- If an instance or queryset has a "user" attribute, ensure that the request.user is the same,
unless the user is an admin with the required django permission.
""" """
ACTION_PERM_MAP = { ACTION_PERM_MAP = {
@ -64,19 +66,58 @@ class EvibesPermission(permissions.BasePermission):
if request.user.has_perm(f"{app_label}.{codename}"): if request.user.has_perm(f"{app_label}.{codename}"):
return True return True
return bool(action in ("list", "retrieve") and getattr(model, "is_publicly_visible", False)) return bool(
action in ("list", "retrieve")
and getattr(model, "is_publicly_visible", False)
)
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
if hasattr(obj, "user"):
if obj.user == request.user:
return True
# Allow admins who hold the required model permission
app_label = obj._meta.app_label
model_name = obj._meta.model_name
action = getattr(view, "action", None)
perm_prefix = self.ACTION_PERM_MAP.get(action)
return bool(perm_prefix and request.user.has_perm(f"{app_label}.{perm_prefix}_{model_name}"))
model = view.queryset.model
app_label = model._meta.app_label
model_name = model._meta.model_name
action = getattr(view, "action", None)
perm_prefix = self.ACTION_PERM_MAP.get(action)
return bool(perm_prefix and request.user.has_perm(f"{app_label}.{perm_prefix}_{model_name}"))
def has_queryset_permission(self, request, view, queryset): def has_queryset_permission(self, request, view, queryset):
""" """
Filter the base queryset according to the action and user. Filter the base queryset according to the action and user.
Staff users still require view permissions to see records. For models with a "user" field, restrict access to records belonging to the request user
unless the admin holds the needed permissions.
""" """
model = view.queryset.model model = view.queryset.model
app_label = model._meta.app_label app_label = model._meta.app_label
model_name = model._meta.model_name model_name = model._meta.model_name
if view.action in self.USER_SCOPED_ACTIONS: if hasattr(model, "user"):
return queryset.filter(user=request.user) if view.action in self.USER_SCOPED_ACTIONS:
return queryset.filter(user=request.user)
if view.action in ("list", "retrieve"):
if request.user.has_perm(f"{app_label}.view_{model_name}"):
if request.user.is_staff:
return queryset
return queryset.filter(user=request.user, is_active=True)
return queryset.none()
base = queryset.filter(is_active=True, user=request.user)
if request.user.is_staff and request.user.has_perm(
f"{app_label}.{self.ACTION_PERM_MAP.get(view.action)}_{model_name}"
):
return queryset.filter(is_active=True)
return base
if view.action in ("list", "retrieve"): if view.action in ("list", "retrieve"):
if request.user.has_perm(f"{app_label}.view_{model_name}"): if request.user.has_perm(f"{app_label}.view_{model_name}"):
@ -87,10 +128,7 @@ class EvibesPermission(permissions.BasePermission):
base = queryset.filter(is_active=True) base = queryset.filter(is_active=True)
match view.action: match view.action:
case "update": case "update" | "partial_update":
if request.user.has_perm(f"{app_label}.change_{model_name}"):
return base
case "partial_update":
if request.user.has_perm(f"{app_label}.change_{model_name}"): if request.user.has_perm(f"{app_label}.change_{model_name}"):
return base return base
case "destroy": case "destroy":