77

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

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

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

4

8 回答 8

65

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

以内置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
于 2013-11-05T08:21:57.363 回答
54

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

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

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

https://github.com/caxap/rest_condition

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

from rest_condition import And, Or, Not

class MyClassBasedView(APIView):

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

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

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

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
于 2014-01-29T10:45:33.550 回答
18

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

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

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

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

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

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

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

示例用例:

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()  # ...

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

于 2019-10-04T15:35:03.707 回答
13

我遇到了这个问题,真的很想使用@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()

一个示例用例:

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)  # ...
于 2019-07-10T14:55:12.717 回答
5

这个问题是关于实例的,但是对于任何登陆这里的人来说,使用下面的装饰器APIView来寻找每个方法的权限覆盖:@actionViewSets


class SandwichViewSet(ModelViewSet):
  permission_classes = [IsAuthenticated]

  @action(..., permission_classes=[CanSeeIngredients])
  def retrieve__ingredients(self, request):
    ...
于 2021-03-31T19:13:22.503 回答
4

我遇到了类似的问题。

我想允许未经身份验证的 POST,但不允许未经身份验证的 GET。

未经身份验证的公众成员可以提交项目,但只有经过身份验证的管理员用户才能检索提交的项目列表。

所以我为 POST 构建了一个自定义权限类UnauthenticatedPost,然后将权限类列表设置为IsAuthentictaedor UnauthenticatedPost

注意我只允许通过设置允许的方法来获取和发布http_method_names = ['get', 'post']

from django.http import HttpResponse
from rest_framework import viewsets
from rest_framework.permissions import BasePermission, IsAuthenticated
from MyAPI.serializers import MyAPISerializer
from MyAPI.models import MyAPI


class UnauthenticatedPost(BasePermission):
    def has_permission(self, request, view):
        return request.method in ['POST']


class MyAPIViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated|UnauthenticatedPost]
    queryset = MyAPI.objects.all().order_by('-TimeSubmitted')
    serializer_class = MyAPISerializer
    http_method_names = ['get', 'post']
于 2020-12-10T19:32:35.580 回答
3

如果您使用ViewSets 或ModelViewSets,我认为覆盖get_permissions就可以了。
看看djoser如何处理这个问题。

例子:

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

    
于 2020-02-08T20:02:44.747 回答
2

在 GET、PUT 和 POST 的不同权限方面,我们遇到了同样的挑战,并且使用定制的权限类解决了这个问题:

from rest_framework import permissions

class HasRequiredPermissionForMethod(permissions.BasePermission):
    get_permission_required = None
    put_permission_required = None
    post_permission_required = None

    def has_permission(self, request, view):
        permission_required_name = f'{request.method.lower()}_permission_required'
        if not request.user.is_authenticated:
            return False
        if not hasattr(view, permission_required_name):
            view_name = view.__class__.__name__
            self.message = f'IMPLEMENTATION ERROR: Please add the {permission_required_name} variable in the API view class: {view_name}.'
            return False

        permission_required = getattr(view, permission_required_name)
        if not request.user.has_perm(permission_required):
            self.message = f'Access denied. You need the {permission_required} permission to access this service with {request.method}.'
            return False

        return True

我们在我们的 API 中使用它,如下所示:

class MyAPIView(APIView):
    permission_classes = [HasRequiredPermissionForMethod]
    get_permission_required = 'permission_to_read_this'
    put_permission_required = 'permission_to_update_this'
    post_permission_required = 'permission_to_create_this'

    def get(self, request):
        # impl get

    def put(self, request):
        # impl put

    def post(self, request):
        # impl post
于 2020-10-02T11:07:34.283 回答