Source code for shopyo.api.perms

from dataclasses import dataclass, field
from typing import Any, Callable, Optional
from enum import Enum, auto


[docs] class Permission(Enum): """Granular permissions for the Shopyo authorization system. Members: USER_MANAGE: Create, update, delete users. USER_READ: View user profiles and lists. ROLE_MANAGE: Create, update, delete roles. SETTINGS_MANAGE: Modify application settings. CONTENT_MANAGE: Create, update, delete content. CONTENT_PUBLISH: Publish or approve content. DASHBOARD_VIEW: Access the admin dashboard. ADMIN_PANEL_ACCESS: Enter the admin panel at all. """ USER_MANAGE = auto() USER_READ = auto() ROLE_MANAGE = auto() SETTINGS_MANAGE = auto() CONTENT_MANAGE = auto() CONTENT_PUBLISH = auto() DASHBOARD_VIEW = auto() ADMIN_PANEL_ACCESS = auto()
[docs] @dataclass class Policy: """A named policy with a callable check. Attributes: name: Unique policy identifier (e.g. ``"perm.CONTENT_PUBLISH"``). check: Callable ``(user, resource=None) -> bool``. """ name: str check: Callable[..., bool]
[docs] class PolicyEngine: """Central authorization engine combining role-based and policy-based access. Usage:: engine = PolicyEngine() engine.grant("admin", Permission.ADMIN_PANEL_ACCESS) engine.define("perm.CONTENT_PUBLISH", lambda u, r: u.id == r.owner_id) # Decorator on views @engine.require(Permission.ADMIN_PANEL_ACCESS) def dashboard(): ... # Programmatic check engine.has_permission(current_user, Permission.USER_MANAGE) """ def __init__(self): self._policies: dict[str, Policy] = {} self._role_permissions: dict[str, set[Permission]] = {}
[docs] def define(self, name: str, check: Callable[..., bool]): """Register a custom policy check. Args: name: Policy name (convention: ``"perm.<PERMISSION_NAME>"``). check: Callable ``(user, resource=None) -> bool``. """ self._policies[name] = Policy(name=name, check=check)
[docs] def grant(self, role_name: str, *permissions: Permission): """Assign one or more permissions to a role. Args: role_name: Name of the role (e.g. ``"admin"``, ``"editor"``). permissions: One or more ``Permission`` enum members. """ self._role_permissions.setdefault(role_name, set()).update(permissions)
[docs] def has_permission( self, user, permission: Permission, resource: Any = None, ) -> bool: """Check whether a user holds a given permission. Evaluation order: 1. Admin users bypass all checks (transitional). 2. Any role the user belongs to is checked for the permission. 3. If a custom policy ``perm.<PERMISSION_NAME>`` exists, it is invoked with ``(user, resource)``. Args: user: Flask-Login ``User`` instance (or proxy). permission: The ``Permission`` enum member to check. resource: Optional resource object for resource-aware policies. Returns: ``True`` if the user has the permission, ``False`` otherwise. """ if user.is_admin: return True for role in getattr(user, "roles", []): if permission in self._role_permissions.get(role.name, set()): return True policy_name = f"perm.{permission.name}" if policy_name in self._policies: return self._policies[policy_name].check(user, resource) return False
[docs] def require(self, permission: Permission, resource: Any = None): """Decorator factory that protects routes with a permission check. Returns 401 if the user is unauthenticated, 403 if the user lacks the required permission. Args: permission: The ``Permission`` required to access the route. resource: Optional resource passed to the policy check. Example:: @blueprint.route("/admin") @engine.require(Permission.ADMIN_PANEL_ACCESS) def admin_index(): return "Admin panel" """ from functools import wraps from flask import abort, g from flask_login import current_user def decorator(f): @wraps(f) def wrapper(*args, **kwargs): user = getattr(g, "current_user", None) or current_user if not user.is_authenticated: abort(401) if not self.has_permission(user, permission, resource): abort(403) return f(*args, **kwargs) return wrapper return decorator