Django REST框架对象级权限

33

我正在使用Django REST框架访问一个名为'user'的资源。

由于用户信息是个人的,我不想通过GET请求列出系统中的每个用户,除非他们是管理员。

如果用户指定了他们的id,并且已经登录,我希望他们能够查看自己的详细信息并根据需要进行修改(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

化学程序员提供的以下解决方案将适用于 viewsets - jfunk
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, )
    ...

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,)

顺便提一下,需要注意的是你可以为这两个viewset使用不同的序列化程序,这意味着你可以在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进行更新


我喜欢这种方法,你甚至不需要创建像DetailViewSet或ListViewSet这样的中间类。而且它使定义权限变得更加容易。 - Overdrivr

11

关于 @will-hart回答的问题,还有一点需要注意。

在 DRF3 的文档中,

注意:仅当视图级别的 has_permission 检查已经通过后,才会调用实例级别的 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

然而,上述代码将允许任何人在用户尝试获取用户列表时获得权限。在这种情况下,最好是根据操作而不是HTTP方法来授权。

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

2
定义复杂权限,这有点像噩梦。有更好的方法吗? - Overdrivr

10
针对“意外发现者”,文档在对象级别权限的限制下指出:

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

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

1
如果您正在使用django-guardian,那么django-rest-framework-guardian是一种自动过滤的选项。 - phoenix

1

这正是我所需要的。问题在于使用 | 运算符时权限的顺序。第一个权限应该是具有 has_object_permission 方法的权限,而这个方法应该调用那些没有实现它的权限的 has_permission 方法。 - Hamza Abbad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接