Перейти к содержанию

Permissions

Warning

The current page still doesn't have a translation for this language.

But you can help translating it: Contributing.

Control who can access what in your Ravyn application.

Permissions are authorization rules evaluated before the handler runs. They are ideal for role checks, access policies, and endpoint-level protection.

What You'll Learn

  • Ravyn native permission classes.
  • Where to apply permissions (app, include, gateway, handler).
  • How to create custom permissions.
  • How to combine permissions with logical operators.

Quick Start

from ravyn import Ravyn, Gateway, get
from ravyn.permissions import IsAuthenticated

@get()
async def profile() -> dict:
    return {"ok": True}

app = Ravyn(
    routes=[Gateway("/profile", handler=profile)],
    permissions=[IsAuthenticated],
)

Built-in Ravyn permissions

Ravyn provides these built-ins:

  • AllowAny
  • DenyAll
  • IsAuthenticated
  • IsAdminUser
  • IsAuthenticatedOrReadOnly

Import from:

from ravyn.permissions import (
    AllowAny,
    DenyAll,
    IsAuthenticated,
    IsAdminUser,
    IsAuthenticatedOrReadOnly,
)

Creating a custom permission

Subclass BasePermission and implement has_permission(request, controller).

from ravyn.permissions import BasePermission
from ravyn.requests import Request


class HasAPIKey(BasePermission):
    async def has_permission(self, request: Request, controller) -> bool:
        return request.headers.get("x-api-key") == "secret"

You can also implement has_permission as a sync method.

Applying permissions

Permissions can be declared at multiple levels.

  • Application level: applies globally.
  • Include level: applies to grouped routes.
  • Gateway/handler level: applies to a specific endpoint.
from ravyn import Ravyn, Include, Gateway, get
from ravyn.permissions import IsAuthenticated

@get()
async def me() -> dict:
    return {"user": "ok"}

app = Ravyn(
    routes=[
        Include(
            "/api",
            routes=[Gateway("/me", handler=me)],
            permissions=[IsAuthenticated],
        )
    ]
)

Authorization flow

request enters app
  -> global permissions (app/include)
  -> route-level permissions
  -> handler execution (if all checks pass)

Use higher levels (app/include) for broad policy and route-level permissions for endpoint-specific rules.

Permission composition (AND, OR, NOT, XOR, NOR)

Ravyn supports composing permissions using operators in BasePermission.

  • A & B -> both must pass
  • A | B -> either one passes
  • ~A -> inverse of A
  • A ^ B -> exactly one passes
  • A - B -> NOR (not (A or B))
from ravyn.permissions import BasePermission, IsAuthenticated


class HasVerifiedEmail(BasePermission):
    async def has_permission(self, request, controller) -> bool:
        user = getattr(request, "user", None)
        return bool(user and getattr(user, "is_verified", False))


Combined = IsAuthenticated & HasVerifiedEmail

Then use Combined anywhere permissions are accepted.

Composition example on a route

from ravyn import get
from ravyn.permissions import BasePermission, IsAuthenticated


class HasStaffFlag(BasePermission):
    async def has_permission(self, request, controller) -> bool:
        user = getattr(request, "user", None)
        return bool(user and getattr(user, "is_staff", False))


@get("/admin", permissions=[IsAuthenticated & HasStaffFlag])
def admin_dashboard() -> dict:
    return {"ok": True}

Ravyn vs Lilya permissions

Ravyn supports both Ravyn-native and Lilya-style permissions.

Important: do not mix both styles on the same Gateway definition.

If you use Ravyn-native permissions, stay consistent in that route tree.

Practical Permission Examples

This section covers real-world permission patterns commonly used in production applications.

Role-Based Permissions

Role-based permissions check if a user has a specific role (e.g., admin, editor, viewer). This is ideal for applications with hierarchical access control.

from ravyn import Ravyn, get
from ravyn.permissions import BasePermission
from ravyn.requests import Request


class IsEditor(BasePermission):
    """
    Grants access only to users with the 'editor' role.
    """

    async def has_permission(self, request: Request, controller) -> bool:
        user = getattr(request, "user", None)
        if not user:
            return False
        # Assuming user has a 'role' attribute
        return getattr(user, "role", None) == "editor"


