feat(N3): Backend respektiert X-Workspace-ID bei Listen — contacts/dms/mail/calendar (#367)
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:
Agent Zero
2026-09-01 10:27:23 +02:00
parent b40adfdd3a
commit 26506a5027
14 changed files with 1156 additions and 13 deletions
+17 -2
View File
@@ -16,7 +16,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
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
from app.plugins.builtins.dms.common import (
_parse_uuid,
)
@@ -35,8 +35,13 @@ async def list_folders(
parent_id: str | None = None,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
workspace_scope: dict | None = Depends(require_workspace_scope("dms")),
):
"""AC1: GET /api/v1/dms/folders → 200 + folder tree (recursive)."""
"""AC1: GET /api/v1/dms/folders → 200 + folder tree (recursive).
Phase N3: an active workspace scope (X-Workspace-ID) reduces the tree to
the folder subtree — pure AND-restriction, never a grant.
"""
tenant_id = uuid.UUID(current_user["tenant_id"])
# Fetch all non-deleted folders for tenant with visibility filter
@@ -52,6 +57,16 @@ async def list_folders(
result = await db.execute(query)
all_folders = result.scalars().all()
# Phase N3: reduce to the scope subtree (folder_ids dimension)
if workspace_scope:
from app.services.workspace_scope_service import 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)
allowed = subtree or set()
all_folders = [f for f in all_folders if f.id in allowed]
# Build lookup map
folder_map: dict[uuid.UUID, dict] = {}
for f in all_folders: