Python Django REST Framework - 每个方法的单独权限

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19773869/
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 14:34:25  来源:igfitidea点击:

Django REST Framework - Separate permissions per methods

pythondjangorestpermissionsdjango-rest-framework

提问by José L. Pati?o

I am writing an API using Django REST Framework and I am wondering if can specify permissions per method when using class based views.

我正在使用 Django REST Framework 编写 API,我想知道在使用基于类的视图时是否可以指定每个方法的权限。

Reading the documentationI see that is quite easy to do if you are writing function based views, just using the @permission_classesdecorator over the function of the views you want to protect with permissions. However, I don't see a way to do the same when using CBVs with the APIViewclass, because then I specify the permissions for the full class with the permission_classesattribute, but that will be applied then to all class methods (get, post, put...).

阅读文档我看到如果您正在编写基于函数的视图,这很容易做到,只需在@permission_classes要使用权限保护的视图的函数上使用装饰器即可。但是,在将 CBV 与APIView类一起使用时,我没有看到执行相同操作的方法,因为然后我使用permission_classes属性指定了完整类的权限,但这将应用于所有类方法(get, post, put... )。

So, is it possible to have the API views written with CBVs and also specify different permissions for each method of a view class?

那么,是否可以使用 CBV 编写 API 视图,并为视图类的每个方法指定不同的权限?

采纳答案by Kevin Stone

Permissions are applied to the entire View class, but you can take into account aspects of the request (like the method such as GET or POST) in your authorization decision.

权限应用于整个 View 类,但您可以在授权决策中考虑请求的各个方面(如 GET 或 POST 等方法)。

See the built-in IsAuthenticatedOrReadOnlyas an example:

以内置IsAuthenticatedOrReadOnly为例:

SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']

class IsAuthenticatedOrReadOnly(BasePermission):
    """
    The request is authenticated as a user, or is a read-only request.
    """

    def has_permission(self, request, view):
        if (request.method in SAFE_METHODS or
            request.user and
            request.user.is_authenticated()):
            return True
        return False

回答by james

I've come across the same problem when using CBV's, as i have fairly complex permissions logic depending on the request method.

我在使用 CBV 时遇到了同样的问题,因为我有相当复杂的权限逻辑,具体取决于请求方法。

The solution i came up with was to use the third party 'rest_condition' app listed at the bottom of this page

我想出的解决方案是使用本页底部列出的第三方“rest_condition”应用程序

http://www.django-rest-framework.org/api-guide/permissions

http://www.django-rest-framework.org/api-guide/permissions

https://github.com/caxap/rest_condition

https://github.com/caxap/rest_condition

I just split the permissions flow logic so that each branch will run, depending on the request method.

我只是拆分了权限流逻辑,以便根据请求方法运行每个分支。

from rest_condition import And, Or, Not

