Django REST Framework - Separate permissions per methods

后端 未结 7 1939
栀梦
栀梦 2020-12-23 13:19

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

Reading the documentation I se

相关标签:
7条回答
  • 2020-12-23 13:45

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

    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

    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_classes property 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.

    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_classes property is patched.

    Note The permissions set through the decorator are the only ones called for object permissions, but for request permissions they are in addition to 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.

    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!

    0 讨论(0)
  • 2020-12-23 13:46

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

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

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

    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.

    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
    
    0 讨论(0)
  • 2020-12-23 13:52

    We're having the same kind of challenges when it comes to having different permissions for GET, PUT and POST, and have solved this using a customized permission-class:

    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
    

    We use this in our API's like this:

    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
    
    0 讨论(0)
  • 2020-12-23 13:56

    I ran into this problem and really wanted to use the @permission_classes decorator to mark some custom view methods with specific permissions. I ended up coming up with a 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)  # ...
    
    0 讨论(0)
  • 2020-12-23 13:57

    I ran into a similar issue.

    I wanted to allow unauthenticated POSTs but disallow unauthenticated GETs.

    An unauthenticated member of the public can submit an item but only an authenticated admin user can retrieve the list of items submitted.

    So I built a custom permission class - UnauthenticatedPost - for the POST and then set the list of permission clases to be IsAuthentictaed or UnauthenticatedPost.

    Note I only allow gets and posts by setting the allowable methods with 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']
    
    0 讨论(0)
  • 2020-12-23 14:06

    If you use ViewSets or ModelViewSets, I think overwriting get_permissions will do the trick.
    Take a look at how djoser handles this.

    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
    
        
    
    0 讨论(0)
提交回复
热议问题