mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-03 14:50:31 +00:00
200 lines
6.5 KiB
Python
200 lines
6.5 KiB
Python
"""Permission filtering must hold against the real indexed document shape.
|
|
|
|
Only three of the index's unsigned ``*_id`` columns are load-bearing:
|
|
``owner_id``, ``viewer_id`` and ``viewer_group_id``, all read by
|
|
build_permission_filter. The rest (correspondent/document_type/storage_path/tag
|
|
ids) were written on every document and read by nothing, and were dropped.
|
|
|
|
These tests index real Documents through the backend's own document builder and
|
|
assert result-level visibility per user, so a mistake about which columns are
|
|
load-bearing shows up as documents leaking across users rather than as a passing
|
|
unit test over a hand-built index.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
from django.contrib.auth.models import Group
|
|
from django.contrib.auth.models import User
|
|
|
|
from documents.models import Correspondent
|
|
from documents.models import Document
|
|
from documents.models import DocumentType
|
|
from documents.models import StoragePath
|
|
from documents.models import Tag
|
|
from paperless_testing.factories import UserFactory
|
|
from paperless_testing.permissions import grant_object
|
|
|
|
if TYPE_CHECKING:
|
|
from documents.search._backend import TantivyBackend
|
|
|
|
pytestmark = [pytest.mark.search, pytest.mark.django_db]
|
|
|
|
|
|
@pytest.fixture
|
|
def owner() -> User:
|
|
return UserFactory(username="owner")
|
|
|
|
|
|
@pytest.fixture
|
|
def stranger() -> User:
|
|
return UserFactory(username="stranger")
|
|
|
|
|
|
@pytest.fixture
|
|
def viewer() -> User:
|
|
return UserFactory(username="viewer")
|
|
|
|
|
|
@pytest.fixture
|
|
def group_member() -> User:
|
|
user = UserFactory(username="group_member")
|
|
user.groups.add(Group.objects.create(name="accounting"))
|
|
return user
|
|
|
|
|
|
class TestPermissionFilteringOnIndexedDocuments:
|
|
def test_unowned_document_is_visible_to_everyone(
|
|
self,
|
|
backend: TantivyBackend,
|
|
stranger: User,
|
|
) -> None:
|
|
"""
|
|
GIVEN:
|
|
- A document with no owner, indexed via the backend's real
|
|
document builder
|
|
WHEN:
|
|
- A stranger (no relation to the document) searches
|
|
THEN:
|
|
- The document is visible to them
|
|
"""
|
|
doc = Document.objects.create(
|
|
title="Public Invoice",
|
|
content="invoice total due",
|
|
checksum="perm-unowned",
|
|
)
|
|
backend.add_or_update(doc)
|
|
|
|
assert backend.search_ids("invoice", user=stranger) == [doc.pk]
|
|
|
|
def test_owned_document_is_visible_only_to_its_owner(
|
|
self,
|
|
backend: TantivyBackend,
|
|
owner: User,
|
|
stranger: User,
|
|
) -> None:
|
|
"""
|
|
GIVEN:
|
|
- A document owned by one user, indexed via the backend's
|
|
real document builder
|
|
WHEN:
|
|
- The owner and an unrelated stranger each search
|
|
THEN:
|
|
- The owner sees the document; the stranger does not
|
|
"""
|
|
doc = Document.objects.create(
|
|
title="Private Invoice",
|
|
content="invoice total due",
|
|
checksum="perm-owned",
|
|
owner=owner,
|
|
)
|
|
backend.add_or_update(doc)
|
|
|
|
assert backend.search_ids("invoice", user=owner) == [doc.pk]
|
|
assert backend.search_ids("invoice", user=stranger) == []
|
|
|
|
def test_explicitly_shared_document_is_visible_to_the_viewer(
|
|
self,
|
|
backend: TantivyBackend,
|
|
owner: User,
|
|
viewer: User,
|
|
stranger: User,
|
|
) -> None:
|
|
"""
|
|
GIVEN:
|
|
- A document owned by one user and explicitly shared with a
|
|
second user via guardian's view_document permission,
|
|
indexed via the backend's real document builder
|
|
WHEN:
|
|
- The shared viewer and an unrelated stranger each search
|
|
THEN:
|
|
- The viewer sees the document; the stranger does not
|
|
"""
|
|
doc = Document.objects.create(
|
|
title="Shared Invoice",
|
|
content="invoice total due",
|
|
checksum="perm-shared-user",
|
|
owner=owner,
|
|
)
|
|
grant_object(viewer, doc, "view_document")
|
|
backend.add_or_update(doc)
|
|
|
|
assert backend.search_ids("invoice", user=viewer) == [doc.pk]
|
|
assert backend.search_ids("invoice", user=stranger) == []
|
|
|
|
def test_group_shared_document_is_visible_to_group_members(
|
|
self,
|
|
backend: TantivyBackend,
|
|
owner: User,
|
|
group_member: User,
|
|
stranger: User,
|
|
) -> None:
|
|
"""
|
|
GIVEN:
|
|
- A document owned by one user and shared with a group via
|
|
guardian's view_document permission, indexed via the
|
|
backend's real document builder
|
|
WHEN:
|
|
- A member of that group and an unrelated stranger each
|
|
search
|
|
THEN:
|
|
- The group member sees the document; the stranger does not
|
|
"""
|
|
doc = Document.objects.create(
|
|
title="Group Invoice",
|
|
content="invoice total due",
|
|
checksum="perm-shared-group",
|
|
owner=owner,
|
|
)
|
|
grant_object(group_member.groups.first(), doc, "view_document")
|
|
backend.add_or_update(doc)
|
|
|
|
assert backend.search_ids("invoice", user=group_member) == [doc.pk]
|
|
assert backend.search_ids("invoice", user=stranger) == []
|
|
|
|
def test_metadata_does_not_widen_visibility(
|
|
self,
|
|
backend: TantivyBackend,
|
|
owner: User,
|
|
stranger: User,
|
|
) -> None:
|
|
"""
|
|
GIVEN:
|
|
- A document owned by one user and carrying
|
|
correspondent/document_type/storage_path/tag metadata,
|
|
indexed via the backend's real document builder
|
|
WHEN:
|
|
- The owner and an unrelated stranger each search
|
|
THEN:
|
|
- The owner sees the document; the stranger does not, since
|
|
the dropped, non-load-bearing metadata *_id columns must
|
|
not widen visibility beyond the owner_id/viewer_id/
|
|
viewer_group_id filter
|
|
"""
|
|
doc = Document.objects.create(
|
|
title="Tagged Invoice",
|
|
content="invoice total due",
|
|
checksum="perm-metadata",
|
|
owner=owner,
|
|
correspondent=Correspondent.objects.create(name="ACME"),
|
|
document_type=DocumentType.objects.create(name="Bill"),
|
|
storage_path=StoragePath.objects.create(name="Archive", path="archive/"),
|
|
)
|
|
doc.tags.add(Tag.objects.create(name="paid"))
|
|
backend.add_or_update(doc)
|
|
|
|
assert backend.search_ids("invoice", user=owner) == [doc.pk]
|
|
assert backend.search_ids("invoice", user=stranger) == []
|