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 @@ import app.plugins.builtins.mail.services as mail_services
|
||||
from app.core.db import get_db
|
||||
from app.core.storage import get_storage_backend
|
||||
from app.core.visibility import apply_visibility_filter, check_single_entity_access
|
||||
from app.deps import require_permission
|
||||
from app.deps import require_permission, require_workspace_scope
|
||||
from app.plugins.builtins.mail.models import (
|
||||
ContactPgpKey,
|
||||
Mail,
|
||||
@@ -214,7 +214,8 @@ async def _check_delegate_access(
|
||||
|
||||
@router.get("/accounts")
|
||||
async def list_accounts(
|
||||
db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("mail:read"))
|
||||
db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("mail:read")),
|
||||
workspace_scope: dict | None = Depends(require_workspace_scope("mail")),
|
||||
):
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
user_id = uuid.UUID(current_user["user_id"])
|
||||
@@ -223,6 +224,13 @@ async def list_accounts(
|
||||
query = await apply_visibility_filter(
|
||||
db, query, "mail_account", MailAccount, user_id, tenant_id, is_system_admin
|
||||
)
|
||||
# Phase N3: workspace scope (X-Workspace-ID) — account picker restriction.
|
||||
if workspace_scope:
|
||||
from app.services.workspace_scope_service import scope_uuid_set
|
||||
|
||||
account_scope = scope_uuid_set(workspace_scope.get("account_ids"))
|
||||
if account_scope is not None:
|
||||
query = query.where(MailAccount.id.in_(account_scope))
|
||||
accounts = (await db.execute(query)).scalars().all()
|
||||
return [account_to_response(a) for a in accounts]
|
||||
|
||||
@@ -878,12 +886,20 @@ async def list_threads(
|
||||
account_id: str | None = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(require_permission("mail:read")),
|
||||
workspace_scope: dict | None = Depends(require_workspace_scope("mail")),
|
||||
):
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
stmt = select(Mail).where(Mail.tenant_id == tenant_id)
|
||||
if account_id:
|
||||
a_id = _parse_uuid(account_id, "account_id")
|
||||
stmt = stmt.where(Mail.account_id == a_id)
|
||||
# Phase N3: workspace scope (X-Workspace-ID) — account subsets, pure AND.
|
||||
if workspace_scope:
|
||||
from app.services.workspace_scope_service import scope_uuid_set
|
||||
|
||||
account_scope = scope_uuid_set(workspace_scope.get("account_ids"))
|
||||
if account_scope is not None:
|
||||
stmt = stmt.where(Mail.account_id.in_(account_scope))
|
||||
mails = (await db.execute(stmt.order_by(desc(Mail.received_at)))).scalars().all()
|
||||
threads: dict[str, dict] = {}
|
||||
for mail in mails:
|
||||
@@ -1887,6 +1903,7 @@ async def list_mails(
|
||||
sort_order: str = Query("desc", pattern="^(asc|desc)$"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(require_permission("mail:read")),
|
||||
workspace_scope: dict | None = Depends(require_workspace_scope("mail")),
|
||||
):
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
stmt = select(Mail).where(Mail.tenant_id == tenant_id)
|
||||
@@ -1896,6 +1913,13 @@ async def list_mails(
|
||||
if account_id:
|
||||
a_id = _parse_uuid(account_id, "account_id")
|
||||
stmt = stmt.where(Mail.account_id == a_id)
|
||||
# Phase N3: workspace scope (X-Workspace-ID) — account subsets, pure AND.
|
||||
if workspace_scope:
|
||||
from app.services.workspace_scope_service import scope_uuid_set
|
||||
|
||||
account_scope = scope_uuid_set(workspace_scope.get("account_ids"))
|
||||
if account_scope is not None:
|
||||
stmt = stmt.where(Mail.account_id.in_(account_scope))
|
||||
total = (await db.execute(select(func.count()).select_from(stmt.subquery()))).scalar()
|
||||
# Dynamic sorting
|
||||
sort_columns = {
|
||||
|
||||
Reference in New Issue
Block a user