JavaRush /Курси /Модуль 3: Django /Кастомізація дозволів для представлень

Кастомізація дозволів для представлень

Модуль 3: Django
Рівень 18 , Лекція 8
Відкрита

Ну як, готові взяти під контроль свої API? Сьогодні ми дізнаємося, як кастомізувати дозволи (permissions) в Django REST Framework (DRF). Це як дати кожному маршруту вашого API суперсилу: «Ти можеш пропустити тільки адміністраторів!» або «Ти відкритий тільки для користувачів із підтвердженою електронною поштою!». Згодьтесь, звучить цікаво!

З цієї лекції ви дізнаєтеся:

  • Як створювати власні класи дозволів.
  • Приклади ситуацій, де кастомізація дозволів корисна.
  • Як перевірити, що наші кастомні дозволи працюють коректно.

Дозволи в DRF: пояснення на пальцях

DRF вже дає нам свої стандартні класи:

  • AllowAny: взагалі без фейсконтролю, пускаємо всіх.
  • IsAuthenticated: пускаємо тільки зареєстрованих (аутентифікованих) користувачів.
  • IsAdminUser: всередину проходять тільки адміністратори.
  • IsAuthenticatedOrReadOnly: записуватися можуть тільки свої, читати — хто завгодно.

Тільки ось іноді стандартного фейсконтролю недостатньо. Наприклад, як обмежити API доступ тільки користувачам з певним статусом (наприклад, тільки підтвердженим користувачам)? Тут у гру вступає кастомізація.

Як працюють кастомні дозволи?

Кастомні дозволи в DRF — це просто Python-клас, який наслідується від базового класу BasePermission. Цей клас повинен реалізувати метод has_permission() або has_object_permission().

Розберемося з обома:

  1. has_permission(self, request, view) — перевірка на рівні представлення.
    Цей метод вирішує, чи можна користувачу взагалі звертатися до даного ендпоінту.

  2. has_object_permission(self, request, view, obj) — перевірка на рівні об'єкта.
    Тут вже перевіряється доступ до певного об'єкта. Наприклад, можна налаштувати так, щоб користувач міг редагувати тільки свої записи.

Створюємо перший кастомний дозвіл

Почнемо з простого. Ми створимо кастомний дозвіл, який буде допускати до ендпоінту тільки користувачів з підтвердженою електронною поштою.

Крок 1. Найпростіший приклад кастомного дозволу

Створюємо файл permissions.py у будь-якому додатку. Наприклад, у папці вашого основного додатку.

from rest_framework.permissions import BasePermission

class IsEmailVerified(BasePermission):
    """
    Дозвіл дозволяє доступ тільки користувачам із підтвердженим email.
    """

    def has_permission(self, request, view):
        # Беремо користувача із запиту та перевіряємо, чи підтверджено його email
        return bool(request.user and request.user.is_authenticated and request.user.is_email_verified)

Як це працює:

  1. Ми наслідуємося від BasePermission.
  2. Перевизначаємо метод has_permission(), який повертає True, якщо доступ дозволено, і False, якщо ні.
  3. Перевіряємо request.user.is_email_verified — флаг підтвердження email.

Крок 2. Підключаємо дозвіл у представленні

Тепер підключимо наш новий дозвіл до якогось APIView.

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

class VerifiedUserView(APIView):
    permission_classes = [IsEmailVerified]

    def get(self, request):
        return Response({"message": "Ви пройшли в суперсекретний клуб!"})

Що відбудеться:

  • Якщо користувач не підтвердив email, він отримає стандартну відповідь із помилкою 403 Forbidden.
  • Якщо email підтверджений, користувач побачить повідомлення про свій успіх.

Перевірка дозволів на рівні об'єктів

Іноді потрібно обмежити доступ не лише в цілому, але й до конкретних записів. Наприклад, дозволити користувачу редагувати тільки свої коментарі.

Крок 1. Дозвіл для об'єктів

Визначимо кастомний дозвіл, який дозволить редагувати тільки свої записи.

