Разрешения в Django Rest Framework
Перевод: Špela Giacomelli (aka GirlLovesToCode) — Permissions in Django Rest Framework
В этой статье рассматриваются особенности использования разрешений has_permission и has_object_permission в Django REST Framework (DRF).
Цели
К концу этой статьи вы сможете объяснить:
- Как работают разрешения DRF
- Сходства и различия между has_permission и has_object_permission
- Когда использовать has_permission и has_object_permission
Разрешения DRF
В DRF разрешения, наряду с аутентификацией (authentication) и регулированием (throttling), используются для предоставления или отказа в доступе для разных классов пользователей к разным частям 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. Далее, для запроса выполняется проверка 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 class | has_permission | has_object_permission |
---|---|---|
AllowAny | ✓ | ✗ |
IsAuthenticated | ✓ | ✗ |
IsAdminUser | ✓ | ✗ |
IsAuthenticatedOrReadOnly | ✓ | ✗ |
DjangoModelPermissions | ✓ | ✗ |
DjangoModelPermissionsOrAnonReadOnly | ✓ | ✗ |
DjangoObjectPermissions | by 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
Этот класс разрешений разрешает доступ к нему только автору объекта:
- В has_permission мы отказываем в разрешении только неаутентифицированным пользователям. На данный момент у нас нет доступа к объекту, поэтому мы не знаем, является ли пользователь, делающий запрос, автором желаемого объекта.
- Если пользователь аутентифицирован, после получения объекта вызывается has_object_permission, где мы проверяем, совпадает ли автор объекта с пользователем.
Полученные результаты:
List view | Detail 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.
Что тут происходит?
- В представлении списка (list view) проверяется только has_permission. Итак, поскольку у настраиваемого класса его нет, он проверяет has_permission из BasePermission, который безоговорочно возвращает True.
- Подробное представление (detail view) сначала проверяет has_permission (опять же, всегда True). Затем проверяется has_object_permission, что запрещает доступ только неаутентифицированным пользователям.
Вот почему в этом примере неаутентифицированные запросы не имеют доступа к подробным представлениям, но у них есть доступ к представлениям списков.
List view | Detail 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) классов разрешений.
Пожалуй, не разрешения а права