Skip to content

Commit 8f654b4

Browse files
author
Johann LORBER
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
1 parent bd79f84 commit 8f654b4

File tree

4 files changed

+70
-51
lines changed

4 files changed

+70
-51
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to
1111
## Added
1212

1313
✨ Add a custom callout block to the editor #892
14+
♻️ Refactored permissions check from serializers to permission class #343
1415

1516
## [3.2.1] - 2025-05-06
1617

src/backend/core/api/permissions.py

+56
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,59 @@ 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 check access rights specific to writing (create/update)
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+
try:
152+
resource_id = view.kwargs["resource_id"]
153+
except KeyError as exc:
154+
raise exceptions.ValidationError(
155+
f"You must set a resource ID in kwargs to create a new access."
156+
) from exc
157+
158+
if not view.queryset.model.objects.filter( # pylint: disable=no-member
159+
Q(user=user) | Q(team__in=user.teams),
160+
role__in=[RoleChoices.OWNER, RoleChoices.ADMIN], # pylint: disable=no-member
161+
**{view.resource_field_name: resource_id},
162+
).exists():
163+
raise exceptions.PermissionDenied(
164+
"You are not allowed to manage accesses for this resource."
165+
)
166+
167+
role = request.data.get("role")
168+
if (
169+
role == RoleChoices.OWNER
170+
and not view.queryset.model.objects.filter(
171+
Q(user=user) | Q(team__in=user.teams),
172+
**{view.resource_field_name: resource_id},
173+
role=RoleChoices.OWNER,
174+
).exists()
175+
):
176+
raise exceptions.PermissionDenied(
177+
"Only owners of a resource can assign other users as owners."
178+
)
179+
return True
180+
181+
def has_object_permission(self, request, view, obj):
182+
if view.action in ["update", "partial_update"]:
183+
role = request.data.get("role")
184+
can_set_role_to = obj.get_abilities(request.user).get("set_role_to", [])
185+
if role and role not in can_set_role_to:
186+
message = (
187+
f"You are only allowed to set role to {', '.join(can_set_role_to)}"
188+
if can_set_role_to
189+
else f"You are not allowed to set this role for this {getattr(view, 'resource_field_name', 'resource')}."
190+
)
191+
raise exceptions.PermissionDenied(message)
192+
return True

src/backend/core/api/serializers.py

+3-49
Original file line numberDiff line numberDiff line change
@@ -68,56 +68,10 @@ def get_abilities(self, access) -> dict:
6868

6969
def validate(self, attrs):
7070
"""
71-
Check access rights specific to writing (create/update)
71+
Add the resource ID to the validated data on creation.
7272
"""
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"]
73+
if not self.instance:
74+
attrs[f"{self.Meta.resource_field_name}_id"] = self.context["resource_id"]
12175
return attrs
12276

12377

src/backend/core/api/viewsets.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -1423,7 +1423,11 @@ class DocumentAccessViewSet(
14231423

14241424
lookup_field = "pk"
14251425
pagination_class = Pagination
1426-
permission_classes = [permissions.IsAuthenticated, permissions.AccessPermission]
1426+
permission_classes = [
1427+
permissions.IsAuthenticated,
1428+
permissions.CanManageAccessPermission,
1429+
permissions.AccessPermission,
1430+
]
14271431
queryset = models.DocumentAccess.objects.select_related("user").all()
14281432
resource_field_name = "document"
14291433
serializer_class = serializers.DocumentAccessSerializer
@@ -1595,7 +1599,11 @@ class TemplateAccessViewSet(
15951599

15961600
lookup_field = "pk"
15971601
pagination_class = Pagination
1598-
permission_classes = [permissions.IsAuthenticated, permissions.AccessPermission]
1602+
permission_classes = [
1603+
permissions.IsAuthenticated,
1604+
permissions.CanManageAccessPermission,
1605+
permissions.AccessPermission,
1606+
]
15991607
queryset = models.TemplateAccess.objects.select_related("user").all()
16001608
resource_field_name = "template"
16011609
serializer_class = serializers.TemplateAccessSerializer

0 commit comments

Comments
 (0)