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
+25 -1
View File
@@ -7,7 +7,7 @@ import uuid
from typing import Any
import redis.asyncio as aioredis
from fastapi import Depends, HTTPException, Request, status
from fastapi import Depends, Header, HTTPException, Request, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -382,6 +382,30 @@ async def get_current_user_id(
return uuid.UUID(current_user["user_id"])
def require_workspace_scope(module_key: str):
"""FastAPI dependency factory (Phase N3): resolve the active workspace
scope config for a module from the X-Workspace-ID header.
Returns the scope dict (e.g. ``{"folder_ids": [...]}``) or ``None``
when no restriction applies (no header, admin, unassigned, empty config).
Callers apply it as a pure AND-restriction — never a grant.
Usage:
scope: dict | None = Depends(require_workspace_scope("contacts"))
"""
async def _resolve(
db: AsyncSession = Depends(get_db),
current_user: dict[str, Any] = Depends(get_current_user),
x_workspace_id: str | None = Header(None, alias="X-Workspace-ID"),
) -> dict[str, Any] | None:
from app.services.workspace_scope_service import resolve_workspace_scope
return await resolve_workspace_scope(db, current_user, x_workspace_id, module_key)
return _resolve
def require_active_plugin(plugin_name: str):
"""FastAPI dependency factory: require that a plugin is active.