Files
gh-christianlouis-dmarq/backend/app/services/workspace_access.py
T
2026-05-23 19:38:27 +02:00

98 lines
2.9 KiB
Python

"""Workspace role and permission foundations."""
from __future__ import annotations
from typing import Dict, List, Set
from fastapi import HTTPException, status
ROLE_WORKSPACE_OWNER = "workspace_owner"
ROLE_DOMAIN_ADMIN = "domain_admin"
ROLE_OPERATOR = "operator"
ROLE_ANALYST = "analyst"
ROLE_AUDITOR = "auditor"
PERMISSION_WORKSPACE_ADMIN = "workspace:admin"
PERMISSION_DOMAINS_WRITE = "domains:write"
PERMISSION_MAIL_SOURCES_WRITE = "mail_sources:write"
PERMISSION_NOTIFICATIONS_WRITE = "notifications:write"
PERMISSION_INTEGRATIONS_WRITE = "integrations:write"
PERMISSION_AUDIT_READ = "audit:read"
PERMISSION_REPORTS_READ = "reports:read"
ROLE_PERMISSIONS: Dict[str, Set[str]] = {
ROLE_WORKSPACE_OWNER: {
PERMISSION_WORKSPACE_ADMIN,
PERMISSION_DOMAINS_WRITE,
PERMISSION_MAIL_SOURCES_WRITE,
PERMISSION_NOTIFICATIONS_WRITE,
PERMISSION_INTEGRATIONS_WRITE,
PERMISSION_AUDIT_READ,
PERMISSION_REPORTS_READ,
},
ROLE_DOMAIN_ADMIN: {
PERMISSION_DOMAINS_WRITE,
PERMISSION_MAIL_SOURCES_WRITE,
PERMISSION_NOTIFICATIONS_WRITE,
PERMISSION_AUDIT_READ,
PERMISSION_REPORTS_READ,
},
ROLE_OPERATOR: {
PERMISSION_MAIL_SOURCES_WRITE,
PERMISSION_NOTIFICATIONS_WRITE,
PERMISSION_AUDIT_READ,
PERMISSION_REPORTS_READ,
},
ROLE_ANALYST: {
PERMISSION_REPORTS_READ,
},
ROLE_AUDITOR: {
PERMISSION_AUDIT_READ,
PERMISSION_REPORTS_READ,
},
}
def permissions_for_role(role: str) -> Set[str]:
"""Return normalized permissions for a workspace role."""
return set(ROLE_PERMISSIONS.get((role or "").strip().lower(), set()))
def role_allows(role: str, permission: str) -> bool:
"""Return True when a role grants a permission."""
return permission in permissions_for_role(role)
def list_workspace_roles() -> List[dict]:
"""Return API-safe role definitions for operator documentation and UI use."""
return [
{
"role": role,
"permissions": sorted(permissions),
}
for role, permissions in sorted(ROLE_PERMISSIONS.items())
]
def role_for_auth_context(auth_context: dict) -> str:
"""Map current admin auth into an initial workspace role.
Logto users and static admin credentials keep owner-level access until
membership assignment and enforcement screens are added.
"""
auth_type = (auth_context or {}).get("auth_type")
if auth_type in {"session", "bearer", "jwt", "api_key", "disabled"}:
return ROLE_WORKSPACE_OWNER
return ROLE_AUDITOR
def require_workspace_permission(auth_context: dict, permission: str) -> None:
"""Raise HTTP 403 when the current role does not grant a permission."""
role = role_for_auth_context(auth_context)
if role_allows(role, permission):
return
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Workspace permission required: {permission}",
)