mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge pull request #3174 from paperless-ngx/fix/issue-3172
Fix: respect permissions on document view actions
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
| @@ -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): | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon