← Назад к вопросам

Зачем нужен Permission class?

2.0 Middle🔥 141 комментариев
#Django#REST API и HTTP

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Permission Class в Django REST Framework: назначение

Permission класс в Django REST Framework — это механизм для реализации гранулярного контроля доступа на уровне API endpoints. Он определяет, кто может выполнять какие действия над какими ресурсами.

1. Основное назначение

Permission класс проверяет разрешение перед выполнением handler функции:

from rest_framework import permissions
from rest_framework.views import APIView
from rest_framework.response import Response

# ❌ Без Permission
class UnsafePostView(APIView):
    def post(self, request):
        # Любой может создавать посты
        Post.objects.create(
            title=request.data['title'],
            content=request.data['content'],
            author=request.user  # Проблема: кто угодно может быть автором
        )
        return Response({"status": "created"})

# ✅ С Permission
class SafePostView(APIView):
    permission_classes = [permissions.IsAuthenticated]  # Только авторизованные
    
    def post(self, request):
        Post.objects.create(
            title=request.data['title'],
            content=request.data['content'],
            author=request.user  # Теперь гарантировано есть пользователь
        )
        return Response({"status": "created"})

2. Встроенные классы Permission

DRF предоставляет готовые классы:

from rest_framework import permissions

# AllowAny — разрешить всем (по умолчанию)
class PublicView(APIView):
    permission_classes = [permissions.AllowAny]

# IsAuthenticated — только авторизованные пользователи
class PrivateView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    
    def get(self, request):
        return Response({"user": request.user.username})

# IsAdminUser — только администраторы
class AdminView(APIView):
    permission_classes = [permissions.IsAdminUser]

# IsAuthenticatedOrReadOnly — авторизованные могут писать, остальные только читать
class BlogView(APIView):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    
    def get(self, request):
        return Response(Post.objects.all().values())
    
    def post(self, request):  # Только авторизованные
        Post.objects.create(**request.data)
        return Response({"status": "created"})

3. Создание кастомного Permission класса

Для специфичных правил доступа:

from rest_framework import permissions

class IsOwner(permissions.BasePermission):
    """
    Разрешить доступ только владельцу объекта
    """
    def has_object_permission(self, request, view, obj):
        # Проверка для конкретного объекта
        return obj.author == request.user

class IsPostOwnerOrReadOnly(permissions.BasePermission):
    """
    Только владелец может редактировать/удалять
    Остальные только читают
    """
    def has_object_permission(self, request, view, obj):
        # Разрешить чтение всем
        if request.method in permissions.SAFE_METHODS:  # GET, HEAD, OPTIONS
            return True
        
        # Редактирование только владельцу
        return obj.author == request.user

class IsNotBlocked(permissions.BasePermission):
    """
    Проверить что пользователь не заблокирован
    """
    def has_permission(self, request, view):
        if not request.user.is_authenticated:
            return True  # Анонимные могут читать
        
        return not request.user.profile.is_blocked

4. Методы Permission класса

from rest_framework import permissions

class CompletePermission(permissions.BasePermission):
    message = "Доступ запрещён"
    
    def has_permission(self, request, view):
        """
        Проверка VIEW-LEVEL разрешения.
        Вызывается для ВСЕХ запросов к view.
        """
        # Пример: проверить rate limit
        if request.user.api_calls_today >= 1000:
            return False
        
        return True
    
    def has_object_permission(self, request, view, obj):
        """
        Проверка OBJECT-LEVEL разрешения.
        Вызывается при доступе к конкретному объекту (detail view).
        """
        # Пример: проверить владельца
        if hasattr(obj, 'author'):
            return obj.author == request.user
        
        return True

# Использование
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [CompletePermission]

5. Комбинирование Permission классов

Логика И (все должны пройти):

from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
    # Все три должны пройти
    permission_classes = [
        IsAuthenticated,           # Должен быть авторизован
        IsNotBlocked,              # Не должен быть заблокирован
        IsPostOwnerOrReadOnly      # Владелец или только чтение
    ]

# В этом примере:
# 1. has_permission() проверяется для IsAuthenticated
# 2. has_permission() проверяется для IsNotBlocked
# 3. has_object_permission() проверяется для IsPostOwnerOrReadOnly (в retrieve/update/delete)

Для логики ИЛИ (хотя бы один должен пройти):

class IsStaffOrReadOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        # OR логика
        if request.method in permissions.SAFE_METHODS:
            return True
        return request.user.is_staff

6. Динамические Permission в зависимости от действия

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
    def get_permissions(self):
        """
        Разные permissions для разных действий
        """
        if self.action in ['list', 'retrieve']:
            # Чтение разрешено для всех
            permission_classes = [permissions.AllowAny]
        elif self.action in ['create']:
            # Создание только для авторизованных
            permission_classes = [permissions.IsAuthenticated]
        elif self.action in ['update', 'partial_update', 'destroy']:
            # Редактирование только для владельца
            permission_classes = [IsOwner]
        else:
            permission_classes = [permissions.IsAuthenticated]
        
        return [permission() for permission in permission_classes]

7. Проверка прав в сериализаторе или сервисе

Помимо View-level permissions, можно проверять в бизнес-логике:

class OrderService:
    @staticmethod
    def cancel_order(order_id: int, user: User) -> Order:
        order = Order.objects.get(id=order_id)
        
        # View-level permission исключит анонимных
        # Но сервис ещё раз проверяет владельца
        if order.customer != user:
            raise PermissionError("Not order owner")
        
        if order.status != Order.Status.PENDING:
            raise ValidationError("Can only cancel pending orders")
        
        order.status = Order.Status.CANCELLED
        order.save()
        return order

# В handler
class OrderViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]
    
    @action(detail=True, methods=['post'])
    def cancel(self, request, pk=None):
        try:
            order = OrderService.cancel_order(pk, request.user)
            return Response({"status": "cancelled"})
        except PermissionError:
            return Response(
                {"detail": "Not authorized"},
                status=status.HTTP_403_FORBIDDEN
            )

8. Интеграция с Django permissions

DRF может использовать встроенные Django permissions:

from rest_framework.permissions import DjangoModelPermissions

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
    # Использует Django model permissions
    # post.add, post.change, post.delete, post.view
    permission_classes = [DjangoModelPermissions]

# В администрировании даёш права группам
# Группа 'Editors' получает post.add, post.change
# Группа 'Moderators' получает post.delete

9. Обработка ошибок Permission

from rest_framework.exceptions import PermissionDenied

class CustomPermission(permissions.BasePermission):
    message = "У вас нет прав на это действие"
    
    def has_permission(self, request, view):
        if not self.check_access(request):
            # Кастомное сообщение об ошибке
            raise PermissionDenied(detail=self.message)
        return True

# API ответ при отказе:
# {
#   "detail": "У вас нет прав на это действие"
# }
# HTTP 403 Forbidden

Ключевые практики:

  • Всегда используй permissions для защиты endpoints
  • Разделяй логику: view-level (has_permission) и object-level (has_object_permission)
  • Создавай кастомные классы для специфичных правил доступа
  • Комбинируй несколько permissions если нужна сложная логика
  • Тестируй permissions отдельно от business logic
  • Документируй какие права нужны для каждого endpoint

Permission классы — критический компонент безопасности API, обеспечивающий контроль доступа и защиту от неавторизованного использования.