Files
blog.pyaqa.ru/app/domain/roles.py
Sergey Vanyushkin 14adcaa3e6
All checks were successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/type Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
style: apply ruff formatting to source and test files
2026-05-02 12:05:14 +03:00

104 lines
2.7 KiB
Python

"""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