30

I am using Django REST Framework to access a resource 'user'.

As user information is personal, I do not want a GET request to list every user on the system, UNLESS they are an admin.

If the user specifies their id, and they are logged in, I would like them to be able to view their details and amend them (PUT POST DELETE) if required.

So in summary, dis-allow GET method for anyone who isn't an admin and allow GET POST DELETE PUT on logged-in users when viewing their information.

I created the custom permission class:

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

This didn't work. After some debugging I found that it checks has_permission first, THEN checks has_object_permission. So if we don't get past that first hurdle GET /user/, then it won't even consider the next GET /user/id.

How I would go about getting this to work?

I was using ModelViewSets.

But if you split the List functionality with the Detail then you can give them separate permission classes:

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
4

5 回答 5

23

我过去曾使用自定义权限完成此操作,并被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, )
    ...
于 2013-09-05T22:13:28.907 回答
14

我也有类似的需求。让我们打电话给我的应用程序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 进行更新

于 2014-05-02T21:54:41.000 回答
10

对于偶然发现,对象级别权限限制下的文档说:

出于性能原因,通用视图在返回对象列表时不会自动将对象级别权限应用于查询集中的每个实例。

因此,详细信息视图将起作用,但对于列表,您需要针对当前用户进行过滤。

于 2015-07-09T18:39:15.617 回答
9

@will-hart 的回答还有一件事。

在 DRF3 文档中,

注意:实例级别的 has_object_permission 方法只有在视图级别的 has_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
于 2016-02-12T11:19:45.873 回答
1

这是对覆盖 has_object_permission() 方法的说明。使用复杂权限时,返回 False 不会按预期工作。有关更多详细信息,请参阅此问题 https://github.com/encode/django-rest-framework/issues/7117

于 2020-06-08T11:49:38.957 回答