""" FastAPI decorators for permission checks. They can be used in API endpoints to enforce user permissions. """ from functools import wraps from typing import Callable from fastapi import HTTPException, Request, status from . import get_user_permissions from .enums import PermissionLevel, PermissionScope from .main import get_scope_level_priorities def scopes_required( required_scopes: dict[PermissionScope, PermissionLevel], ) -> Callable: """ Decorator to enforce that the user has the required permission scopes (OR logic). Args: required_scopes: A dictionary mapping scope names to required values. This works in OR logic, meaning if the user has any of the required scopes with the specified values, they will be allowed access. To use AND logic, you can add multiple decorators with different scopes. Returns: Callable: A decorator for FastAPI endpoints that checks user permissions. Raises: HTTPException: If the user does not have the required scope or value. """ def decorator(endpoint): @wraps(endpoint) async def wrapper(*args, request: Request, **kwargs): user_email = request.headers.get("X-User-Email") if user_email is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User email not provided in request headers", ) user_perms = get_user_permissions(user_email) for scope, level in required_scopes.items(): user_value_for_scope = user_perms.scopes.get( scope, PermissionLevel.NONE ) if user_value_for_scope == PermissionLevel.NONE: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"Missing scope: {scope}", ) scope_levels = get_scope_level_priorities() required_level = scope_levels[level] user_level = scope_levels[user_value_for_scope] if user_level < required_level: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"Insufficient scope level for {scope}: " f"required {level}, but got {user_value_for_scope}", ) else: # If any required scope is satisfied with equal or higher level, # proceed to the endpoint return await endpoint(*args, request=request, **kwargs) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User does not have any of the required scopes", ) return wrapper return decorator