Skip to content

Commit 12f1205

Browse files
committed
refine code and add test
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent dc0555c commit 12f1205

2 files changed

Lines changed: 69 additions & 21 deletions

File tree

dje/api_permissions.py

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# See https://aboutcode.org for more information about AboutCode FOSS projects.
77
#
88

9-
109
from django.contrib.auth import get_user_model
1110
from django.core.exceptions import ObjectDoesNotExist
1211

@@ -28,37 +27,22 @@ class CanManageObjectPermissions(permissions.BasePermission):
2827
"""
2928
Allows managing object-level permissions if the user is:
3029
- a superuser, or
31-
- the object's owner (configurable via ``owner_field`` on the View), or
32-
- has a special manage permission (global or object-level).
30+
- the object's owner (configurable via ``owner_field`` on the View).
3331
"""
3432

3533
owner_field = "created_by"
36-
manage_permission_codename = "manage_object_permissions"
3734

3835
def has_object_permission(self, request, view, obj):
3936
user = request.user
4037
if not user.is_authenticated:
4138
return False
4239

43-
# 1. Superusers always allowed
4440
if user.is_superuser:
4541
return True
4642

47-
# 2. Check if user matches object's owner field
48-
# The field can be overridden on the ViewSet (e.g., owner_field = "owner")
4943
owner_field = getattr(view, "owner_field", self.owner_field)
5044
owner = getattr(obj, owner_field, None)
51-
if owner == user:
52-
return True
53-
54-
# 3. Check for specific manage permission (global or object-level)
55-
app_label = obj._meta.app_label
56-
codename = getattr(view, "manage_permission_codename", self.manage_permission_codename)
57-
perm_name = f"{app_label}.{codename}"
58-
if user.has_perm(perm_name) or user.has_perm(perm_name, obj):
59-
return True
60-
61-
return False
45+
return owner == user
6246

6347

6448
class ObjectPermissionSerializer(serializers.Serializer):
@@ -100,9 +84,9 @@ class ObjectPermissionsMixin:
10084
Mixin that adds a `/permissions/` endpoint for any object-level ViewSet.
10185
Supports GET (list), POST (assign), and DELETE (remove) operations.
10286
103-
GET /api/{model}/{uuid}/permissions/ list all users and perms
104-
POST /api/{model}/{uuid}/permissions/ assign perms to a user
105-
DELETE /api/{model}/{uuid}/permissions/ remove perms from a user
87+
GET /api/{model}/{uuid}/permissions/ list all users and perms
88+
POST /api/{model}/{uuid}/permissions/ assign perms to a user
89+
DELETE /api/{model}/{uuid}/permissions/ remove perms from a user
10690
"""
10791

10892
@action(

product_portfolio/tests/test_api.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,70 @@ def test_api_product_endpoint_cyclonedx_sbom_action(self):
586586
self.assertEqual(status.HTTP_400_BAD_REQUEST, response.status_code)
587587
self.assertEqual("Spec version 10.10 not supported", response.data)
588588

589+
def test_api_product_endpoint_manage_permissions_action(self):
590+
url = reverse("api_v2:product-manage-permissions", args=[self.product1.uuid])
591+
592+
# User without view_product gets 404 (object not in secured queryset)
593+
self.client.login(username=self.base_user.username, password="secret")
594+
response = self.client.get(url)
595+
self.assertEqual(status.HTTP_404_NOT_FOUND, response.status_code)
596+
597+
# User with view_product but not owner gets 403
598+
assign_perm("view_product", self.base_user, self.product1)
599+
response = self.client.get(url)
600+
self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code)
601+
602+
# Superuser can GET the permissions list
603+
self.client.login(username=self.super_user.username, password="secret")
604+
response = self.client.get(url)
605+
self.assertEqual(status.HTTP_200_OK, response.status_code)
606+
# base_user has view_product at this point
607+
self.assertEqual(1, len(response.data))
608+
self.assertEqual(self.base_user.username, response.data[0]["username"])
609+
self.assertIn("view_product", response.data[0]["object_permissions"])
610+
611+
# Superuser can POST to assign permissions
612+
data = {"user": self.admin_user.username, "permissions": ["view_product"]}
613+
response = self.client.post(url, data, format="json")
614+
self.assertEqual(status.HTTP_200_OK, response.status_code)
615+
self.assertEqual({"status": "permissions assigned"}, response.data)
616+
self.assertIn("view_product", get_perms(self.admin_user, self.product1))
617+
618+
# Superuser can DELETE to remove permissions
619+
data = {"user": self.admin_user.username, "permissions": ["view_product"]}
620+
response = self.client.delete(url, data, content_type="application/json")
621+
self.assertEqual(status.HTTP_200_OK, response.status_code)
622+
self.assertEqual({"status": "permissions removed"}, response.data)
623+
self.assertNotIn("view_product", get_perms(self.admin_user, self.product1))
624+
625+
# Product creator (created_by) can manage permissions
626+
self.product1.created_by = self.admin_user
627+
self.product1.save()
628+
assign_perm("view_product", self.admin_user, self.product1)
629+
self.client.login(username=self.admin_user.username, password="secret")
630+
data = {"user": self.base_user.username, "permissions": ["change_product"]}
631+
response = self.client.post(url, data, format="json")
632+
self.assertEqual(status.HTTP_200_OK, response.status_code)
633+
self.assertIn("change_product", get_perms(self.base_user, self.product1))
634+
635+
# Invalid permission codename returns 400
636+
self.client.login(username=self.super_user.username, password="secret")
637+
data = {"user": self.base_user.username, "permissions": ["nonexistent_perm"]}
638+
response = self.client.post(url, data, format="json")
639+
self.assertEqual(status.HTTP_400_BAD_REQUEST, response.status_code)
640+
self.assertIn("errors", response.data)
641+
642+
# Missing required fields returns 400
643+
response = self.client.post(url, {}, format="json")
644+
self.assertEqual(status.HTTP_400_BAD_REQUEST, response.status_code)
645+
self.assertIn("errors", response.data)
646+
647+
# Empty permissions list returns 400
648+
data = {"user": self.base_user.username, "permissions": []}
649+
response = self.client.post(url, data, format="json")
650+
self.assertEqual(status.HTTP_400_BAD_REQUEST, response.status_code)
651+
self.assertIn("errors", response.data)
652+
589653

590654
class ProductRelatedAPITestCase(TestCase):
591655
def setUp(self):

0 commit comments

Comments
 (0)