Skip to content

Commit 605c546

Browse files
committed
♻️(backend) refactored permissions check from BaseAccess serializer to permission class
extracted access role validation from BaseAccessSerializer into DRF permissions, invoking them in the according viewset Signed-off-by: Johann LORBER <[email protected]>
1 parent 393e7a0 commit 605c546

File tree

4 files changed

+80
-54
lines changed

4 files changed

+80
-54
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ and this project adheres to
3131
- ✅(frontend) Improve tests coverage #949
3232
- ⬆️(docker) upgrade backend image to python 3.13 #973
3333
- ⬆️(docker) upgrade node images to alpine 3.21 #973
34+
- ♻️(backend) Refactored permissions check from serializers to permission class #343
3435

3536
### Fixed
3637
- 🐛(y-provider) increase JSON size limits for transcription conversion #989

src/backend/core/api/permissions.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,68 @@ def has_object_permission(self, request, view, obj):
134134
raise Http404
135135

136136
return has_permission
137+
138+
139+
class CanManageAccessPermission(permissions.BasePermission):
140+
"""
141+
Permission class to control who can create or update access objects for a resource.
142+
"""
143+
144+
def has_permission(self, request, view):
145+
user = request.user
146+
147+
if not (bool(request.auth) or user.is_authenticated):
148+
return False
149+
150+
if view.action != "create":
151+
return True
152+
153+
try:
154+
resource_id = view.kwargs["resource_id"]
155+
except KeyError as exc:
156+
raise exceptions.ValidationError(
157+
"You must set a resource ID in kwargs to create a new access."
158+
) from exc
159+
160+
resource_field = view.resource_field_name
161+
162+
# Check if user is owner or admin for the resource
163+
if not view.queryset.model.objects.filter(
164+
Q(user=user) | Q(team__in=user.teams),
165+
role__in=[RoleChoices.OWNER, RoleChoices.ADMIN],
166+
**{resource_field: resource_id}
167+
).exists():
168+
raise exceptions.PermissionDenied(
169+
"You are not allowed to manage accesses for this resource."
170+
)
171+
172+
if (
173+
request.data.get("role") == RoleChoices.OWNER
174+
and not view.queryset.model.objects.filter(
175+
Q(user=user) | Q(team__in=user.teams),
176+
role=RoleChoices.OWNER,
177+
**{resource_field: resource_id}
178+
).exists()
179+
):
180+
raise exceptions.PermissionDenied(
181+
"Only owners of a resource can assign other users as owners."
182+
)
183+
184+
return True
185+
186+
def has_object_permission(self, request, view, obj):
187+
if view.action in ["update", "partial_update"]:
188+
requested_role = request.data.get("role")
189+
abilities = obj.get_abilities(request.user)
190+
allowed_roles = abilities.get("set_role_to", [])
191+
192+
if requested_role and requested_role not in allowed_roles:
193+
if allowed_roles:
194+
raise exceptions.PermissionDenied(
195+
f"You are only allowed to set role to {', '.join(allowed_roles)}."
196+
)
197+
else:
198+
raise exceptions.PermissionDenied(
199+
f"You are not allowed to set this role for this {getattr(view, 'resource_field_name', 'resource')}."
200+
)
201+
return True

src/backend/core/api/serializers.py

Lines changed: 4 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from django.utils.translation import gettext_lazy as _
1111

1212
import magic
13-
from rest_framework import exceptions, serializers
13+
from rest_framework import serializers
1414

1515
from core import enums, models, utils
1616
from core.services.ai_services import AI_ACTIONS
@@ -67,57 +67,9 @@ def get_abilities(self, access) -> dict:
6767
return {}
6868

6969
def validate(self, attrs):
70-
"""
71-
Check access rights specific to writing (create/update)
72-
"""
73-
request = self.context.get("request")
74-
user = getattr(request, "user", None)
75-
role = attrs.get("role")
76-
77-
# Update
78-
if self.instance:
79-
can_set_role_to = self.instance.get_abilities(user)["set_role_to"]
80-
81-
if role and role not in can_set_role_to:
82-
message = (
83-
f"You are only allowed to set role to {', '.join(can_set_role_to)}"
84-
if can_set_role_to
85-
else "You are not allowed to set this role for this template."
86-
)
87-
raise exceptions.PermissionDenied(message)
88-
89-
# Create
90-
else:
91-
try:
92-
resource_id = self.context["resource_id"]
93-
except KeyError as exc:
94-
raise exceptions.ValidationError(
95-
"You must set a resource ID in kwargs to create a new access."
96-
) from exc
97-
98-
if not self.Meta.model.objects.filter( # pylint: disable=no-member
99-
Q(user=user) | Q(team__in=user.teams),
100-
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
101-
**{self.Meta.resource_field_name: resource_id}, # pylint: disable=no-member
102-
).exists():
103-
raise exceptions.PermissionDenied(
104-
"You are not allowed to manage accesses for this resource."
105-
)
106-
107-
if (
108-
role == models.RoleChoices.OWNER
109-
and not self.Meta.model.objects.filter( # pylint: disable=no-member
110-
Q(user=user) | Q(team__in=user.teams),
111-
role=models.RoleChoices.OWNER,
112-
**{self.Meta.resource_field_name: resource_id}, # pylint: disable=no-member
113-
).exists()
114-
):
115-
raise exceptions.PermissionDenied(
116-
"Only owners of a resource can assign other users as owners."
117-
)
118-
119-
# pylint: disable=no-member
120-
attrs[f"{self.Meta.resource_field_name}_id"] = self.context["resource_id"]
70+
"""Add the resource ID to the validated data on creation."""
71+
if not self.instance:
72+
attrs[f"{self.Meta.resource_field_name}_id"] = self.context["resource_id"] # pylint: disable=no-member
12173
return attrs
12274

12375

src/backend/core/api/viewsets.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,7 +1497,11 @@ class DocumentAccessViewSet(
14971497

14981498
lookup_field = "pk"
14991499
pagination_class = Pagination
1500-
permission_classes = [permissions.IsAuthenticated, permissions.AccessPermission]
1500+
permission_classes = [
1501+
permissions.IsAuthenticated,
1502+
permissions.CanManageAccessPermission,
1503+
permissions.AccessPermission,
1504+
]
15011505
queryset = models.DocumentAccess.objects.select_related("user").all()
15021506
resource_field_name = "document"
15031507
serializer_class = serializers.DocumentAccessSerializer
@@ -1669,7 +1673,11 @@ class TemplateAccessViewSet(
16691673

16701674
lookup_field = "pk"
16711675
pagination_class = Pagination
1672-
permission_classes = [permissions.IsAuthenticated, permissions.AccessPermission]
1676+
permission_classes = [
1677+
permissions.IsAuthenticated,
1678+
permissions.CanManageAccessPermission,
1679+
permissions.AccessPermission
1680+
]
16731681
queryset = models.TemplateAccess.objects.select_related("user").all()
16741682
resource_field_name = "template"
16751683
serializer_class = serializers.TemplateAccessSerializer

0 commit comments

Comments
 (0)