Skip to content

♻️(backend) refactored permissions check from BaseAccess serializer to permission class #960

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to
- 🚩(frontend) version MIT only #911
- ✨(backend) integrate maleware_detection from django-lasuite #936
- 🩺(CI) add lint spell mistakes #954
- ♻️ Refactored permissions check from serializers to permission class #343

## Changed

Expand Down
56 changes: 56 additions & 0 deletions src/backend/core/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,59 @@ def has_object_permission(self, request, view, obj):
raise Http404

return has_permission


class CanManageAccessPermission(permissions.BasePermission):
"""
Permission class to check access rights specific to writing (create/update)
"""

def has_permission(self, request, view):
user = request.user

if not (bool(request.auth) or user.is_authenticated):
return False

if view.action == "create":
try:
resource_id = view.kwargs["resource_id"]
except KeyError as exc:
raise exceptions.ValidationError(
f"You must set a resource ID in kwargs to create a new access."
) from exc

if not view.queryset.model.objects.filter( # pylint: disable=no-member
Q(user=user) | Q(team__in=user.teams),
role__in=[RoleChoices.OWNER, RoleChoices.ADMIN], # pylint: disable=no-member
**{view.resource_field_name: resource_id},
).exists():
raise exceptions.PermissionDenied(
"You are not allowed to manage accesses for this resource."
)

role = request.data.get("role")
if (
role == RoleChoices.OWNER
and not view.queryset.model.objects.filter(
Q(user=user) | Q(team__in=user.teams),
**{view.resource_field_name: resource_id},
role=RoleChoices.OWNER,
).exists()
):
raise exceptions.PermissionDenied(
"Only owners of a resource can assign other users as owners."
)
return True

def has_object_permission(self, request, view, obj):
if view.action in ["update", "partial_update"]:
role = request.data.get("role")
can_set_role_to = obj.get_abilities(request.user).get("set_role_to", [])
if role and role not in can_set_role_to:
message = (
f"You are only allowed to set role to {', '.join(can_set_role_to)}"
if can_set_role_to
else f"You are not allowed to set this role for this {getattr(view, 'resource_field_name', 'resource')}."
)
raise exceptions.PermissionDenied(message)
return True
52 changes: 3 additions & 49 deletions src/backend/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,56 +68,10 @@ def get_abilities(self, access) -> dict:

def validate(self, attrs):
"""
Check access rights specific to writing (create/update)
Add the resource ID to the validated data on creation.
"""
request = self.context.get("request")
user = getattr(request, "user", None)
role = attrs.get("role")

# Update
if self.instance:
can_set_role_to = self.instance.get_abilities(user)["set_role_to"]

if role and role not in can_set_role_to:
message = (
f"You are only allowed to set role to {', '.join(can_set_role_to)}"
if can_set_role_to
else "You are not allowed to set this role for this template."
)
raise exceptions.PermissionDenied(message)

# Create
else:
try:
resource_id = self.context["resource_id"]
except KeyError as exc:
raise exceptions.ValidationError(
"You must set a resource ID in kwargs to create a new access."
) from exc

if not self.Meta.model.objects.filter( # pylint: disable=no-member
Q(user=user) | Q(team__in=user.teams),
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
**{self.Meta.resource_field_name: resource_id}, # pylint: disable=no-member
).exists():
raise exceptions.PermissionDenied(
"You are not allowed to manage accesses for this resource."
)

if (
role == models.RoleChoices.OWNER
and not self.Meta.model.objects.filter( # pylint: disable=no-member
Q(user=user) | Q(team__in=user.teams),
role=models.RoleChoices.OWNER,
**{self.Meta.resource_field_name: resource_id}, # pylint: disable=no-member
).exists()
):
raise exceptions.PermissionDenied(
"Only owners of a resource can assign other users as owners."
)

# pylint: disable=no-member
attrs[f"{self.Meta.resource_field_name}_id"] = self.context["resource_id"]
if not self.instance:
attrs[f"{self.Meta.resource_field_name}_id"] = self.context["resource_id"]
return attrs


Expand Down
12 changes: 10 additions & 2 deletions src/backend/core/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1441,7 +1441,11 @@ class DocumentAccessViewSet(

lookup_field = "pk"
pagination_class = Pagination
permission_classes = [permissions.IsAuthenticated, permissions.AccessPermission]
permission_classes = [
permissions.IsAuthenticated,
permissions.CanManageAccessPermission,
permissions.AccessPermission,
]
queryset = models.DocumentAccess.objects.select_related("user").all()
resource_field_name = "document"
serializer_class = serializers.DocumentAccessSerializer
Expand Down Expand Up @@ -1613,7 +1617,11 @@ class TemplateAccessViewSet(

lookup_field = "pk"
pagination_class = Pagination
permission_classes = [permissions.IsAuthenticated, permissions.AccessPermission]
permission_classes = [
permissions.IsAuthenticated,
permissions.CanManageAccessPermission,
permissions.AccessPermission,
]
queryset = models.TemplateAccess.objects.select_related("user").all()
resource_field_name = "template"
serializer_class = serializers.TemplateAccessSerializer
Expand Down