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:
+25
-1
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user