🐛(backend) prevent owner from leaving a soft-deleted document

Owners could leave trashed documents via the leave endpoint, deleting
their `DocumentAccess` and all subtree access. When the last owner
leaves a document it creates an orphan as no owners remains in the doc.

Signed-off-by: BOUKERFA Mohamed El Amine <boukerfa.ma@gmail.com>
This commit is contained in:
Amine BOUKERFA
2026-06-29 13:04:04 +00:00
committed by GitHub
parent a64b5cb447
commit 36dced1e56
3 changed files with 43 additions and 5 deletions
+1
View File
@@ -24,6 +24,7 @@ and this project adheres to
### Fixed
- 🐛(backend) prevent owner from leaving a soft-deleted document
- 🐛(frontend) fix removed item in the tree #2420
- 🐛(frontend) fix service worker causing reload on tab focus #2454
+9 -5
View File
@@ -1326,11 +1326,15 @@ class Document(MP_Node, BaseModel):
) and not is_deleted
# compute can_leave
# A user can leave a document if it has non privileged role on the document or it has
# access to it with a link_trace
can_leave = user.is_authenticated and (
(has_access_role and not is_owner_or_admin)
or (not has_access_role and self.has_link_trace(user))
# An authenticated user can leave a document if it has a non
# privileged role on the document or access to it with a link_trace
can_leave = (
user.is_authenticated
and not is_deleted
and (
(has_access_role and not is_owner_or_admin)
or (not has_access_role and self.has_link_trace(user))
)
)
link_select_options = LinkReachChoices.get_select_options(
@@ -183,6 +183,39 @@ def test_api_documents_leave_connected_access_with_privileged_role_not_allowed(
assert models.DocumentAccess.objects.count() == 5
@pytest.mark.parametrize(
"link_reach",
models.LinkReachChoices.values,
)
def test_api_documents_leave_connected_owner_on_deleted_document_not_allowed(
link_reach,
):
"""
An owner can not leave a soft-deleted document, even when it also has a link_trace.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(
link_reach=link_reach,
link_traces=[user],
users=[(user, models.RoleChoices.OWNER)],
)
document.soft_delete()
assert models.DocumentAccess.objects.filter(document=document, user=user).exists()
assert models.LinkTrace.objects.filter(document=document, user=user).exists()
client = APIClient()
client.force_login(user)
response = client.post(f"/api/v1.0/documents/{document.id!s}/leave/")
assert response.status_code == status.HTTP_403_FORBIDDEN
# The owner kept its access and link_trace: it did not leave the trashed document.
assert models.DocumentAccess.objects.filter(document=document, user=user).exists()
assert models.LinkTrace.objects.filter(document=document, user=user).exists()
@pytest.mark.parametrize(
"link_reach",
models.LinkReachChoices.values,