Fix: handle errors for trash actions and only show documents user can restore or delete (#7119)

This commit is contained in:
shamoon
2024-06-27 13:33:39 -07:00
committed by GitHub
parent f01283c309
commit ac0ed0def8
6 changed files with 195 additions and 43 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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):