Права доступа в Django REST Framework
Серия "Права доступа в Django REST Framework":
- Права доступа в Django REST Framework (эта статья!)
- Встроенные классы разрешений в Django REST Framework
- Пользовательские классы прав доступа в Django REST Framework
Цели
К концу этой статьи вы должны быть в состоянии объяснить:
- Как работают разрешения DRF
- Сходства и различия между
has_permission
иhas_object_permission
- Когда использовать
has_permission
иhas_object_permission
Права в DRF
В DRF, разрешения, наряду с аутентификацией и ограничением, используются для предоставления или запрета доступа различных классов пользователей к различным частям API.
Аутентификация и авторизация работают рука об руку. Аутентификация всегда выполняется перед авторизацией.
Если аутентификация - это процесс проверки личности пользователя (пользователя, от которого пришел запрос, токена, которым он был подписан), то авторизация - это процесс проверки наличия у пользователя запроса необходимых разрешений для его выполнения (является ли он суперпользователем, является ли создателем объекта).
Процесс авторизации в DRF охватывается разрешениями.
Вид разрешений
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
вызывается до выполнения обработчика представления, а 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
вызывается после извлечения объекта из базы данных для всех детальных представлений.
# 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
есть два метода, has_permission
и has_object_permission
, которые оба возвращают True
. Классы разрешений переопределяют один или оба метода, чтобы безусловно возвращали 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
) выполняется до выполнения обработчика представления, без его явного вызова.
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
никогда не выполняется для списочных представлений (независимо от того, из какого представления вы расширяетесь) или когда метод запросаPOST
(поскольку объект еще не существует).- Когда любой
has_permission
возвращаетFalse
,has_object_permission
не проверяется. Запрос немедленно отклоняется.
has_permission или has_object_permission
В чем разница между has_permission
и has_object_permission
в Django REST Framework?
Опять же, для:
- Списочные представления, выполняется только
has_permission
и запрос либо предоставляется, либо отказывается в доступе. Если в доступе отказано, объекты никогда не будут получены. - Подробные представления,
has_permission
выполняется, а затем толькоесли разрешение предоставлено,has_object_permission
выполняется после получения объекта.
Встроенные классы разрешений DRF
Что касается встроенных классов разрешений DRF, то все они переопределяют has_permission
, в то время как только DjangoObjectPermissions
переопределяет has_object_permission
:
Permission class | has_permission | has_object_permission |
---|---|---|
AllowAny | ✓ | ✗ |
IsAuthenticated | ✓ | ✗ |
IsAuthenticatedOrReadOnly | ✓ | ✗ |
IsAdminUser | ✓ | ✗ |
DjangoModelPermissions | ✓ | ✗ |
DjangoModelPermissionsOrAnonReadOnly | ✓ | ✗ |
DjangoObjectPermissions | by extending DjangoModelPermissions |
✓ |
Для получения дополнительной информации о встроенных классах разрешений обязательно ознакомьтесь со второй статьей из этого цикла, Встроенные классы разрешений в Django REST Framework.
Пользовательские классы разрешений
Для пользовательских классов разрешений вы можете переопределить один или оба метода. Если вы переопределите только один из них, вам нужно быть осторожным, особенно если разрешения, которые вы используете, сложные или вы комбинируете несколько разрешений. И has_permission
, и has_object_permission
по умолчанию равны True
, поэтому если вы не зададите один из них явно, отказ в запросе будет зависеть от того, который вы задали явно.
Для получения дополнительной информации о классах пользовательских разрешений обязательно ознакомьтесь со второй статьей из этого цикла, Классы пользовательских разрешений в Django REST Framework.
Правильное использование
Давайте рассмотрим небольшой пример:
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 | Grants permission to the authenticated user | Grants permission to the authenticated user |
has_object_permission | Has no impact | Grants permission to the author of the object |
Result | Access granted for the authenticated users | Access granted to the owner of the object if they are authenticated |
Неправильное использование
Давайте рассмотрим разрешение, которое не делает того, что мы хотим, чтобы лучше понять, что происходит:
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
.
Подробный просмотр для неаутентифицированного пользователя:
Несмотря на то, что автоматически сгенерированный просматриваемый API показывает кнопку удаления, неавторизованный пользователь не может удалить сообщение.
И вид списка для неаутентифицированного пользователя:
Что происходит?
- Представление списка проверяет только
has_permission
. Так как у пользовательского класса его нет, он проверяетhas_permission
изBasePermission
, который безоговорочно возвращаетTrue
. - В детальном представлении сначала проверяется
has_permission
(опять же, всегдаTrue
). Затем проверяетсяhas_object_permission
, который запрещает доступ неаутентифицированным пользователям.
Вот почему в этом примере неаутентифицированные запросы не имеют доступа к подробным представлениям, но имеют доступ к представлениям списков.
List view | Detail view | |
---|---|---|
has_permission | Uses the default function that grants permission without any condition | Uses the default function that grants permission without any condition |
has_object_permission | Has no impact | Grants permission to the authenticated user |
Result | Permission is always granted | Permission is granted for the authenticated users |
Этот класс разрешения был создан только для того, чтобы показать, как работают эти два метода. Вам следует использовать встроенный класс
IsAuthenticated
, а не создавать свой собственный.
Заключение
Все разрешения, пользовательские или встроенные, в Django REST Framework используют либо has_permission
, либо has_object_permission
, либо оба для ограничения доступа к конечным точкам API.
Хотя has_permission
не имеет ограничений на то, когда его можно использовать, у него нет доступа к нужному объекту. Поэтому это скорее "общая" проверка прав доступа, чтобы убедиться, что запрос и пользователь могут получить доступ к представлению. С другой стороны, поскольку has_object_permission
имеет доступ к объекту, критерии могут быть гораздо более конкретными, но у него есть много ограничений на то, когда он может быть использован.
Помните, что если вы не переопределите методы, они всегда будут возвращать True
, предоставляя неограниченный доступ. Только has_permission
влияет на доступ к представлению списка, в то время как оба они влияют на доступ к представлению деталей.
Знание и понимание того, как работают оба этих метода, особенно важно при создании пользовательских классов разрешений.
Серия "Разрешения фреймворка Django REST":
- Права доступа в Django REST Framework (эта статья!)
- Встроенные классы разрешений в Django REST Framework
- Пользовательские классы прав доступа в Django REST Framework