class MyClassBasedView(APIView):

    permission_classes = [Or(And(IsReadOnlyRequest, IsAllowedRetrieveThis, IsAllowedRetrieveThat),
                             And(IsPostRequest, IsAllowedToCreateThis, ...),
                             And(IsPutPatchRequest, ...),
                             And(IsDeleteRequest, ...)]

So the 'Or' determines which branch of the permissions should run depending on the request method and the 'And' wraps the permissions relating to the accepted request method, so all must pass for permission to be granted. You can also mix 'Or', 'And' and 'Not' within each flow to create even more complex permissions.

因此,“Or”根据请求方法确定应运行权限的哪个分支,“And”包装与接受的请求方法相关的权限,因此所有权限都必须通过才能授予权限。您还可以在每个流中混合使用“或”、“与”和“非”以创建更复杂的权限。

The permission classes to run each branch simply look like this,

运行每个分支的权限类看起来像这样,

class IsReadyOnlyRequest(permissions.BasePermission):

    def has_permission(self, request, view):
        return request.method in permissions.SAFE_METHODS


class IsPostRequest(permissions.BasePermission):

    def has_permission(self, request, view):
        return request.method == "POST"


... #You get the idea

回答by GDorn

I ran into this problem and really wanted to use the @permission_classesdecorator to mark some custom view methods with specific permissions. I ended up coming up with a mixin:

我遇到了这个问题,真的很想使用@permission_classes装饰器来标记一些具有特定权限的自定义视图方法。我最终想出了一个mixin:

class PermissionsPerMethodMixin(object):
    def get_permissions(self):
        """
        Allows overriding default permissions with @permission_classes
        """
        view = getattr(self, self.action)
        if hasattr(view, 'permission_classes'):
            return [permission_class() for permission_class in view.permission_classes]
        return super().get_permissions()

An example use case:

一个示例用例:

from rest_framework.decorators import action, permission_classes  # other imports elided

class MyViewset(PermissionsPerMethodMixin, viewsets.ModelViewSet):
    permission_classes = (IsAuthenticatedOrReadOnly,)  # used for default ViewSet endpoints
    queryset = MyModel.objects.all()
    serializer_class = MySerializer

    @action(detail=False, methods=['get'])
    @permission_classes((IsAuthenticated,))  # overrides IsAuthenticatedOrReadOnly
    def search(self, request):
        return do_search(request)  # ...

回答by robin

Update 30 March 2020: My original solution only patched objectpermissions, not request permissions. I've included an update below to make this work with request permissions as well.

2020 年 3 月 30 日更新:我的原始解决方案仅修补对象权限,而不是请求权限。我在下面包含了一个更新,以使这项工作也适用于请求权限。

I know this is an old question but I recently ran into the same problem and wanted to share my solution (since the accepted answer wasn't quite what I needed). @GDorn's answer put me on the right track, but it only works with ViewSets because of the self.action

我知道这是一个老问题,但我最近遇到了同样的问题并想分享我的解决方案(因为接受的答案并不是我所需要的)。@GDorn 的回答让我走上了正确的轨道,但它只适用于ViewSets,因为self.action

I've solved it creating my own decorator:

我已经解决了它创建我自己的装饰器:

def method_permission_classes(classes):
    def decorator(func):
        def decorated_func(self, *args, **kwargs):
            self.permission_classes = classes
            # this call is needed for request permissions
            self.check_permissions(self.request)
            return func(self, *args, **kwargs)
        return decorated_func
    return decorator

Instead of setting the permission_classesproperty on the function, like the built-in decorator does, my decorator wraps the call and sets the permission classes on the view instance that is being called. This way, the normal get_permissions()doesn't need any changes, since that simply relies on self.permission_classes.

permission_classes不像内置装饰器那样在函数上设置属性,我的装饰器包装调用并在被调用的视图实例上设置权限类。这样,法线get_permissions()不需要任何更改,因为这仅依赖于self.permission_classes.

To work with request permissions, we do need to call check_permission()from the decorator, because the it's orginally called in initial()so before the permission_classesproperty is patched.

要使用请求权限,我们确实需要check_permission()从装饰器调用,因为它最初是在修补属性initial()之前调用的permission_classes

NoteThe permissions set through the decorator are the only ones called for objectpermissions, but for request permissions they are in additionto the class wide permissions, because those are always checked before the request method is even called. If you want to specify all permissions per method only, set permission_classes = []on the class.

注意通过装饰器设置的权限是唯一调用对象权限的权限,但对于请求权限,它们是类范围权限的补充,因为它们总是在调用请求方法之前进行检查。如果只想为每个方法指定所有权限,请permission_classes = []在类上设置。

Example use case:

示例用例:

from rest_framework import views, permissions

class MyView(views.APIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)  # used for default APIView endpoints
    queryset = MyModel.objects.all()
    serializer_class = MySerializer


    @method_permission_classes((permissions.IsOwnerOfObject,))  # in addition to IsAuthenticatedOrReadOnly
    def delete(self, request, id):
        instance = self.get_object()  # ...

Hope this helps someone running into the same problem!

希望这可以帮助遇到同样问题的人!

回答by Tomasz Wojcik

If you use actions, I think overwriting get_permissionswill do the trick.
If you don't use actions, there are some workarounds as well.
Take a look how djoserhandles this.

如果您使用操作,我认为覆盖get_permissions可以解决问题。
如果您不使用操作,也有一些解决方法。
看看djoser如何处理这个问题。

Example:

例子:

class UserViewSet(viewsets.ModelViewSet):
    permission_classes = settings.PERMISSIONS.user  # default

    def get_permissions(self):
        if self.action == "activation":  # per action
            self.permission_classes = settings.PERMISSIONS.activation
        return super().get_permissions()

    @action(["post"], detail=False)  # action
    def activation(self, request, *args, **kwargs):
        pass