Разрешения на уровне объекта Django REST
Я использую Django REST Framework для доступа к ресурсу 'user'.
Поскольку информация пользователя является личной, я не хочу, чтобы запрос GET перечислил каждого пользователя в системе, ЕСЛИ они являются администратором.
Если пользователь указывает свой идентификатор, и они вошли в систему, я бы хотел, чтобы они могли просматривать их данные и изменять их (PUT POST DELETE), если это необходимо.
Итак, вкратце, запретите метод GET для всех, кто не является администратором, и разрешите GET POST DELETE PUT при входе в систему при просмотре их информации.
поэтому я создал собственный класс разрешений:
class UserPermissions(permissions.BasePermission):
"""
Owners of the object or admins can do anything.
Everyone else can do nothing.
"""
def has_permission(self, request, view):
# if admin: True otherwise False
def has_object_permission(self, request, view, obj):
# if request.user is the same user that is contained within the obj then allow
Это не сработало. После некоторой отладки я обнаружил, что он сначала проверяет has_permission, THEN проверяет has_object_permission. Поэтому, если мы не преодолеем этот первый барьер GET/user/, тогда он даже не рассмотрит следующий GET/user/id.
Итак, кто-нибудь знает, как я собираюсь заставить это работать?
Спасибо:)
EDIT:
Я использовал ModelViewSets, у которых есть эта проблема, как я описал.
Но если вы разделили функциональность "Список" с помощью "Детали", вы можете предоставить им отдельные классы разрешений:
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes=(UserPermissionsAll,)
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes=(UserPermissionsObj,)
class UserPermissionsAll(permissions.BasePermission):
"""
Owners of the object or admins can do anything.
Everyone else can do nothing.
"""
def has_permission(self, request, view):
if request.user.is_staff:
return True
else:
return False
class UserPermissionsObj(permissions.BasePermission):
"""
Owners of the object or admins can do anything.
Everyone else can do nothing.
"""
def has_object_permission(self, request, view, obj):
if request.user.is_staff:
return True
return obj == request.user
Ответы
Ответ 1
Я делал это в прошлом, используя пользовательское разрешение и переопределил has_object_permission
как has_object_permission
ниже:
from rest_framework import permissions
class MyUserPermissions(permissions.BasePermission):
"""
Handles permissions for users. The basic rules are
- owner may GET, PUT, POST, DELETE
- nobody else can access
"""
def has_object_permission(self, request, view, obj):
# check if user is owner
return request.user == obj
Вы можете сделать некоторые более подробные вещи, такие как запрет определенных типов запросов (например, чтобы разрешить запросы GET для всех пользователей):
class MyUserPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Allow get requests for all
if request.method == 'GET':
return True
return request.user == obj
Затем, по вашему мнению, вы должны использовать класс разрешений:
from my_custom_permissions import MyUserPermissions
class UserView(generics.ListCreateAPIView):
...
permission_classes = (MyUserPermissions, )
...
Ответ 2
У меня такая же потребность. Позволяет вызвать мое приложение x
. Вот что я придумал.
Сначала поставьте это в x/viewsets.py
:
# viewsets.py
from rest_framework import mixins, viewsets
class DetailViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet):
pass
class ReadOnlyDetailViewSet(
mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
pass
class ListViewSet(
mixins.ListModelMixin,
viewsets.GenericViewSet):
pass
Затем в x/permissions.py
:
# permissions.py
from rest_framework import permissions
class UserIsOwnerOrAdmin(permissions.BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_authenticated()
def check_object_permission(self, user, obj):
return (user and user.is_authenticated() and
(user.is_staff or obj == user))
def has_object_permission(self, request, view, obj):
return self.check_object_permission(request.user, obj)
Затем в x/views.py
:
# views.py
from x.viewsets import DetailViewSet, ListViewSet
from rest_framework import permissions
class UserDetailViewSet(DetailViewSet):
queryset = User.objects.all()
serializer_class = UserDetailSerializer
permission_classes = (UserIsOwnerOrAdmin,)
class UserViewSet(ListViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes (permissions.IsAdminUser,)
Кстати, обратите внимание, что для этих двух видов просмотра можно использовать другой сериализатор, что означает, что вы можете показывать разные атрибуты в представлении list
, чем в представлении retrieve
! Например:
# serializers.py
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('username', 'url',)
class UserDetailSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'groups', 'profile', 'password',)
write_only_fields = ('password',)
Затем в x/urls.py
:
# urls.py
from x import views
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'users', views.UserDetailViewSet)
...
Я был слегка удивлен, что router
принял тот же шаблон дважды, но он, похоже, работает.
Caveat lector: Я подтвердил, что все это работает через браузер API, но я еще не пробовал обновление через API.
Ответ 3
Для справки, документация в соответствии с ограничениями разрешения уровня объекта говорит:
For performance reasons the generic views will not automatically apply object level permissions to each instance in a queryset when returning a list of objects.
Итак, просмотр сведений будет работать, но для списка вам нужно filter от текущего пользователя.
Ответ 4
Просто еще одна вещь для ответа @will-hart.
В документации DRF3
Примечание. Метод has_object_permission на уровне экземпляра будет вызываться только в том случае, если проверки уровня_изличения уже прошли
Поэтому has_permission
следует указать, чтобы использовать has_object_permission
.
from rest_framework import permissions
class MyUserPermissions(permissions.BasePermission):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
return request.user == obj
Однако, выше код даст разрешение кому угодно, когда пользователь попытается получить список пользователей. В этом случае было бы лучше дать разрешение в соответствии с action
, а не HTTP method
.
from rest_framework import permissions
def has_permission(self, request, view):
if request.user.is_superuser:
return True
elif view.action == 'retrieve':
return True
else:
return False
def has_object_permission(self, request, view, obj):
if request.user.is_superuser:
return True
elif view.action == 'retrieve':
return obj == request.user or request.user.is_staff