class IsAdmin(BasePermission):
    """
    Grants access only to users with the 'admin' role.
    """

    async def has_permission(self, request: Request, controller) -> bool:
        user = getattr(request, "user", None)
        if not user:
            return False
        return getattr(user, "role", None) == "admin"


@get("/api/admin/settings", permissions=[IsAdmin])
def admin_settings() -> dict:
    return {"settings": "admin-only"}


@get("/api/editor/posts", permissions=[IsEditor])
def editor_posts() -> dict:
    return {"posts": "editor-access"}


# You can also combine roles for "admin OR editor" access
@get("/api/content", permissions=[IsAdmin | IsEditor])
def content_management() -> dict:
    return {"access": "admin or editor"}

Resource-Based Permissions

Resource-based permissions check if a user has access to a specific resource (e.g., ownership, team membership). This is ideal for multi-tenant applications.

from ravyn import Ravyn, get, post
from ravyn.permissions import BasePermission, IsAuthenticated
from ravyn.requests import Request


class IsOwner(BasePermission):
    """
    Grants access only if the user owns the resource.
    Checks if the user_id from the URL matches the authenticated user's ID.
    """

    async def has_permission(self, request: Request, controller) -> bool:
        user = getattr(request, "user", None)
        if not user:
            return False

        # Extract user_id from path parameters
        resource_user_id = request.path_params.get("user_id")
        if not resource_user_id:
            return False

        # Check if the authenticated user owns the resource
        return str(getattr(user, "id", None)) == str(resource_user_id)


class IsTeamMember(BasePermission):
    """
    Grants access only if the user is a member of the team.
    """

    async def has_permission(self, request: Request, controller) -> bool:
        user = getattr(request, "user", None)
        if not user:
            return False

        # Extract team_id from path parameters
        team_id = request.path_params.get("team_id")
        if not team_id:
            return False

        # Assuming user has a 'teams' list attribute
        user_teams = getattr(user, "teams", [])
        return int(team_id) in user_teams


# Only the user can access their own profile
@get("/users/{user_id}/profile", permissions=[IsAuthenticated & IsOwner])
def get_user_profile(user_id: int) -> dict:
    return {"user_id": user_id, "profile": "private data"}


# Only the user can update their own settings
@post("/users/{user_id}/settings", permissions=[IsAuthenticated & IsOwner])
def update_user_settings(user_id: int, settings: dict) -> dict:
    return {"user_id": user_id, "updated": True}


# Only team members can access team resources
@get("/teams/{team_id}/projects", permissions=[IsAuthenticated & IsTeamMember])
def get_team_projects(team_id: int) -> dict:
    return {"team_id": team_id, "projects": []}

Combining Permissions for Complex Rules

You can combine multiple permissions using logical operators for more complex access control scenarios:

from ravyn import get
from ravyn.permissions import BasePermission, IsAuthenticated


class IsPremiumUser(BasePermission):
    """
    Grants access only to premium/paid users.
    """

    async def has_permission(self, request, controller) -> bool:
        user = getattr(request, "user", None)
        if not user:
            return False
        return getattr(user, "is_premium", False)


class HasFeatureAccess(BasePermission):
    """
    Grants access based on feature flag.
    """

    def __init__(self, feature_name: str):
        self.feature_name = feature_name

    async def has_permission(self, request, controller) -> bool:
        user = getattr(request, "user", None)
        if not user:
            return False
        enabled_features = getattr(user, "enabled_features", [])
        return self.feature_name in enabled_features


# Premium feature: requires authentication AND premium status
@get("/api/premium/analytics", permissions=[IsAuthenticated & IsPremiumUser])
def premium_analytics() -> dict:
    return {"analytics": "premium-only"}


# Admin OR premium users can access advanced features
@get("/api/advanced/reports", permissions=[IsAdmin | IsPremiumUser])
def advanced_reports() -> dict:
    return {"reports": "advanced"}

Best practices

  • Keep permissions focused on authorization only.
  • Reuse permission classes instead of inline checks in handlers.
  • Compose small permission classes for complex access rules.
  • Prefer explicit denial messages in your exception handlers when needed.
  • Pair permissions with Security dependencies when credentials and authorization rules are both required.