"""Role-based access control definitions.""" from collections.abc import Callable from enum import Enum from functools import wraps from typing import Any from app.domain.exceptions import ForbiddenException class Role(str, Enum): """User roles in the system.""" ADMIN = "admin" USER = "user" GUEST = "guest" class Permission: """Permission definitions.""" # Post permissions POST_CREATE = "post:create" POST_READ = "post:read" POST_READ_UNPUBLISHED = "post:read_unpublished" POST_UPDATE = "post:update" POST_DELETE = "post:delete" POST_PUBLISH = "post:publish" # Role-based permission mapping ROLE_PERMISSIONS: dict[Role, list[str]] = { Role.ADMIN: [ Permission.POST_CREATE, Permission.POST_READ, Permission.POST_READ_UNPUBLISHED, Permission.POST_UPDATE, Permission.POST_DELETE, Permission.POST_PUBLISH, ], Role.USER: [ Permission.POST_CREATE, Permission.POST_READ, Permission.POST_UPDATE, Permission.POST_DELETE, Permission.POST_PUBLISH, ], Role.GUEST: [ Permission.POST_READ, ], } def has_permission(role: Role, permission: str) -> bool: """Check if role has specific permission.""" return permission in ROLE_PERMISSIONS.get(role, []) def require_permission( permission: str, ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: """Decorator to require specific permission.""" def decorator(func: Callable[..., Any]) -> Callable[..., Any]: @wraps(func) async def wrapper(*args: Any, **kwargs: Any) -> Any: # Get token_info from kwargs token_info = kwargs.get("token_info") if not token_info: raise ForbiddenException("Authentication required") # Determine role from token or default to guest roles = getattr(token_info, "roles", []) if Role.ADMIN.value in roles: role = Role.ADMIN elif Role.USER.value in roles: role = Role.USER else: role = Role.GUEST if not has_permission(role, permission): raise ForbiddenException( f"Permission '{permission}' required for role '{role.value}'" ) return await func(*args, **kwargs) return wrapper return decorator def get_effective_role(roles: list[str]) -> Role: """Determine effective role from list of roles. Priority: admin > user > guest """ if Role.ADMIN.value in roles: return Role.ADMIN elif Role.USER.value in roles: return Role.USER else: return Role.GUEST