Django REST framework object level permissions

前端 未结 5 1954
我寻月下人不归
我寻月下人不归 2021-02-01 03:46

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

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

相关标签:
5条回答
  • 2021-02-01 04:00

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

    First, put this in 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:

    # 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:

    # 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 serializer for those two viewsets, which means you can show different attributes in the list view than in the retrieve view! For example:

    # 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:

    # 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 router accepted the same pattern twice, but it does appear to work.

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

    0 讨论(0)
  • 2021-02-01 04:16

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

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

    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, )
        ...
    
    0 讨论(0)
  • 2021-02-01 04:17

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

    In DRF3 documentation,

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

    Therefore, has_permission should be specified to use 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.

    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
    
    0 讨论(0)
  • 2021-02-01 04:20

    For the stumble-upons, the documentation under 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 filter against the current user.

    0 讨论(0)
  • 2021-02-01 04:20

    This is a clarification on overriding the has_object_permission() method. Returning False wouldn't work as intended when using complex permissions. Refer to this issue for more details https://github.com/encode/django-rest-framework/issues/7117

    0 讨论(0)
提交回复
热议问题