mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Fix: handle errors for trash actions and only show documents user can restore or delete (#7119)
This commit is contained in:
@@ -276,3 +276,17 @@ class ObjectOwnedOrGrantedPermissionsFilter(ObjectPermissionsFilter):
|
||||
objects_owned = queryset.filter(owner=request.user)
|
||||
objects_unowned = queryset.filter(owner__isnull=True)
|
||||
return objects_with_perms | objects_owned | objects_unowned
|
||||
|
||||
|
||||
class ObjectOwnedPermissionsFilter(ObjectPermissionsFilter):
|
||||
"""
|
||||
A filter backend that limits results to those where the requesting user
|
||||
owns the objects or objects without an owner (for backwards compat)
|
||||
"""
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
if request.user.is_superuser:
|
||||
return queryset
|
||||
objects_owned = queryset.filter(owner=request.user)
|
||||
objects_unowned = queryset.filter(owner__isnull=True)
|
||||
return objects_owned | objects_unowned
|
||||
|
@@ -1,3 +1,4 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
from rest_framework import status
|
||||
@@ -10,7 +11,8 @@ class TestTrashAPI(APITestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.user = User.objects.create_superuser(username="temp_admin")
|
||||
self.user = User.objects.create_user(username="temp_admin")
|
||||
self.user.user_permissions.add(*Permission.objects.all())
|
||||
self.client.force_authenticate(user=self.user)
|
||||
cache.clear()
|
||||
|
||||
@@ -97,6 +99,56 @@ class TestTrashAPI(APITestCase):
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(Document.global_objects.count(), 0)
|
||||
|
||||
def test_api_trash_show_owned_only(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing documents in trash
|
||||
WHEN:
|
||||
- API request to show documents in trash for regular user
|
||||
- API request to show documents in trash for superuser
|
||||
THEN:
|
||||
- Only owned documents are returned
|
||||
"""
|
||||
|
||||
document_u1 = Document.objects.create(
|
||||
title="Title",
|
||||
content="content",
|
||||
checksum="checksum",
|
||||
mime_type="application/pdf",
|
||||
owner=self.user,
|
||||
)
|
||||
document_u1.delete()
|
||||
document_not_owned = Document.objects.create(
|
||||
title="Title2",
|
||||
content="content2",
|
||||
checksum="checksum2",
|
||||
mime_type="application/pdf",
|
||||
)
|
||||
document_not_owned.delete()
|
||||
user2 = User.objects.create_user(username="user2")
|
||||
document_u2 = Document.objects.create(
|
||||
title="Title3",
|
||||
content="content3",
|
||||
checksum="checksum3",
|
||||
mime_type="application/pdf",
|
||||
owner=user2,
|
||||
)
|
||||
document_u2.delete()
|
||||
|
||||
# user only sees their own documents or unowned documents
|
||||
resp = self.client.get("/api/trash/")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["count"], 2)
|
||||
self.assertEqual(resp.data["results"][0]["id"], document_not_owned.pk)
|
||||
self.assertEqual(resp.data["results"][1]["id"], document_u1.pk)
|
||||
|
||||
# superuser sees all documents
|
||||
superuser = User.objects.create_superuser(username="superuser")
|
||||
self.client.force_authenticate(user=superuser)
|
||||
resp = self.client.get("/api/trash/")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["count"], 3)
|
||||
|
||||
def test_api_trash_insufficient_permissions(self):
|
||||
"""
|
||||
GIVEN:
|
||||
@@ -107,9 +159,6 @@ class TestTrashAPI(APITestCase):
|
||||
- 403 Forbidden
|
||||
"""
|
||||
|
||||
user1 = User.objects.create_user(username="user1")
|
||||
self.client.force_authenticate(user=user1)
|
||||
self.client.force_login(user=user1)
|
||||
user2 = User.objects.create_user(username="user2")
|
||||
document = Document.objects.create(
|
||||
title="Title",
|
||||
|
@@ -96,6 +96,7 @@ from documents.filters import CustomFieldFilterSet
|
||||
from documents.filters import DocumentFilterSet
|
||||
from documents.filters import DocumentTypeFilterSet
|
||||
from documents.filters import ObjectOwnedOrGrantedPermissionsFilter
|
||||
from documents.filters import ObjectOwnedPermissionsFilter
|
||||
from documents.filters import ShareLinkFilterSet
|
||||
from documents.filters import StoragePathFilterSet
|
||||
from documents.filters import TagFilterSet
|
||||
@@ -2060,7 +2061,7 @@ class SystemStatusView(PassUserMixin):
|
||||
class TrashView(ListModelMixin, PassUserMixin):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = TrashSerializer
|
||||
filter_backends = (ObjectOwnedOrGrantedPermissionsFilter,)
|
||||
filter_backends = (ObjectOwnedPermissionsFilter,)
|
||||
pagination_class = StandardPagination
|
||||
|
||||
model = Document
|
||||
@@ -2079,7 +2080,7 @@ class TrashView(ListModelMixin, PassUserMixin):
|
||||
docs = (
|
||||
Document.global_objects.filter(id__in=doc_ids)
|
||||
if doc_ids is not None
|
||||
else Document.deleted_objects.all()
|
||||
else self.filter_queryset(self.get_queryset()).all()
|
||||
)
|
||||
for doc in docs:
|
||||
if not has_perms_owner_aware(request.user, "delete_document", doc):
|
||||
|
Reference in New Issue
Block a user