Встроенные и кастомные permissions, object-level доступ
Права доступа (permissions) определяют, имеет ли пользователь право на выполнение запроса. Проверка прав происходит после аутентификации.
Permissions — механизм DRF, который проверяет, может ли аутентифицированный пользователь выполнить запрошенное действие. Права проверяются:
Глобальная настройка в settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}Разрешает доступ всем пользователям (анонимным и аутентифицированным):
from rest_framework.permissions import AllowAny
class PublicView(APIView):
permission_classes = [AllowAny]
def get(self, request):
return Response({'message': 'Public data'})Использование: Для публичных endpoints (регистрация, логин, публичные данные).
Требует аутентификации пользователя:
from rest_framework.permissions import IsAuthenticated
class ProtectedView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({'user': request.user.username})Использование: Для всех endpoints, требующих входа пользователя.
Требует, чтобы пользователь был администратором (is_staff=True):
from rest_framework.permissions import IsAdminUser
class AdminView(APIView):
permission_classes = [IsAdminUser]
def get(self, request):
return Response({'message': 'Admin only'})Использование: Для админ-панелей, управления пользователями, системных endpoints.
Проверяет права модели Django (add, change, delete, view):
from rest_framework.permissions import DjangoModelPermissions
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [DjangoModelPermissions]Требования:
queryset должен быть определён в viewМаппинг методов к правам:
| Метод | Требуемое право |
|---|---|
| POST | add |
| PUT/PATCH | change |
| DELETE | delete |
| GET (list/retrieve) | view (Django 2.1+) |
Как DjangoModelPermissions, но разрешает GET запросы анонимным пользователям:
from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly
class PublicArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [DjangoModelPermissionsOrAnonReadOnly]
# GET — всем, POST/PUT/DELETE — только с правамиРазрешает чтение (GET) всем, запись — только аутентифицированным:
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
# GET — всем, POST/PUT/DELETE — только авторизованнымСоздание своего класса прав:
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Разрешает чтение всем, но редактирование/удаление — только владельцу.
"""
def has_permission(self, request, view):
# Безопасные методы (GET, HEAD, OPTIONS) — всем
if request.method in permissions.SAFE_METHODS:
return True
# Для создания/обновления/удаления — только авторизованным
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
# Чтение — всем
if request.method in permissions.SAFE_METHODS:
return True
# Запись — только владельцу
return obj.author == request.userИспользование:
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsOwnerOrReadOnly]Проверяет право доступа до выполнения view:
def has_permission(self, request, view):
# request — текущий запрос
# view — экземпляр view
# Вернуть True для доступа, False для отказа
return request.user.is_staffПроверяет право доступа к конкретному объекту:
def has_object_permission(self, request, view, obj):
# obj — объект модели, к которому идёт обращение
# Вернуть True для доступа к объекту
return obj.author == request.userВажно: has_object_permission вызывается только для detail endpoints (retrieve, update, destroy), не для list.
Можно использовать несколько классов прав одновременно:
from rest_framework.permissions import IsAuthenticated, IsAdminUser
class StrictView(APIView):
permission_classes = [IsAuthenticated & IsAdminUser]
# Требуется и аутентификация, и права админа
class RelaxedView(APIView):
permission_classes = [IsAdminUser | IsAuthenticated]
# Требуется или админ, или просто аутентификация (фактически IsAuthenticated)Операторы:
& (AND) — все условия должны быть истинны| (OR) — хотя бы одно условие истинно~ (NOT) — инверсия условияПроверка прав на уровне объекта:
from rest_framework import permissions
class IsOwnerOrAdmin(permissions.BasePermission):
"""Только владелец или админ может редактировать объект"""
def has_object_permission(self, request, view, obj):
# Админы могут всё
if request.user.is_staff:
return True
# Владелец может редактировать
return obj.owner == request.user
# В view
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticated, IsOwnerOrAdmin]Проверка прав в валидации:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author']
def validate_author(self, value):
# Запретить установку другого автора
request = self.context['request']
if request.user != value and not request.user.is_staff:
raise serializers.ValidationError(
"Нельзя создать статью от имени другого пользователя"
)
return valuefrom rest_framework import viewsets, permissions
from rest_framework.decorators import action
from rest_framework.response import Response
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticated]
@action(detail=True, methods=['post'], permission_classes=[IsAdminUser])
def publish(self, request, pk=None):
"""Только админ может публиковать статьи"""
article = self.get_object()
article.is_published = True
article.save()
return Response({'status': 'published'})
@action(detail=True, methods=['post'])
def like(self, request, pk=None):
"""Любой авторизованный пользователь может лайкнуть"""
article = self.get_object()
article.likes.add(request.user)
return Response({'status': 'liked'})IsAuthenticated по умолчанию — безопаснее, чем разрешать всё('GET', 'HEAD', 'OPTIONS')& и | для гибкой логикиВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.