"""Role-based access control definitions. This module provides role and permission definitions for the application along with utility functions and decorators for permission checking. """ 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. Defines the available user roles with hierarchical permissions. ADMIN has full access, USER has standard access, GUEST has read-only. Attributes: ADMIN: Administrator with full system access. USER: Regular authenticated user. GUEST: Unauthenticated or limited access user. Example: >>> if role == Role.ADMIN: ... grant_full_access() """ ADMIN = "admin" USER = "user" GUEST = "guest" class Permission: """Permission definitions. Contains string constants for all available permissions in the system. Used for role-based access control checks. Example: >>> if has_permission(role, Permission.POST_CREATE): ... allow_post_creation() """ 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_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. Args: role: User role to check. permission: Permission string to verify. Returns: True if role has the permission, False otherwise. Example: >>> has_permission(Role.ADMIN, Permission.POST_DELETE) True """ return permission in ROLE_PERMISSIONS.get(role, []) def require_permission( permission: str, ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: """Decorator to require specific permission. Creates a decorator that checks if the user has the required permission before executing the decorated function. Args: permission: Permission string required for execution. Returns: Decorator function for permission checking. Raises: ForbiddenException: If user lacks the required permission. Example: >>> @require_permission(Permission.POST_CREATE) ... async def create_post(): ... ... """ def decorator(func: Callable[..., Any]) -> Callable[..., Any]: @wraps(func) async def wrapper(*args: Any, **kwargs: Any) -> Any: token_info = kwargs.get("token_info") if not token_info: raise ForbiddenException("Authentication required") 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. Evaluates multiple roles and returns the highest privilege role. Priority order: admin > user > guest. Args: roles: List of role strings from token. Returns: Highest privilege Role enum value. Example: >>> get_effective_role(["user", "admin"]) """ if Role.ADMIN.value in roles: return Role.ADMIN elif Role.USER.value in roles: return Role.USER else: return Role.GUEST