Разрешения на уровне объекта инфраструктуры Django REST

Я использую Django REST Framework для доступа к «пользователю» ресурса.

Поскольку информация о пользователе является личной, я не хочу, чтобы запрос 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, ЗАТЕМ проверяется has_object_permission. Так что, если мы не преодолеем это первое препятствие GET / user /, тогда он даже не будет рассматривать следующий GET / user / id.

Итак, вкратце, кто-нибудь знает, как я могу заставить это работать?

Спасибо :)

РЕДАКТИРОВАТЬ:

Я использовал 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

person user1830568    schedule 05.09.2013    source источник
comment
Приведенное ниже решение от Chemical Programmer будет работать с viewsets.   -  person jfunk    schedule 06.06.2016


Ответы (5)


Раньше я делал это с помощью специального разрешения и переопределил 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, )
    ...
person will-hart    schedule 05.09.2013

У меня такая же потребность. Назовем мое приложение 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 дважды принял один и тот же шаблон, но, похоже, он работает.

Предостережение: я подтвердил, что все это работает через браузер API, но я еще не пробовал обновлять через API.

person Tim Ruddick    schedule 02.05.2014
comment
Мне нравится этот подход, вам даже не нужно создавать промежуточные классы, такие как DetailViewSet или ListViewSet. И это значительно упрощает определение разрешений. - person Overdrivr; 03.01.2019

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

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.

Таким образом, просмотр подробностей будет работать, но для списка вам потребуется фильтр против текущий пользователь.

person keni    schedule 09.07.2015
comment
django-rest-framework-guardian - это один из вариантов автоматической фильтрации, если вы используете _ 2_. - person phoenix; 01.07.2019

Еще одна вещь к ответу @ will-hart.

В документации DRF3

Примечание. Метод has_object_permission на уровне экземпляра будет вызываться только в том случае, если проверки has_permission на уровне представления уже прошли.

Следовательно, для использования has_object_permission необходимо указать has_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
person Chemical Programmer    schedule 12.02.2016
comment
Для определения сложных разрешений это своего рода кошмар. Есть ли способ лучше ? - person Overdrivr; 03.01.2019

Это пояснение по поводу переопределения метода has_object_permission (). Возврат False не будет работать должным образом при использовании сложных разрешений. Обратитесь к этой проблеме для получения дополнительных сведений. https://github.com/encode/django-rest-framework/issues/7117

person sri vathsa    schedule 08.06.2020
comment
Это именно то, что мне нужно. Проблема заключалась в порядке разрешений при использовании оператора |. Первыми должны быть разрешения с has_object_permission методом, и этот метод должен вызывать has_permission для разрешений, которые его не реализуют. - person Hamza Abbad; 18.09.2020