from rest_framework.permissions import BasePermission

class IsOwner(BasePermission):
    """
    Доступ дозволено тільки власникам об'єкта.
    """

    def has_object_permission(self, request, view, obj):
        # Перевіряємо, що об'єкт має поле user, і воно збігається з request.user
        return obj.user == request.user

Крок 2. Підключаємо у ViewSet

Тепер застосуємо цей дозвіл у ViewSet.

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated
from .models import Comment
from .serializers import CommentSerializer
from .permissions import IsOwner

class CommentViewSet(ModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
    permission_classes = [IsAuthenticated, IsOwner]

    def get_permissions(self):
        # has_object_permission застосовується тільки до "безпечних" методів, таких як GET
        if self.action in ['update', 'partial_update', 'destroy']:
            return [IsOwner()]
        return [IsAuthenticated()]

Корисні трюки з кастомними дозволами

Складні перевірки з кількома умовами

Іноді потрібно врахувати кілька факторів. Наприклад: «Користувач має бути адміністратором або власником об'єкта».

from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsAdminOrOwner(BasePermission):
    """
    Доступ тільки для адміністраторів або власників об'єкта.
    """

    def has_object_permission(self, request, view, obj):
        return bool(
            request.user and
            (
                request.user.is_staff or obj.user == request.user
            )
        )

Логіка з SAFE_METHODS

Іноді можна дозволити перегляд усім, але редагування тільки обраним.

class ReadOnlyOrAdmin(BasePermission):
    """
    Доступ на читання для всіх, але зміни можуть робити тільки адміністратори.
    """

    def has_permission(self, request, view):
        return bool(
            request.method in SAFE_METHODS or
            request.user and request.user.is_staff
        )

Тестуємо наші кастомні дозволи

Щоб переконатися в коректній роботі кастомних дозволів, важливо протестувати їх. Можна це зробити за допомогою вбудованого DRF тест-клієнта або pytest.

Приклад тесту дозволу

Використовуємо Django TestCase для перевірки.

from django.test import TestCase
from rest_framework.test import APIClient
from django.contrib.auth.models import User
from .models import Post

class PermissionTests(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.user = User.objects.create_user(username="testuser", password="password", email="test@example.com")
        self.admin = User.objects.create_superuser(username="admin", password="adminpass", email="admin@example.com")
        self.post = Post.objects.create(title="Test Post", content="Some content", user=self.user)

    def test_owner_permission(self):
        self.client.force_authenticate(user=self.user)
        response = self.client.delete(f"/posts/{self.post.id}/")
        self.assertEqual(response.status_code, 204)  # Успішне видалення своїм власником

    def test_admin_permission(self):
        self.client.force_authenticate(user=self.admin)
        response = self.client.delete(f"/posts/{self.post.id}/")
        self.assertEqual(response.status_code, 204)  # Адмін може видаляти будь-який пост

    def test_no_permission(self):
        another_user = User.objects.create_user(username="another", password="password", email="another@example.com")
        self.client.force_authenticate(user=another_user)
        response = self.client.delete(f"/posts/{self.post.id}/")
        self.assertEqual(response.status_code, 403)  # Інший користувач не може видалити пост

Типові помилки та підводні камені при налаштуванні дозволів

Коли працюєте з кастомними дозволами, пам'ятайте:

  • Дозволи перевіряються зверху вниз. Якщо у вас кілька класів дозволів, хоча б один із них може «завалити» запит.
  • Не забувайте налаштувати IsAuthenticated у DEFAULT_PERMISSION_CLASSES, якщо автентифікація обов'язкова за замовчуванням.
  • Часто забувають перевіряти, що request.user може бути AnonymousUser (це відбувається, якщо користувач не автентифікований).

Тепер, коли у вас є потужний набір інструментів для роботи з кастомними дозволами, ви можете налаштувати свої API так, щоб вони відповідали абсолютно будь-яким вимогам безпеки! 🚀

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