API Permissions: FastAPI Mason Security System¶
FastAPI Mason provides a robust permission system that allows you to control access to your API endpoints. The system is inspired by Django REST Framework and provides both view-level and object-level permissions, ensuring your FastAPI applications are secure and properly protected.
Overview¶
The permission system consists of:
- Permission Classes - Define access rules
- Permission Checking - Automatic verification on each request
- Custom Permissions - Create your own access logic
- Object-Level Permissions - Fine-grained control per object
Basic Permission Usage¶
Set permissions on your ViewSet:
from fastapi_mason.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from fastapi_mason.viewsets import ModelViewSet
@viewset(router)
class CompanyViewSet(ModelViewSet[Company]):
model = Company
read_schema = CompanyReadSchema
create_schema = CompanyCreateSchema
# Apply permissions to all actions
permission_classes = [IsAuthenticatedOrReadOnly]
Built-in Permission Classes¶
IsAuthenticated¶
Allows access only to authenticated users:
from fastapi_mason.permissions import IsAuthenticated
@viewset(router)
class CompanyViewSet(ModelViewSet[Company]):
permission_classes = [IsAuthenticated]
# All endpoints require authentication
IsAuthenticatedOrReadOnly¶
Allows read access to everyone, write access only to authenticated users:
from fastapi_mason.permissions import IsAuthenticatedOrReadOnly
@viewset(router)
class CompanyViewSet(ModelViewSet[Company]):
permission_classes = [IsAuthenticatedOrReadOnly]
# GET requests: anyone can access
# POST/PUT/DELETE: only authenticated users
DenyAll¶
Denies all access (useful for disabled endpoints):
from fastapi_mason.permissions import DenyAll
@viewset(router)
class CompanyViewSet(ModelViewSet[Company]):
permission_classes = [DenyAll]
# All requests will be denied with 403
Conditional Permissions¶
Apply different permissions based on the action:
@viewset(router)
class CompanyViewSet(ModelViewSet[Company]):
permission_classes = [IsAuthenticatedOrReadOnly] # Default permissions
def get_permissions(self):
"""Customize permissions per action"""
if self.action in ('stats', 'public_info'):
return [] # No permissions required
if self.action == 'sensitive_data':
return [IsAuthenticated()] # Require authentication
if self.action in ('create', 'update', 'destroy'):
return [IsOwnerOrAdmin()] # Custom permission
return super().get_permissions() # Use default or return []
Custom Permission Classes¶
Create your own permission classes by inheriting from BasePermission
:
IsOwner Permission¶
from fastapi_mason.permissions import BasePermission
from fastapi import HTTPException
class IsOwner(BasePermission):
"""Allow access only to object owners"""
async def has_object_permission(self, request, view, obj):
"""Check if user owns the object"""
if not view.user:
return False
# Assuming obj has an 'owner' field
return hasattr(obj, 'owner') and obj.owner == view.user.id
@viewset(router)
class CompanyViewSet(ModelViewSet[Company]):
permission_classes = [IsAuthenticated, IsOwner]
Role-Based Permission¶
class HasRole(BasePermission):
"""Check if user has specific role"""
def __init__(self, required_role: str):
self.required_role = required_role
async def has_permission(self, request, view):
"""Check user role"""
if not view.user:
return False
return getattr(view.user, 'role', None) == self.required_role
class IsAdmin(HasRole):
"""Shortcut for admin role"""
def __init__(self):
super().__init__('admin')
class IsManager(HasRole):
"""Shortcut for manager role"""
def __init__(self):
super().__init__('manager')
# Usage
@viewset(router)
class CompanyViewSet(ModelViewSet[Company]):
permission_classes = [IsAuthenticated, IsAdmin]
Method-Based Permissions¶
from fastapi_mason.permissions import SAFE_METHODS
class ReadOnlyUnlessOwner(BasePermission):
"""Read-only for everyone, write access for owners"""
async def has_object_permission(self, request, view, obj):
# Read permissions for everyone
if request.method in SAFE_METHODS: # GET, HEAD, OPTIONS
return True
# Write permissions only for owners
if not view.user:
return False
return hasattr(obj, 'owner') and obj.owner == view.user.id
Permission Error Handling¶
Customize error messages: