Python Django REST 框架对象级权限

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18645175/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 11:18:51  来源:igfitidea点击:

Django REST framework object level permissions

pythondjangopermissionsdjango-rest-framework

提问by user1830568

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

我正在使用 Django REST Framework 访问资源“用户”。

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

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

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.

如果用户指定了他们的 id,并且他们已经登录,我希望他们能够查看他们的详细信息并在需要时修改它们(PUT POST DELETE)。

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.

因此,总而言之,对于不是管理员的任何人,都不允许使用 GET 方法,并在查看他们的信息时允许登录用户使用 GET POST DELETE PUT。

so 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.

这没有用。经过一些调试我发现它首先检查has_permission,然后检查has_object_permission。因此,如果我们不通过第一个障碍 GET /user/,那么它甚至不会考虑下一个 GET /user/id。

so In summary, does anyone know how I would go about getting this to work?

所以总而言之,有谁知道我将如何让它发挥作用?

Thanks :)

谢谢 :)

EDIT:

编辑:

I was using ModelViewSets, which do have this problem as I described.

我正在使用 ModelViewSets,正如我所描述的那样,它确实存在这个问题。

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

但是,如果您将 List 功能与 Detail 分开,那么您可以为它们提供单独的权限类:

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

回答by will-hart

I have done this in the past using a custom permission and overridden has_object_permissionlike the following:

我过去使用自定义权限完成了此操作,并按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

You can do some more detailed things such as deny specific request types (for instance to allow a GET requests for all users):

你可以做一些更详细的事情,比如拒绝特定的请求类型(例如允许所有用户的 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

Then in your view you tell it to use the permissions class:

然后在您的视图中告诉它使用权限类:

from my_custom_permissions import MyUserPermissions

class UserView(generics.ListCreateAPIView):
    ...
    permission_classes = (MyUserPermissions, )
    ...

回答by Tim Ruddick

I have a similar need. Lets call my app x. Here's what I came up with.

我也有类似的需求。让我们调用我的应用程序x。这是我想出的。

First, put this in x/viewsets.py:

首先,把它放在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

Then in x/permissions.py:

然后在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)

Then in x/views.py:

然后在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,)

By the way, notice that you can use a different serializerfor those two viewsets, which means you can show different attributes in the listview than in the retrieveview! For example:

顺便说一句,请注意您可以为这两个视图集使用不同的序列化程序,这意味着您可以在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',)

Then in x/urls.py:

然后在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)

...

I was mildly surprised that routeraccepted the same pattern twice, but it does appear to work.

router两次接受相同的模式让我有点惊讶,但它似乎确实有效。

Caveat lector: I've confirmed this all works via the API browser, but I haven't tried updatingvia the API yet.

警告讲师:我已经通过 API 浏览器确认这一切都有效,但我还没有尝试通过 API 进行更新

回答by keni

For the stumble-upons, the documentationunder limitations of object level permission says:

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

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.

So, details view will work but for the list, you'll need to filteragainst the current user.

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

回答by Chemical Programmer

Just one more thing to @will-hart's answer.

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

In DRF3 documentation,

在 DRF3 文档中,

Note: The instance-level has_object_permission method will only be called if the view-level has_permission checks have already passed

注意:实例级 has_object_permission 方法只有在视图级 has_permission 检查已经通过时才会被调用

Therefore, has_permissionshould be specifiedto use 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


However, above code will give permission to anyone when user tries to get list of user. In this case, it would be better to give permission according to action, not the HTTP method.

但是,当用户尝试获取用户列表时,上面的代码将授予任何人权限。在这种情况下,最好根据 而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