Ну як, готові взяти під контроль свої API? Сьогодні ми дізнаємося, як кастомізувати дозволи (permissions) в Django REST Framework (DRF). Це як дати кожному маршруту вашого API суперсилу: «Ти можеш пропустити тільки адміністраторів!» або «Ти відкритий тільки для користувачів із підтвердженою електронною поштою!». Згодьтесь, звучить цікаво!
З цієї лекції ви дізнаєтеся:
- Як створювати власні класи дозволів.
- Приклади ситуацій, де кастомізація дозволів корисна.
- Як перевірити, що наші кастомні дозволи працюють коректно.
Дозволи в DRF: пояснення на пальцях
DRF вже дає нам свої стандартні класи:
AllowAny: взагалі без фейсконтролю, пускаємо всіх.IsAuthenticated: пускаємо тільки зареєстрованих (аутентифікованих) користувачів.IsAdminUser: всередину проходять тільки адміністратори.IsAuthenticatedOrReadOnly: записуватися можуть тільки свої, читати — хто завгодно.
Тільки ось іноді стандартного фейсконтролю недостатньо. Наприклад, як обмежити API доступ тільки користувачам з певним статусом (наприклад, тільки підтвердженим користувачам)? Тут у гру вступає кастомізація.
Як працюють кастомні дозволи?
Кастомні дозволи в DRF — це просто Python-клас, який наслідується від базового класу BasePermission. Цей клас повинен реалізувати метод has_permission() або has_object_permission().
Розберемося з обома:
has_permission(self, request, view)— перевірка на рівні представлення.
Цей метод вирішує, чи можна користувачу взагалі звертатися до даного ендпоінту.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)
Як це працює:
- Ми наслідуємося від
BasePermission. - Перевизначаємо метод
has_permission(), який повертаєTrue, якщо доступ дозволено, іFalse, якщо ні. - Перевіряємо
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 так, щоб вони відповідали абсолютно будь-яким вимогам безпеки! 🚀
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