feat(N3): Backend respektiert X-Workspace-ID bei Listen — contacts/dms/mail/calendar (#367)
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
- Core-Resolver resolve_workspace_scope(): Zuweisungs-Check, leere Werte fallen weg; Exemptions System-Admin + workspaces:configure_modules (Editor-Deadlock) - require_workspace_scope(module_key) FastAPI-Dependency (deps.py) - expand_folder_scope(): Ordner-Subtree (zyklensicher) für ContactFolder + DMS Folder; scope_uuid_set() fail-closed - contacts: folder_ids-Subtree + contact_types auf GET /contacts, List-Cache bei aktivem Scope deaktiviert (Cache-Leak-Gefahr) - dms: folder_ids-Subtree + file_types (semantische Matcher) auf /files, Baum-Reduktion auf /folders - mail: account_ids auf /mails, /threads, /accounts - calendar: calendar_ids auf /calendar/entries, /calendars - Frontend-Defaults: getModuleConfig() im workspaceStore, ContactsList default_saved_view_id, Calendar default_view - Tests: 21/21 neu (TDD rot→grün), Regression 81 passed, Checker 0, tsc clean, Vitest grün, Build OK
This commit is contained in:
@@ -21,7 +21,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.db import get_db
|
||||
from app.core.storage import LocalStorage, get_storage_backend
|
||||
from app.core.visibility import apply_visibility_filter, check_single_entity_access
|
||||
from app.deps import get_current_user, require_permission
|
||||
from app.deps import get_current_user, require_permission, require_workspace_scope
|
||||
|
||||
# BUG-018 God-Object-Split: Helper/Konstanten leben jetzt in common.py;
|
||||
# Re-Exports sichern Import- und Patch-Kompatibilitaet
|
||||
@@ -245,8 +245,13 @@ async def get_file(
|
||||
async def list_all_files(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
workspace_scope: dict | None = Depends(require_workspace_scope("dms")),
|
||||
):
|
||||
"""List all non-deleted files for the current tenant."""
|
||||
"""List all non-deleted files for the current tenant.
|
||||
|
||||
Phase N3: applies the active workspace scope (X-Workspace-ID) as a pure
|
||||
AND-restriction — folder subtree + file types. Never a grant.
|
||||
"""
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
user_id = uuid.UUID(current_user["user_id"])
|
||||
is_system_admin = current_user.get("role") == "admin"
|
||||
@@ -258,9 +263,32 @@ async def list_all_files(
|
||||
query = await apply_visibility_filter(
|
||||
db, query, "dms_file", DmsFile, user_id, tenant_id, is_system_admin
|
||||
)
|
||||
|
||||
# Phase N3: workspace scope filters (folder subtree + file types)
|
||||
if workspace_scope:
|
||||
from app.services.workspace_scope_service import (
|
||||
DMS_FILE_TYPE_MATCHERS,
|
||||
expand_folder_scope,
|
||||
)
|
||||
|
||||
scope_folder_ids = workspace_scope.get("folder_ids")
|
||||
if isinstance(scope_folder_ids, list) and scope_folder_ids:
|
||||
subtree = await expand_folder_scope(db, Folder, scope_folder_ids)
|
||||
query = query.where(DmsFile.folder_id.in_(subtree or set()))
|
||||
|
||||
result = await db.execute(query)
|
||||
files = result.scalars().all()
|
||||
|
||||
# file_types needs Python-side matching (semantic matchers, not SQL-LIKE)
|
||||
if workspace_scope:
|
||||
from app.services.workspace_scope_service import DMS_FILE_TYPE_MATCHERS
|
||||
|
||||
scope_file_types = workspace_scope.get("file_types")
|
||||
if isinstance(scope_file_types, list) and scope_file_types:
|
||||
matchers = [DMS_FILE_TYPE_MATCHERS[t] for t in scope_file_types if t in DMS_FILE_TYPE_MATCHERS]
|
||||
if matchers:
|
||||
files = [f for f in files if any(m(f.mime_type) for m in matchers)]
|
||||
|
||||
return [
|
||||
{
|
||||
"id": str(f.id),
|
||||
|
||||
Reference in New Issue
Block a user