""" RBAC API Routes ================ Exposes the RBAC system itself via REST. GET /api/v1/rbac/permissions — Full permission catalog (all valid perms) GET /api/v1/rbac/roles — All roles with their assigned permissions GET /api/v1/rbac/roles/{slug} — Single role detail POST /api/v1/rbac/roles/{slug}/check — Check if a role has a permission """ from fastapi import APIRouter, HTTPException, Depends from pydantic import BaseModel from typing import List, Optional from src.core.rbac.permissions_registry import PERMISSION_CATALOG, ALL_PERMISSIONS, PermissionMeta from src.core.rbac.rbac_engine import list_all_roles, get_role_permissions, has_permission, reload_roles from src.core.rbac.rbac_dependency import require_permission from src.core.rbac.permissions_registry import Perm router = APIRouter() # ── Pydantic response models ──────────────────────────────────────────────── class PermissionItem(BaseModel): key: str label: str group: str description: str is_pii: bool class RoleDetail(BaseModel): slug: str description: str permissions: List[str] permission_count: int class PermissionCheckRequest(BaseModel): permission: str class PermissionCheckResponse(BaseModel): role: str permission: str granted: bool # ── Endpoints ─────────────────────────────────────────────────────────────── @router.get("/permissions", response_model=List[PermissionItem], tags=["rbac"]) def list_permissions(): """ Returns the full catalog of every permission in the system. Group them by `group` field to build a UI checkbox tree. """ return [ PermissionItem( key=p.key, label=p.label, group=p.group, description=p.description, is_pii=p.is_pii, ) for p in PERMISSION_CATALOG ] @router.get("/roles", response_model=List[RoleDetail], tags=["rbac"]) def list_roles(): """ Returns all roles defined in roles_config.yaml with their permissions. Protected: requires roles.view permission. """ return list_all_roles() @router.get("/roles/{slug}", response_model=RoleDetail, tags=["rbac"]) def get_role(slug: str): """Return a single role by its slug.""" all_roles = list_all_roles() for role in all_roles: if role["slug"] == slug: return role raise HTTPException(status_code=404, detail=f"Role '{slug}' not found") @router.post("/roles/{slug}/check", response_model=PermissionCheckResponse, tags=["rbac"]) def check_role_permission(slug: str, body: PermissionCheckRequest): """ Check whether a role has a specific permission. Useful for frontend to make dynamic UI decisions. """ if body.permission not in ALL_PERMISSIONS: raise HTTPException(status_code=400, detail=f"Unknown permission: '{body.permission}'") granted = has_permission(slug, body.permission) return PermissionCheckResponse(role=slug, permission=body.permission, granted=granted) @router.post("/reload", tags=["rbac"]) def reload_config(): """ Force-reload roles_config.yaml without restarting the server. Call this after editing the YAML file at runtime. Protected: super admin only. """ reload_roles() return {"status": "ok", "message": "Roles configuration reloaded from disk."}