Ir para o conteúdo

Permissions

Access control for ViewSets and actions.

Permission Check Flow

flowchart TB
    REQ[Request] --> HP{has_permission?}
    HP -->|No| DENY1[403 Forbidden]
    HP -->|Yes| DETAIL{Detail View?}
    
    DETAIL -->|No| ALLOW[✓ Allow]
    DETAIL -->|Yes| HOP{has_object_permission?}
    
    HOP -->|No| DENY2[403 Forbidden]
    HOP -->|Yes| ALLOW
    
    style HP fill:#fff3e0
    style HOP fill:#fff3e0
    style ALLOW fill:#c8e6c9
    style DENY1 fill:#ffcdd2
    style DENY2 fill:#ffcdd2

Permission Hierarchy

flowchart LR
    subgraph "Permission Classes"
        AA[AllowAny]
        IA[IsAuthenticated]
        IAU[IsAdminUser]
        ISU[IsSuperUser]
    end
    
    AA --> |"No auth"| PUBLIC[Public Access]
    IA --> |"Logged in"| USER[User Access]
    IAU --> |"is_staff"| STAFF[Staff Access]
    ISU --> |"is_superuser"| SUPER[Full Access]
    
    style AA fill:#e8f5e9
    style IA fill:#e3f2fd
    style IAU fill:#fff3e0
    style ISU fill:#ffcdd2

Built-in Permissions

Class Description
AllowAny No authentication required
IsAuthenticated User must be logged in
IsAdminUser User must have is_staff=True
IsSuperUser User must have is_superuser=True

Usage in ViewSets

Default Permission

from strider import ModelViewSet
from strider.permissions import IsAuthenticated

class PostViewSet(ModelViewSet):
    model = Post
    permission_classes = [IsAuthenticated]

Per-Action Permissions

from strider.permissions import AllowAny, IsAuthenticated, IsAdminUser

class PostViewSet(ModelViewSet):
    model = Post
    
    permission_classes_by_action = {
        "list": [AllowAny],
        "retrieve": [AllowAny],
        "create": [IsAuthenticated],
        "update": [IsAuthenticated],
        "destroy": [IsAdminUser],
    }

Action Decorator

from strider import action
from strider.permissions import IsAdminUser

class PostViewSet(ModelViewSet):
    model = Post
    
    @action(detail=True, methods=["POST"], permission_classes=[IsAdminUser])
    async def publish(self, request, db, id: int):
        ...

Custom Permissions

Basic Permission

from strider.permissions import Permission

class IsOwner(Permission):
    """Only allow owners to access."""
    
    async def has_permission(self, request, view) -> bool:
        return request.user is not None
    
    async def has_object_permission(self, request, view, obj) -> bool:
        return obj.author_id == request.user.id

Usage

class PostViewSet(ModelViewSet):
    model = Post
    permission_classes = [IsAuthenticated, IsOwner]

Role-Based Permissions

HasRole

from strider.permissions import HasRole

class PostViewSet(ModelViewSet):
    model = Post
    permission_classes = [HasRole("editor")]

Multiple Roles

class PostViewSet(ModelViewSet):
    model = Post
    permission_classes = [HasRole("editor", "admin")]  # OR logic

Group-Based Permissions

from strider.permissions import Permission

class InGroup(Permission):
    def __init__(self, *groups):
        self.groups = groups
    
    async def has_permission(self, request, view) -> bool:
        if not request.user:
            return False
        user_groups = {g.name for g in request.user.groups}
        return bool(user_groups & set(self.groups))

# Usage
class PostViewSet(ModelViewSet):
    permission_classes = [InGroup("editors", "moderators")]

Combining Permissions

Permissions are combined with AND logic:

# User must be authenticated AND owner
permission_classes = [IsAuthenticated, IsOwner]

For OR logic, create a custom permission:

class IsOwnerOrAdmin(Permission):
    async def has_object_permission(self, request, view, obj) -> bool:
        if request.user.is_staff:
            return True
        return obj.author_id == request.user.id

Model-Level Permissions

class HasModelPermission(Permission):
    def __init__(self, permission: str):
        self.permission = permission
    
    async def has_permission(self, request, view) -> bool:
        if not request.user:
            return False
        return await request.user.has_perm(self.permission)

# Usage
class PostViewSet(ModelViewSet):
    model = Post
    
    permission_classes_by_action = {
        "create": [HasModelPermission("posts.add")],
        "update": [HasModelPermission("posts.change")],
        "destroy": [HasModelPermission("posts.delete")],
    }

Permission Check Flow

  1. has_permission() — Called for all requests
  2. has_object_permission() — Called for detail views (retrieve, update, destroy)
class IsOwner(Permission):
    async def has_permission(self, request, view) -> bool:
        # Called first, for all actions
        return request.user is not None
    
    async def has_object_permission(self, request, view, obj) -> bool:
        # Called second, only for detail actions
        return obj.author_id == request.user.id

Permission Denied Response

{
  "detail": "Permission denied",
  "code": "permission_denied"
}

Status: 403 Forbidden

Collect Permissions

List all permissions in the system:

core collectpermissions

Examples

Public Read, Auth Write

class PostViewSet(ModelViewSet):
    model = Post
    
    permission_classes_by_action = {
        "list": [AllowAny],
        "retrieve": [AllowAny],
        "create": [IsAuthenticated],
        "update": [IsAuthenticated],
        "destroy": [IsAuthenticated],
    }

Owner Only

class IsOwner(Permission):
    async def has_object_permission(self, request, view, obj) -> bool:
        return obj.user_id == request.user.id

class ProfileViewSet(ModelViewSet):
    model = Profile
    permission_classes = [IsAuthenticated, IsOwner]

Admin or Owner

class IsAdminOrOwner(Permission):
    async def has_object_permission(self, request, view, obj) -> bool:
        if request.user.is_staff:
            return True
        return obj.author_id == request.user.id

class PostViewSet(ModelViewSet):
    model = Post
    permission_classes = [IsAuthenticated, IsAdminOrOwner]

Next