Дозволи у фреймворку Django Rest

14 хв. читання

У цій статті ми розберемо, як працюють дозволи у фреймворку Django REST (DRF).

Мета

Після прочитання ви розумітимете:

  1. Як працюють дозволи DRF.
  2. Що спільного та чим відрізняються has_permission і has_object_permission.
  3. У яких випадках застосовувати has_permission та has_object_permission.

Дозволи DRF

У DRF дозволи, поряд з автентифікацією та тротлінгом, мають надавати або забороняти доступ різним класам користувачів до різних частин API.

Автентифікація та авторизація працюють пліч-о-пліч. Автентифікація завжди виконується перед авторизацією.

Якщо автентифікація — це процес розпізнавання користувача (від якого надходить підписаний токен доступу), то авторизація — це перевірка того, чи має необхідні дозволи на виконання запиту цей користувач (чи це суперкористувач, чи творець об'єкта).

Дозволи регулюють процес авторизації у DRF.

Дозволи View

APIView має два методи, які перевіряють наявність дозволів:

  1. check_permissions перевіряє, чи слід дозволити запит на основі даних запиту.
  2. 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?

Дозволи у фреймворку 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

Цей клас дозволу надасть доступ лише тому, хто створив об'єкт:

  1. У has_permission, ми відмовляємо в дозволі лише неавторизованим користувачам. У цей момент ми не маємо доступу до об'єкта, тож ми не знаємо, чи є творцем бажаного об'єкта користувач, який робить запит.
  2. Якщо користувача автентифіковано, після отримання об'єкта викликається 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 для неавтентифікованого користувача:

Дозволи у фреймворку Django Rest

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

І list view для неавтентифікованого користувача:

Дозволи у фреймворку Django Rest

Що ж відбувається?

  1. list view перевіряє лише has_permission. Отож, оскільки власний клас не має такого, він перевіряє has_permission у BasePermission, який безумовно повертає True.
  2. 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.

Коли ви створюєте власні класи дозволів, особливо важливо знати і розуміти обидва ці методи.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 6.2K
Приєднався: 7 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація