from __future__ import annotations from typing import Optional, Set # Role hierarchy used across MCP server and FastAPI routes VALID_ROLES = ("viewer", "editor", "admin", "owner") ROLE_ORDER = {role: idx for idx, role in enumerate(VALID_ROLES)} # Permission matrix defining which roles can perform which enterprise actions PERMISSIONS: dict[str, Set[str]] = { "manage_rules": {"owner", "admin"}, "ingest_documents": {"owner", "admin", "editor"}, "delete_documents": {"owner", "admin"}, "view_analytics": {"viewer", "editor", "admin", "owner"}, # All roles can view analytics } # Mapping of MCP tool names to enterprise actions TOOL_PERMISSION_MAP: dict[str, str] = { "admin.addRule": "manage_rules", "admin.deleteRule": "manage_rules", "rag.ingest": "ingest_documents", "rag.delete": "delete_documents", } def normalize_role(raw_value: Optional[str]) -> str: """ Normalize an inbound role string. Defaults to 'viewer' when undefined or invalid. """ if not raw_value: return "viewer" value = raw_value.strip().lower() if value not in VALID_ROLES: return "viewer" return value def allowed_roles_for(action: str) -> Set[str]: """ Return the set of roles that can execute the given action. If the action is unknown, all roles are allowed. """ return PERMISSIONS.get(action, set(VALID_ROLES)) def role_allows(role: str, action: str) -> bool: """ Check whether the supplied role has permission for the action. Unknown actions default to allow-all to avoid accidental lockouts. """ allowed = allowed_roles_for(action) return role in allowed if allowed else True def describe_allowed_roles(action: str) -> str: """ Return a human-friendly description of which roles are allowed for an action. """ allowed = sorted(allowed_roles_for(action), key=lambda r: ROLE_ORDER.get(r, 0)) return ", ".join(allowed) def get_required_action_for_tool(tool_name: str) -> Optional[str]: """ Look up the enterprise action that applies to a tool name, if any. """ return TOOL_PERMISSION_MAP.get(tool_name)