From 2ece405b6dbe8128bd80345da1922228536ac876 Mon Sep 17 00:00:00 2001 From: stumpylog <797416+stumpylog@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:24:41 -0700 Subject: [PATCH] test: add permission-filtering security regression suite for Document --- .../test_permission_filtering_security.py | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/documents/tests/test_permission_filtering_security.py diff --git a/src/documents/tests/test_permission_filtering_security.py b/src/documents/tests/test_permission_filtering_security.py new file mode 100644 index 000000000..b1704ece0 --- /dev/null +++ b/src/documents/tests/test_permission_filtering_security.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +import pytest +from django.contrib.auth.models import AnonymousUser +from django.contrib.auth.models import Group +from django.contrib.auth.models import User +from guardian.shortcuts import assign_perm + +from documents.permissions import permitted_document_ids +from documents.tests.factories import DocumentFactory + + +def assert_visible_document_ids(actual_ids, *, expected_visible, expected_hidden): + actual_ids = set(actual_ids) + for doc_id in expected_visible: + assert doc_id in actual_ids, ( + f"document {doc_id} should be visible but was hidden" + ) + for doc_id in expected_hidden: + assert doc_id not in actual_ids, ( + f"document {doc_id} leaked but should be hidden" + ) + + +@pytest.mark.django_db +class TestPermittedDocumentIdsSecurity: + def test_owner_sees_own_document(self): + user = User.objects.create_user(username="alice") + stranger = User.objects.create_user(username="mallory") + owned = DocumentFactory(owner=user) + strangers_doc = DocumentFactory(owner=stranger) + + visible = permitted_document_ids(user) + + assert_visible_document_ids( + visible, + expected_visible=[owned.pk], + expected_hidden=[strangers_doc.pk], + ) + + def test_unowned_document_visible_to_everyone(self): + user = User.objects.create_user(username="alice") + unowned = DocumentFactory(owner=None) + + assert_visible_document_ids( + permitted_document_ids(user), + expected_visible=[unowned.pk], + expected_hidden=[], + ) + + def test_explicit_user_permission_grants_visibility(self): + grantee = User.objects.create_user(username="alice") + stranger = User.objects.create_user(username="mallory") + owner = User.objects.create_user(username="owner") + shared = DocumentFactory(owner=owner) + not_shared = DocumentFactory(owner=owner) + assign_perm("view_document", grantee, shared) + + assert_visible_document_ids( + permitted_document_ids(grantee), + expected_visible=[shared.pk], + expected_hidden=[not_shared.pk], + ) + assert_visible_document_ids( + permitted_document_ids(stranger), + expected_visible=[], + expected_hidden=[shared.pk, not_shared.pk], + ) + + def test_explicit_group_permission_grants_visibility_to_members_only(self): + owner = User.objects.create_user(username="owner") + member = User.objects.create_user(username="member") + non_member = User.objects.create_user(username="non_member") + group = Group.objects.create(name="finance") + member.groups.add(group) + shared = DocumentFactory(owner=owner) + assign_perm("view_document", group, shared) + + assert_visible_document_ids( + permitted_document_ids(member), + expected_visible=[shared.pk], + expected_hidden=[], + ) + assert_visible_document_ids( + permitted_document_ids(non_member), + expected_visible=[], + expected_hidden=[shared.pk], + ) + + def test_soft_deleted_document_excluded_by_default(self): + owner = User.objects.create_user(username="owner") + doc = DocumentFactory(owner=owner) + doc.delete() # soft delete + + assert_visible_document_ids( + permitted_document_ids(owner), + expected_visible=[], + expected_hidden=[doc.pk], + ) + + def test_superuser_sees_everything_including_no_perm_documents(self): + superuser = User.objects.create_superuser(username="root") + owner = User.objects.create_user(username="owner") + doc = DocumentFactory(owner=owner) + + assert_visible_document_ids( + permitted_document_ids(superuser), + expected_visible=[doc.pk], + expected_hidden=[], + ) + + def test_anonymous_user_sees_only_unowned_documents(self): + owner = User.objects.create_user(username="owner") + owned = DocumentFactory(owner=owner) + unowned = DocumentFactory(owner=None) + + assert_visible_document_ids( + permitted_document_ids(AnonymousUser()), + expected_visible=[unowned.pk], + expected_hidden=[owned.pk], + )