]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Security: enforce ownership for permission updates
authorshamoon <4887959+shamoon@users.noreply.github.com>
Fri, 30 Jan 2026 21:55:55 +0000 (13:55 -0800)
committershamoon <4887959+shamoon@users.noreply.github.com>
Fri, 30 Jan 2026 21:55:55 +0000 (13:55 -0800)
src/documents/serialisers.py
src/documents/tests/test_api_permissions.py

index e96400eff78cc8e71895497afdb75587d1afe6f6..75e73d8787db6e28ec848c935129db3e66123f39 100644 (file)
@@ -40,6 +40,7 @@ from guardian.utils import get_group_obj_perms_model
 from guardian.utils import get_user_obj_perms_model
 from rest_framework import fields
 from rest_framework import serializers
+from rest_framework.exceptions import PermissionDenied
 from rest_framework.fields import SerializerMethodField
 from rest_framework.filters import OrderingFilter
 
@@ -436,6 +437,19 @@ class OwnedObjectSerializer(
         return instance
 
     def update(self, instance, validated_data):
+        user = getattr(self, "user", None)
+        is_superuser = user.is_superuser if user is not None else False
+        is_owner = instance.owner == user if user is not None else False
+        is_unowned = instance.owner is None
+
+        if (
+            ("owner" in validated_data and validated_data["owner"] != instance.owner)
+            or "set_permissions" in validated_data
+        ) and not (is_superuser or is_owner or is_unowned):
+            raise PermissionDenied(
+                _("Insufficient permissions."),
+            )
+
         if "set_permissions" in validated_data:
             self._set_permissions(validated_data["set_permissions"], instance)
         self.validate_unique_together(validated_data, instance)
index bc81dabe91167299e81cc2c6d73f6c1a3cba12f7..31b8607452552432696d87ccafe87510ecf9a6b8 100644 (file)
@@ -441,6 +441,59 @@ class TestApiAuth(DirectoriesMixin, APITestCase):
         self.assertTrue(checker.has_perm("change_document", doc))
         self.assertIn("change_document", get_perms(group1, doc))
 
+    def test_document_permissions_change_requires_owner(self):
+        owner = User.objects.create_user(username="owner")
+        editor = User.objects.create_user(username="editor")
+        editor.user_permissions.add(
+            *Permission.objects.all(),
+        )
+
+        doc = Document.objects.create(
+            title="Ownered doc",
+            content="sensitive",
+            checksum="abc123",
+            mime_type="application/pdf",
+            owner=owner,
+        )
+
+        assign_perm("view_document", editor, doc)
+        assign_perm("change_document", editor, doc)
+
+        self.client.force_authenticate(editor)
+        response = self.client.patch(
+            f"/api/documents/{doc.pk}/",
+            json.dumps(
+                {
+                    "set_permissions": {
+                        "view": {
+                            "users": [editor.id],
+                            "groups": [],
+                        },
+                        "change": {
+                            "users": None,
+                            "groups": None,
+                        },
+                    },
+                },
+            ),
+            content_type="application/json",
+        )
+
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+        self.client.force_authenticate(editor)
+        response = self.client.patch(
+            f"/api/documents/{doc.pk}/",
+            json.dumps(
+                {
+                    "owner": editor.id,
+                },
+            ),
+            content_type="application/json",
+        )
+
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
     def test_dynamic_permissions_fields(self):
         user1 = User.objects.create_user(username="user1")
         user1.user_permissions.add(*Permission.objects.filter(codename="view_document"))