Python

Разрешения в Django Rest Framework

Spread the love

Перевод: Špela Giacomelli (aka GirlLovesToCode)Permissions in Django Rest Framework

В этой статье рассматриваются особенности использования разрешений has_permission и has_object_permission в Django REST Framework (DRF).

Цели

К концу этой статьи вы сможете объяснить:

  1. Как работают разрешения DRF
  2. Сходства и различия между has_permission и has_object_permission
  3. Когда использовать has_permission и has_object_permission

Разрешения DRF

В DRF разрешения, наряду с аутентификацией (authentication) и регулированием (throttling), используются для предоставления или отказа в доступе для разных классов пользователей к разным частям 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. Далее, для запроса выполняется проверка throttling.

check_permissions вызывается перед выполнением обработчика представления, в то время как 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 views).

# 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.

Классы разрешений (permissions)

Разрешения в 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 имеет два метода, 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 Views (например, RetrieveAPIView) has_object_permission выполняется через check_object_permission внутри метода get_object из коробки.
  • has_object_permission никогда не выполняется для представлений списка (list views) (независимо от того, из какого представления вы расширяетесь) или когда используется метод запроса — POST (поскольку объект еще не существует).
  • Когда любое разрешение в has_permission возвращает False, has_object_permission не проверяется. Запрос немедленно отклоняется.

has_permission против has_object_permission

В чем разница между has_permission и has_object_permission в Django REST Framework?

Опять же, для:

  • В представления списков (List views), выполняется только has_permission, и запрос либо предоставляется, либо отклоняется. Если в доступе отказано, объекты никогда не будут извлечены.
  • В подробных представлениях (Detail views), has_permission выполняется только если разрешение предоставлено, has_object_permission выполняется после получения объекта.

Встроенные классы разрешений DRF

Что касается встроенных классов разрешений DRF, все они переопределяют has_permission, в то время как только DjangoObjectPermissions переопределяет has_object_permission:

Permission classhas_permissionhas_object_permission
AllowAny
IsAuthenticated
IsAdminUser
IsAuthenticatedOrReadOnly
DjangoModelPermissions
DjangoModelPermissionsOrAnonReadOnly
DjangoObjectPermissionsby extending DjangoModelPermissions

Пользовательские классы разрешений

Для пользовательских классов разрешений (custom permission classes) вы можете переопределить один или оба метода. Если вы переопределяете только одно из них, вам нужно быть осторожным, особенно если используемые вами разрешения сложны или вы объединяете несколько разрешений. Оба параметра 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 viewDetail view
has_permissionПредоставляет разрешение аутентифицированному пользователюПредоставляет разрешение аутентифицированному пользователю
has_object_permissionНе влияетПредоставляет разрешение автору объекта
ResultДоступ предоставлен аутентифицированным пользователямДоступ предоставляется владельцу объекта, если он аутентифицирован

Неправильное использование

Давайте посмотрим на разрешение, которое лучше не делать, мы его создали только для того чтобы лучше понять, что происходит:

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.

Что тут происходит?

  1. В представлении списка (list view) проверяется только has_permission. Итак, поскольку у настраиваемого класса его нет, он проверяет has_permission из BasePermission, который безоговорочно возвращает True.
  2. Подробное представление (detail view) сначала проверяет has_permission (опять же, всегда True). Затем проверяется has_object_permission, что запрещает доступ только неаутентифицированным пользователям.

Вот почему в этом примере неаутентифицированные запросы не имеют доступа к подробным представлениям, но у них есть доступ к представлениям списков.

List viewDetail view
has_permissionИспользует функцию по умолчанию, которая предоставляет разрешение без каких-либо условийИспользует функцию по умолчанию, которая предоставляет разрешение без каких-либо условий
has_object_permissionНе влияетПредоставляет разрешение аутентифицированному пользователю
ResultРазрешение всегда предоставляетсяРазрешение предоставляется авторизованным пользователям

Этот класс разрешений был создан только для того, чтобы показать, как работают два метода. Вам следует использовать встроенный класс IsAuthenticated вместо того, чтобы создавать свой собственный.

Заключение

Все разрешения, настраиваемые или встроенные, в Django REST Framework используют метод либо has_permission, либо has_object_permission, либо оба варианта для ограничения доступа к конечным точкам API.

Хотя has_permission не имеет ограничений относительно того, когда его можно использовать, у него нет доступа к желаемому объекту. Из-за этого это скорее «общая» проверка разрешений, чтобы гарантировать, что запрос и пользователь могут получить доступ к представлению. С другой стороны, поскольку has_object_permission имеет доступ к объекту, разрешения которого могут быть гораздо более конкретными, но у него есть много ограничений относительно того, когда его можно использовать.

Имейте в виду, что если вы не переопределите методы, они всегда будут возвращать True, предоставляя неограниченный доступ. При этом has_permission влияет только на доступ к представлениям списков, в то время как они оба влияют на доступ к подробному представлению.

Знание и понимание того, как работают оба этих метода, особенно важно при создании пользовательских (custom) классов разрешений.

Была ли вам полезна эта статья?
[16 / 4.8]

Spread the love
Editorial Team

View Comments

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

10 месяцев ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

10 месяцев ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

2 года ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

2 года ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

2 года ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

2 года ago