У цій статті ми розберемо, як працюють дозволи у фреймворку Django REST (DRF).
Мета
Після прочитання ви розумітимете:
- Як працюють дозволи DRF.
- Що спільного та чим відрізняються
has_permission
іhas_object_permission
. - У яких випадках застосовувати
has_permission
таhas_object_permission
.
Дозволи DRF
У DRF дозволи, поряд з автентифікацією та тротлінгом, мають надавати або забороняти доступ різним класам користувачів до різних частин API.
Автентифікація та авторизація працюють пліч-о-пліч. Автентифікація завжди виконується перед авторизацією.
Якщо автентифікація — це процес розпізнавання користувача (від якого надходить підписаний токен доступу), то авторизація — це перевірка того, чи має необхідні дозволи на виконання запиту цей користувач (чи це суперкористувач, чи творець об'єкта).
Дозволи регулюють процес авторизації у DRF.
Дозволи View
APIView має два методи, які перевіряють наявність дозволів:
check_permissions
перевіряє, чи слід дозволити запит на основі даних запиту.check_object_permissions
перевіряє, чи дозволити запит на основі поєднання даних запиту та даних об'єкта.
# rest_framework/views.py
class APIView(View):
# other methods
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
def check_object_permissions(self, request, obj):
"""
Check if the request should be permitted for a given object.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_object_permission(request, self, obj):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
Коли надходить запит, виконується автентифікація. Якщо автентифікація не є успішною, спрацьовує помилка NotAuthenticated
. Після цього дозволи перевіряються циклом, і, якщо будь-яку з перевірок не пройдено, викликається помилка PermissionDenied
. Нарешті, щодо запиту виконується тротлінг-перевірка.
check_permissions
викликається перед запуском view-обробника, разом з тим check_object_permissions
не виконується, якщо ви явно не викликаєте його. Наприклад:
class MessageSingleAPI(APIView):
def get(self, request, pk):
message = get_object_or_404(Message.objects.all(), pk=pk)
self.check_object_permissions(request, message) # explicitly called
serializer = MessageSerializer(message)
return Response(serializer.data)
Із ViewSets та Generic Views check_object_permissions
викликається після отримання об'єкта з бази даних для всіх Detail view.
# rest_framework/generics.py
class GenericAPIView(views.APIView):
# other methods
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj) # HERE
return obj
Якщо на запит хоча б одного з дозволів повертається відповідь False
, викликається помилка PermissionDenied
.
Класи дозволів
Дозволи DRF визначаються як список класів дозволів. Ви можете створити свій власний або застосовувати один з семи вбудованих класів. Усі класи дозволів, хоч власні, хоч вбудовані, розширюються класом BasePermission
:
class BasePermission(metaclass=BasePermissionMetaclass):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
return True
Як бачимо, BasePermission
має два методи, які повертають True
: has_permission
і has_object_permission
. Класи дозволів перевизначають один або обидва методи, щоб умовно повернути True
.
Повернімося до методів check_permissions
і check_object_permissions
з попереднього розділу:
check_permissions
викликаєhas_permission
для кожного дозволу.check_object_permissions
теж викликаєhas_object_permission
для кожного дозволу.
has_permission
has_permission
визначає, чи дозволено запиту та користувачеві мати доступ до певних об'єктів.
До прикладу:
-
Чи дозволений метод запиту?
-
Чи автентифіковано користувача?
-
Користувач є адміністратором чи суперкористувачем?
has_permission
володіє відомостями про запит, але не про об'єкт запиту.
Як ми пояснили на початку, метод has_permission
(викликаний методом check_permissions
) виконується перед запуском view-обробника, без явного виклику цього методу.
has_object_permission
has_object_permission
визначає, якому саме користувачу дозволено взаємодію з певним об'єктом.
До прикладу:
-
Ким створено об'єкт?
-
Коли його створено?
-
До якої групи належить об'єкт?
Окрім відомостей про сам запит, has_object_permission
також має дані про об'єкт запиту. Метод виконується після витягнення об'єкта з бази даних.
На відміну від has_permission
, виконування has_object_permission
не завжди є типовою поведінкою:
-
З
APIView
ви повинні явно викликатиcheck_object_permission
, щоб виконатиhas_object_permission
для всіх класів дозволів. -
З
ViewSets
(такими якModelViewSet
) або Generic View (як отRetrieveAPI1View
) виконуванняhas_object_permission
черезcheck_object_permission
всередині методуget_object
— це стандартна поведінка. -
has_object_permission
ніколи не виконується для list view (незалежно від того, який з view ви розширюєте) або для методу запитуPOST
(оскільки об'єкт ще не існує). -
Якщо будь-який
has_permission
повертаєFalse
, тоhas_object_permission
не перевіряється, а запит негайно відхиляється.
Порівняння has_permission і has_object_permission
Чим відрізняються has_permission
і has_object_permission
у фреймворку Django REST?

Повторимо:
- Для List view виконується лише
has_permission
і запитаний доступ або надається або відхиляється. Якщо у доступі відмовлено, об'єкти ніколи не повертаються у відповідь.
Для Detail view виконується has_permission
, і лише якщо дозвіл надано, то виконується has_object_permission
після отримання об'єкта.
Вбудовані класи дозволів DRF
Якщо ми поглянемо на вбудовані класи дозволів DRF, то всі вони перевизначають has_permission
, водночас лише DjangoObjectPermissions
перевизначає has_object_permission
:
Клас дозволу | has_permission | has_object_permission |
---|---|---|
AllowAny | ✓ | ✗ |
IsAuthenticated | ✓ | ✗ |
IsAuthenticatedOrReadOnly | ✓ | ✗ |
IsAdminUser | ✓ | Text |
DjangoModelPermissions | ✓ | ✗ |
DjangoModelPermissionsOrAnonReadOnly | ✓ | ✗ |
DjangoObjectPermissions | шляхом розширення DjangoModelPermissions |
✓ |
Власні класи дозволів
Для власних класів дозволів можна перевизначити один або обидва методи. Слід бути обережними, якщо ви перевизначаєте лише один із них: особливо якщо застосовуєте складні дозволи або поєднуєте кілька дозволів. І has_permission
, і has_object_permission
типово мають значення True
. Якщо ви явно не встановите значення одному з них, то відмова на запит залежатиме від того методу, для якого явно встановлено значення.
Правильне застосування
Розглянемо коротенький приклад:
from rest_framework import permissions
class AuthorOrReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_authenticated:
return True
return False
def has_object_permission(self, request, view, obj):
if obj.author == request.user:
return True
return False
Цей клас дозволу надасть доступ лише тому, хто створив об'єкт:
- У
has_permission
, ми відмовляємо в дозволі лише неавторизованим користувачам. У цей момент ми не маємо доступу до об'єкта, тож ми не знаємо, чи є творцем бажаного об'єкта користувач, який робить запит. - Якщо користувача автентифіковано, після отримання об'єкта викликається
has_object_permission
, де ми перевіряємо, чи автор об'єкта і є нашим користувачем.
Підсумуємо:
List view | Detail view | |
---|---|---|
has_permission | Надає дозвіл автентифікованому користувачеві | Надає дозвіл автентифікованому користувачеві |
has_object_permission | Не впливає | Надає дозвіл творцю об'єкта |
Результат | Доступ надається автентифікованим користувачам | Доступ надається власнику об'єкта після автентифікації |
Неправильне застосування
Тепер розглянемо дозвіл, який не робитиме те, що нам потрібно, — так ми зможемо краще зрозуміти, що відбувається:
from rest_framework import permissions
class AuthenticatedOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.user.is_authenticated:
return True
return False
Цей дозвіл забороняє доступ неавтентифікованому користувачеві, але перевірка виконується у has_object_permission
замість has_permission
.
Detail view для неавтентифікованого користувача:

Автоматично згенерований і доступний для перегляду API показує кнопку видалення — але неавтентифікований користувач все одно не може видалити повідомлення.
І list view для неавтентифікованого користувача:

Що ж відбувається?
- list view перевіряє лише
has_permission
. Отож, оскільки власний клас не має такого, він перевіряєhas_permission
уBasePermission
, який безумовно повертаєTrue
. - detail view спочатку перевіряє
has_permission
(знову ж таки повертаєTrue
). Потім перевіряєhas_object_permission
, який забороняє доступ неавтентифікованим користувачам.
Ось чому в цьому прикладі неавтентифіковані запити не мають доступу до detail views, але вони мають доступ до list views.
List view | Detail view | |
---|---|---|
has_permission | Застосовує типову функцію, яка надає дозвіл без будь-яких умов | Застосовує типову функцію, яка надає дозвіл без будь-яких умов |
has_object_permission | Не впливає | Надає дозвіл автентифікованому користувачу |
Результат | Дозвіл надається завжди | Дозвіл надається автентифікованим користувачам |
Цей клас дозволів створений, лише щоб показати, як працюють два методи. Вам слід застосовувати вбудований клас IsAuthenticated
, а не створювати власний.
Висновок
Усіма дозволами, як власними, так і вбудованими, у фреймворку Django Rest керують або has_permission
, або has_object_permission
, або обоє одночасно для обмеження доступу до кінцевих точок API.
Немає обмежень щодо того, коли може застосовуватись has_permission
, але він не має доступу до потрібного об'єкта. Тому це радше «загальна» перевірка дозволу, аби гарантувати, що запит та користувач можуть отримати доступ до view. З іншого боку, метод has_object_permission
має доступ до об'єкта, тож умови доступу можуть бути значно точнішими. Але він має багато обмежень щодо того, коли саме його можна застосувати.
Пам'ятайте, якщо ви не перевизначите методи, вони завжди повертатимуть True, надаючи необмежений доступ. Лише has_permission
впливає на доступ до list view, тоді як обидва розглянуті методи впливають на доступ до detail view.
Коли ви створюєте власні класи дозволів, особливо важливо знати і розуміти обидва ці методи.
Ще немає коментарів