]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Respect permissions on document view actions 3174/head
authorshamoon <4887959+shamoon@users.noreply.github.com>
Tue, 25 Apr 2023 16:20:56 +0000 (09:20 -0700)
committershamoon <4887959+shamoon@users.noreply.github.com>
Wed, 26 Apr 2023 05:49:37 +0000 (22:49 -0700)
src/documents/permissions.py
src/documents/tests/test_api.py
src/documents/views.py

index d4114e488c6b5fe09cb9a097f790a6e7e0bf9df7..0bfff031c9e5ccb218e1e081144044c3db4dadd6 100644 (file)
@@ -9,6 +9,7 @@ from guardian.shortcuts import get_users_with_perms
 from guardian.shortcuts import remove_perm
 from rest_framework.permissions import BasePermission
 from rest_framework.permissions import DjangoObjectPermissions
+from guardian.core import ObjectPermissionChecker
 
 
 class PaperlessObjectPermissions(DjangoObjectPermissions):
@@ -114,3 +115,8 @@ def get_objects_for_user_owner_aware(user, perms, Model):
         accept_global_perms=False,
     )
     return objects_owned | objects_unowned | objects_with_perms
+
+
+def has_perms_owner_aware(user, perms, obj):
+    checker = ObjectPermissionChecker(user)
+    return obj.owner is None or obj.owner == user or checker.has_perm(perms, obj)
index 6cd6b610a79a35a5181c5d95667e15c93c4d9a6d..ba5c58f3f74377eb2eb22d514eb9f482220596db 100644 (file)
@@ -206,6 +206,67 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         self.assertEqual(response.content, content_thumbnail)
 
+    def test_document_actions_with_perms(self):
+        """
+        GIVEN:
+            - Document with owner and without granted permissions
+            - User is then granted permissions
+        WHEN:
+            - User tries to load preview, thumbnail
+        THEN:
+            - Initially, HTTP 403 Forbidden
+            - With permissions, HTTP 200 OK
+        """
+        _, filename = tempfile.mkstemp(dir=self.dirs.originals_dir)
+
+        content = b"This is a test"
+        content_thumbnail = b"thumbnail content"
+
+        with open(filename, "wb") as f:
+            f.write(content)
+
+        user1 = User.objects.create_user(username="test1")
+        user2 = User.objects.create_user(username="test2")
+        user1.user_permissions.add(*Permission.objects.filter(codename="view_document"))
+        user2.user_permissions.add(*Permission.objects.filter(codename="view_document"))
+
+        self.client.force_authenticate(user2)
+
+        doc = Document.objects.create(
+            title="none",
+            filename=os.path.basename(filename),
+            mime_type="application/pdf",
+            owner=user1,
+        )
+
+        with open(
+            os.path.join(self.dirs.thumbnail_dir, f"{doc.pk:07d}.webp"),
+            "wb",
+        ) as f:
+            f.write(content_thumbnail)
+
+        response = self.client.get(f"/api/documents/{doc.pk}/download/")
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+        response = self.client.get(f"/api/documents/{doc.pk}/preview/")
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+        response = self.client.get(f"/api/documents/{doc.pk}/thumb/")
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+        from guardian.shortcuts import assign_perm
+
+        assign_perm("view_document", user2, doc)
+
+        response = self.client.get(f"/api/documents/{doc.pk}/download/")
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+        response = self.client.get(f"/api/documents/{doc.pk}/preview/")
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+        response = self.client.get(f"/api/documents/{doc.pk}/thumb/")
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
     @override_settings(FILENAME_FORMAT="")
     def test_download_with_archive(self):
 
index 1edbdccc35f97d1bee2194154d2bfa44b2bc36b6..446bb2ecc4a6c3406a84e1a04fdda2f39c211dd8 100644 (file)
@@ -23,7 +23,7 @@ from django.db.models import Sum
 from django.db.models import When
 from django.db.models.functions import Length
 from django.db.models.functions import Lower
-from django.http import Http404
+from django.http import Http404, HttpResponseForbidden
 from django.http import HttpResponse
 from django.http import HttpResponseBadRequest
 from django.shortcuts import get_object_or_404
@@ -33,7 +33,7 @@ from django.views.decorators.cache import cache_control
 from django.views.generic import TemplateView
 from django_filters.rest_framework import DjangoFilterBackend
 from documents.filters import ObjectOwnedOrGrantedPermissionsFilter
-from documents.permissions import PaperlessAdminPermissions
+from documents.permissions import PaperlessAdminPermissions, has_perms_owner_aware
 from documents.permissions import PaperlessObjectPermissions
 from documents.tasks import consume_file
 from langdetect import detect
@@ -59,7 +59,6 @@ from rest_framework.viewsets import GenericViewSet
 from rest_framework.viewsets import ModelViewSet
 from rest_framework.viewsets import ReadOnlyModelViewSet
 from rest_framework.viewsets import ViewSet
-
 from .bulk_download import ArchiveOnlyStrategy
 from .bulk_download import OriginalAndArchiveStrategy
 from .bulk_download import OriginalsOnlyStrategy
@@ -295,6 +294,12 @@ class DocumentViewSet(
 
     def file_response(self, pk, request, disposition):
         doc = Document.objects.get(id=pk)
+        if request.user is not None and not has_perms_owner_aware(
+            request.user,
+            "view_document",
+            doc,
+        ):
+            return HttpResponseForbidden("Insufficient permissions")
         if not self.original_requested(request) and doc.has_archive_version:
             file_handle = doc.archive_file
             filename = doc.get_public_filename(archive=True)
@@ -354,6 +359,12 @@ class DocumentViewSet(
     def metadata(self, request, pk=None):
         try:
             doc = Document.objects.get(pk=pk)
+            if request.user is not None and not has_perms_owner_aware(
+                request.user,
+                "view_document",
+                doc,
+            ):
+                return HttpResponseForbidden("Insufficient permissions")
         except Document.DoesNotExist:
             raise Http404
 
@@ -391,6 +402,12 @@ class DocumentViewSet(
     @action(methods=["get"], detail=True)
     def suggestions(self, request, pk=None):
         doc = get_object_or_404(Document, pk=pk)
+        if request.user is not None and not has_perms_owner_aware(
+            request.user,
+            "view_document",
+            doc,
+        ):
+            return HttpResponseForbidden("Insufficient permissions")
 
         classifier = load_classifier()
 
@@ -430,6 +447,12 @@ class DocumentViewSet(
     def thumb(self, request, pk=None):
         try:
             doc = Document.objects.get(id=pk)
+            if request.user is not None and not has_perms_owner_aware(
+                request.user,
+                "view_document",
+                doc,
+            ):
+                return HttpResponseForbidden("Insufficient permissions")
             if doc.storage_type == Document.STORAGE_TYPE_GPG:
                 handle = GnuPG.decrypted(doc.thumbnail_file)
             else:
@@ -468,6 +491,12 @@ class DocumentViewSet(
     def notes(self, request, pk=None):
         try:
             doc = Document.objects.get(pk=pk)
+            if request.user is not None and not has_perms_owner_aware(
+                request.user,
+                "view_document",
+                doc,
+            ):
+                return HttpResponseForbidden("Insufficient permissions")
         except Document.DoesNotExist:
             raise Http404