Permissions¶
Permissions decide whether a request may execute a ViewSet action. They are small async classes with two hooks:
has_permission(request, view)for view-level checks;has_object_permission(request, view, obj)for object-level checks.
Ronin checks view-level permissions for routed actions and checks object-level
permissions when get_object() retrieves a model instance.
Built-In Permissions¶
| Class | Behavior |
|---|---|
IsAuthenticated |
Allows requests only when view.state.user is set. |
IsAuthenticatedOrReadOnly |
Allows GET, HEAD, OPTIONS; write methods require view.state.user. |
DenyAll |
Denies every request. |
Basic Usage¶
from fastapi_ronin.permissions import IsAuthenticatedOrReadOnly
@viewset(router)
class CompanyViewSet(ModelViewSet[Company]):
model = Company
read_schema = CompanySchema
create_schema = CompanyCreateSchema
permission_classes = [IsAuthenticatedOrReadOnly]
Permissions are instantiated per request by get_permissions().
Set the Current User¶
Ronin does not prescribe authentication. Use a FastAPI dependency or middleware to authenticate the request and put the user into request state.
from fastapi import Depends, FastAPI
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from fastapi_ronin.state import BaseStateManager
security = HTTPBearer(auto_error=False)
async def get_current_user(credentials: HTTPAuthorizationCredentials | None = Depends(security)):
if credentials and credentials.credentials == 'token':
user = {'id': 1, 'email': 'admin@example.com'}
BaseStateManager.set_user(user)
return user
return None
app = FastAPI(dependencies=[Depends(get_current_user)])
Action-Specific Permissions¶
Override get_permissions() when different actions need different rules.
from fastapi_ronin.permissions import IsAuthenticated
@viewset(router)
class CompanyViewSet(ModelViewSet[Company]):
permission_classes = [IsAuthenticated]
def get_permissions(self):
if self.action in ('list', 'retrieve', 'stats'):
return []
return [IsAuthenticated()]
self.action is set by Ronin while handling a route.
Custom Permissions¶
from fastapi_ronin.permissions import BasePermission
class IsOwner(BasePermission):
PERMISSION_DENIED_MESSAGE = 'Authentication required'
OBJECT_PERMISSION_DENIED_MESSAGE = 'Only the owner can access this object'
async def has_permission(self, request, view) -> bool:
return bool(view.user)
async def has_object_permission(self, request, view, obj) -> bool:
return getattr(obj, 'owner_id', None) == view.user['id']
Read-Only Custom Permission¶
from fastapi_ronin.permissions import BasePermission, SAFE_METHODS
class ReadOnlyOrStaff(BasePermission):
async def has_permission(self, request, view) -> bool:
if request.method in SAFE_METHODS:
return True
return bool(view.user and view.user.get('is_staff'))
Guidance¶
- Keep authentication in FastAPI dependencies or middleware.
- Keep authorization in permission classes.
- Use
get_permissions()for action-specific policy. - Put object ownership checks in
has_object_permission(). - Customize
PERMISSION_DENIED_MESSAGEandOBJECT_PERMISSION_DENIED_MESSAGEfor API-friendly errors.