DRF Мне нужно, чтобы разрешения на уровне объекта / экземпляра были предоставлены, но разрешения на уровне представления / модели были отклонены
У меня довольно простой сервер и API с двумя типами пользователей, User и Admin. Я создал группы и разрешения в Django. Администратор должен все обрабатывать, а Пользователь должен только просматривать и редактировать себя.
Я использовал DjangoModelPermissions в DRF, и он работает должным образом для разрешений на уровне представления / модели. Это выглядит следующим образом.
class MyAppModelPermissions(permissions.DjangoModelPermissions):
def __init__(self):
self.perms_map = copy.deepcopy(self.perms_map)
self.perms_map['GET'] = ['%(app_label)s.view_%(model_name)s']
Для разрешений на уровне объекта/экземпляра я добавил пользовательское разрешение, которое содержит только метод has_object_permission() и проверяет, является ли запрашивающий пользователь объектом, к которому осуществляется доступ. Это выглядит следующим образом:
class UserAccessPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if (request.user == obj) or (get_user_role(request.user) in ("admin",)):
return True
else:
return False
Теперь, поскольку в DRF сначала проверяются разрешения на уровне представления/модели, и, если они разрешены, могут быть проверены разрешения на уровне объекта /экземпляра, любому пользователю, скажем, пользователю с id=4, всегда разрешено просматривать все другие пользовательские данные при доступе к списку пользователей, т.е.: api/users/. Это связано с тем, что этот параметр должен быть разрешен, чтобы пользователь мог получить доступ к своим собственным данным (т.е.: api/users/4/).
Мне удалось "решить" это, добавив проверку в list() метод пользователя ModelViewSet, так что, если пользователь попытается получить доступ к api/users/, он просто будет заблокирован, примерно так:
class UserViewset(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [MyAppModelPermissions & UserAccessPermission]
def list(self, request, *args, **kwargs):
if not (utils.get_user_role(request.user) == "admin"):
content = {"detail": "No permissions."}
return Response(content, status=status.HTTP_403_FORBIDDEN)
return super().list(request, *args, **kwargs)
Итак, это работает, но выглядит как-то халтурно. Есть ли какой-нибудь другой, более правильный способ сделать это?
Что вы можете сделать здесь, так это поработать с FilterBackend для фильтрации, тогда это будет выглядеть следующим образом:
class MeOrAdminFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
if get_user_role(request.user) == 'admin':
return queryset
else:
return queryset.filter(pk=request.user.pk)
и зарегистрируйте это как:
class UserViewset(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [MyAppModelPermissions & UserAccessPermission]
filter_backends = [MeOrAdminFilter]
# no override of list necessary
При извлечении будет возвращен список, содержащий только текущего пользователя api/users.
У меня была идея, что фильтрация на самом деле является частью того, что могут делать классы разрешений, но, возможно, это не в фреймворке Django REST, а в другом фреймворке.
Другой вариант - проверить действие в разрешении:
class UserAccessPermission(permissions.BasePermission):
def has_permission(self, request, view):
if view.action == 'list':
return get_user_role(request.user) == 'admin'
else:
# retrieve, etc. is allowed for everyone
return True
def has_object_permission(self, request, view, obj):
return request.user == obj or get_user_role(request.user) == 'admin'
Это предотвратит запуск .list() для пользователей, не являющихся администраторами, и, таким образом, вернет сообщение 403 "Отказано в разрешении".