♻️(backend) open tree endpoint to deleted documents only for owners

The tree endpoint will now return a result only for owners. For other
users the endpoint still returns a 403. Also, the endpoint does look for
ancestors anymore, it only stay on the current document.
This commit is contained in:
Manuel Raynaud
2025-10-06 11:25:17 +02:00
committed by Anthony LC
parent f772801fd0
commit 31389bcae2
2 changed files with 88 additions and 21 deletions

View File

@@ -851,11 +851,25 @@ class DocumentViewSet(
try: try:
current_document = ( current_document = (
self.queryset.select_related(None).only("depth", "path").get(pk=pk) self.queryset.select_related(None)
.only("depth", "path", "ancestors_deleted_at")
.get(pk=pk)
) )
except models.Document.DoesNotExist as excpt: except models.Document.DoesNotExist as excpt:
raise drf.exceptions.NotFound() from excpt raise drf.exceptions.NotFound() from excpt
is_deleted = current_document.ancestors_deleted_at is not None
if is_deleted:
if current_document.get_role(user) != models.RoleChoices.OWNER:
raise (
drf.exceptions.PermissionDenied()
if request.user.is_authenticated
else drf.exceptions.NotAuthenticated()
)
highest_readable = current_document
ancestors = self.queryset.select_related(None).filter(pk=pk)
else:
ancestors = ( ancestors = (
( (
current_document.get_ancestors() current_document.get_ancestors()
@@ -864,7 +878,6 @@ class DocumentViewSet(
.filter(ancestors_deleted_at__isnull=True) .filter(ancestors_deleted_at__isnull=True)
.order_by("path") .order_by("path")
) )
# Get the highest readable ancestor # Get the highest readable ancestor
highest_readable = ( highest_readable = (
ancestors.select_related(None) ancestors.select_related(None)
@@ -872,6 +885,7 @@ class DocumentViewSet(
.only("depth", "path") .only("depth", "path")
.first() .first()
) )
if highest_readable is None: if highest_readable is None:
raise ( raise (
drf.exceptions.PermissionDenied() drf.exceptions.PermissionDenied()

View File

@@ -1205,3 +1205,56 @@ def test_api_documents_tree_list_authenticated_related_team_members(
"updated_at": parent.updated_at.isoformat().replace("+00:00", "Z"), "updated_at": parent.updated_at.isoformat().replace("+00:00", "Z"),
"user_role": access.role, "user_role": access.role,
} }
def test_api_documents_tree_list_deleted_document():
"""
Tree of a deleted document should only be accessible to the owner.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
parent = factories.DocumentFactory(link_reach="public")
document, _ = factories.DocumentFactory.create_batch(2, parent=parent)
factories.DocumentFactory(link_reach="public", parent=document)
document.soft_delete()
response = client.get(f"/api/v1.0/documents/{document.id!s}/tree/")
assert response.status_code == 403
def test_api_documents_tree_list_deleted_document_owner(django_assert_num_queries):
"""
Tree of a deleted document should only be accessible to the owner.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
parent = factories.DocumentFactory(link_reach="public", users=[(user, "owner")])
document, _ = factories.DocumentFactory.create_batch(2, parent=parent)
child = factories.DocumentFactory(parent=document)
document.soft_delete()
document.refresh_from_db()
child.refresh_from_db()
with django_assert_num_queries(9):
client.get(f"/api/v1.0/documents/{document.id!s}/tree/")
with django_assert_num_queries(5):
response = client.get(f"/api/v1.0/documents/{document.id!s}/tree/")
assert response.status_code == 200
content = response.json()
assert content["id"] == str(document.id)
assert content["deleted_at"] == document.deleted_at.isoformat().replace(
"+00:00", "Z"
)
assert len(content["children"]) == 1
assert content["children"][0]["id"] == str(child.id)
assert content["children"][0][
"deleted_at"
] == child.ancestors_deleted_at.isoformat().replace("+00:00", "Z")