fix: address PR #813 latest review (ACL-aware doc-type discovery, robustness)
- get_indexed_doc_types: add optional accessible_owners param and reuse
build_ownership_filter so cross-user doc-type discovery matches the real
search scope (was self-only / ACL-blind); docstring documents the self-only
default. Covered by test_get_indexed_doc_types_is_acl_aware.
- access_filter: build_ownership_filter now omits the owner_id branch entirely
for an empty owner set instead of relying on undocumented MatchAny(any=[])
semantics; updated the empty-list unit test accordingly.
- access_filter: make the uid_owner/owner share-owner extraction explicit
("absent, not empty") to avoid skipping on a falsy-but-present field.
- access_filter: add an operator note that pre-owner_id points need a re-index
to surface to share recipients (ACL search is a no-op for legacy data).
- verification/webdav: lock the file_accessible_by_id(scope="") contract with a
targeted multi-user test (owner + recipient True, non-recipient False).
- viz_routes: comment that verify-on-read eviction runs inline by design (no
lifespan task group available on the Starlette route).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
531228d407
commit
423d0a1758
@@ -5,9 +5,10 @@ from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
from qdrant_client.models import FieldCondition, Filter, MatchValue, ScoredPoint
|
||||
from qdrant_client.models import Filter, ScoredPoint
|
||||
|
||||
from nextcloud_mcp_server.config import get_settings
|
||||
from nextcloud_mcp_server.search.access_filter import build_ownership_filter
|
||||
from nextcloud_mcp_server.vector.placeholder import get_placeholder_filter
|
||||
from nextcloud_mcp_server.vector.qdrant_client import get_qdrant_client
|
||||
|
||||
@@ -75,14 +76,24 @@ class NextcloudClientProtocol(Protocol):
|
||||
...
|
||||
|
||||
|
||||
async def get_indexed_doc_types(user_id: str) -> set[str]:
|
||||
async def get_indexed_doc_types(
|
||||
user_id: str, accessible_owners: list[str] | None = None
|
||||
) -> set[str]:
|
||||
"""Query Qdrant to get actually-indexed document types for a user.
|
||||
|
||||
This enables search algorithms to check which document types are available
|
||||
before attempting to search/verify them, allowing graceful cross-app search.
|
||||
|
||||
Args:
|
||||
user_id: User ID to filter by
|
||||
user_id: User ID to filter by.
|
||||
accessible_owners: Owner UIDs the user may read (self + share senders),
|
||||
as computed by ``access_filter.list_accessible_owners``. When
|
||||
provided, doc-type discovery is ACL-aware and matches the same
|
||||
ownership scope as the actual search (so a share recipient discovers
|
||||
cross-user doc_types). When ``None`` (the default), discovery is
|
||||
**self-only** — a recipient won't see doc_types that exist only in
|
||||
another owner's shared content. Pass the expanded set for cross-user
|
||||
discovery.
|
||||
|
||||
Returns:
|
||||
Set of document type strings (e.g., {"note", "file", "calendar"})
|
||||
@@ -106,7 +117,9 @@ async def get_indexed_doc_types(user_id: str) -> set[str]:
|
||||
scroll_filter=Filter(
|
||||
must=[
|
||||
get_placeholder_filter(), # Exclude placeholders from doc_type discovery
|
||||
FieldCondition(key="user_id", match=MatchValue(value=user_id)),
|
||||
# ACL-aware ownership scope (owner_id IN owners OR legacy
|
||||
# user_id == user_id), matching the real search filter.
|
||||
build_ownership_filter(user_id, accessible_owners),
|
||||
]
|
||||
),
|
||||
limit=1000, # Sample size to discover types
|
||||
|
||||
Reference in New Issue
Block a user