49

I am confused with the BasePermission in Django-rest-framework.

Here I defined a class: IsAuthenticatedAndOwner.

class IsAuthenticatedAndOwner(BasePermission):
    message = 'You must be the owner of this object.'
    def has_permission(self, request, view):
        print('called')
        return False
    def has_object_permission(self, request, view, obj):
        # return obj.user == request.user
        return False

Using in views.py

class StudentUpdateAPIView(RetrieveUpdateAPIView):
    serializer_class = StudentCreateUpdateSerializer
    queryset = Student.objects.all()
    lookup_field = 'pk'
    permissions_classes = [IsAuthenticatedAndOwner]

But it doesn't work at all. Everyone can pass the permission and update the data.

The called wasn't printed.


And I used to define this class: IsNotAuthenticated

class IsNotAuthenticated(BasePermission):
    message = 'You are already logged in.'
    def has_permission(self, request, view):
        return not request.user.is_authenticated()

It works well in the function

class UserCreateAPIView(CreateAPIView):
    serializer_class = UserCreateSerializer
    queryset = User.objects.all()
    permission_classes = [IsNotAuthenticated]

So, what are the differences between the examples above, and function has_object_permission & has_permission?

M1nt_zwy
  • 887
  • 3
  • 9
  • 19

5 Answers5

70

We have following two permission methods on BasePermission class:

  • def has_permission(self, request, view)
  • def has_object_permission(self, request, view, obj)

Those two different methods are called for restricting unauthorized users for data insertion and manipulation.

has_permission is called on all HTTP requests whereas, has_object_permission is called from DRF's method def get_object(self). Hence, has_object_permission method is available for GET, PUT, DELETE, not for POST request.

In summary:

  • permission_classes are looped over the defined list.
  • has_object_permission method is called after has_permission method returns value True except in POST method (in POST method only has_permission is executed).
  • When a False value is returned from the permission_classes method, the request gets no permission and will not loop more, otherwise, it checks all permissions on looping.
  • has_permission method will be called on all (GET, POST, PUT, DELETE) HTTP request.
  • has_object_permission method will not be called on HTTP POST request, hence we need to restrict it from has_permission method.
RaiBnod
  • 2,141
  • 2
  • 19
  • 25
  • Very nice answer. But where did you get all this information? Is there a link to it in the documentation? If there is, it would be nice to add it to the answer itself. – Philippe Fanaro Aug 21 '19 at 11:49
  • 1
    @PhilippeFanaro documention here: https://www.django-rest-framework.org/api-guide/permissions/#object-level-permissions – luistm Oct 17 '19 at 16:30
  • 1
    @luistm here https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions – Alex78191 Dec 26 '19 at 12:01
  • In my case `has_object_permission` is not being called even though `has_permission` returns True and the method is HTTP GET – Sabito stands with Ukraine Sep 04 '21 at 06:51
27

Basically, the first code denies everything because has_permission return False.

has_permission is a check made before calling the has_object_permission. That means that you need to be allowed by has_permission before you get any chance to check the ownership test.

What you want is:

class IsAuthenticatedAndOwner(BasePermission):
    message = 'You must be the owner of this object.'
    def has_permission(self, request, view):
        return request.user and request.user.is_authenticated
    def has_object_permission(self, request, view, obj):
        return obj.user == request.user

This will also allow authenticated users to create new items or list them.

Community
  • 1
  • 1
Linovia
  • 19,812
  • 4
  • 47
  • 48
  • Thanks for your answer.Maybe my question is not clear.At first, my codes was just like yours.But it didn't work and everyone could pass the permission.Then i update the function by`return False`. And it still pass everyone. – M1nt_zwy Mar 28 '17 at 14:01
  • So what is the difference? Why do we need 2 functions as opposed to only one? – Krimson Apr 17 '19 at 02:49
  • 2
    DRF documentation explains it. `has_permission` is a "generic" permission check to see whether or not the request meet some criteria. `has_object_permission` is more specific and linked to a specific instance. – Linovia Apr 17 '19 at 10:33
2

has_permission() is a method on the BasePermission class that is used to check if the user has permission to perform a certain action on the entire model. For example, you might use it to check if a user has permission to view a list of all objects of a certain model.

has_object_permission() is a method on the BasePermission class that is used to check if the user has permission to perform a certain action on a specific instance of the model. For example, you might use it to check if a user has permission to view, update or delete a specific object of a certain model.

For example, you might have a Book model and a User model in your application. You could use has_permission() to check if a user has permission to view a list of all books, while you use has_object_permission() to check if a user has permission to view, update or delete a specific book.

class IsBookOwnerOrAdmin(permissions.BasePermission):
    def has_permission(self, request, view):
       # Check if the user is authenticated
       if not request.user.is_authenticated:
          return False
       # Allow access for superusers
       if request.user.is_superuser:
           return True
       # Allow access if the user is the owner of the book
       if request.method in permissions.SAFE_METHODS:
           return True
       return False

   def has_object_permission(self, request, view, obj):
       # Allow access for superusers
       if request.user.is_superuser:
           return True
       # Allow access if the user is the owner of the book
       return obj.owner == request.user
Hamza.S
  • 1,319
  • 9
  • 18
1

I think this can help:

class IsAuthorOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        # Read-only permissions are allowed for any request
        if request.method in permissions.SAFE_METHODS:
            return True
        # Write permissions are only allowed to the author of a post
        return obj.user == request.user
0

As far as I can see, you are not adding your custom permission to the class as an argument.

This is your code:

class StudentUpdateAPIView(RetrieveUpdateAPIView):
    serializer_class = StudentCreateUpdateSerializer
    queryset = Student.objects.all()
    lookup_field = 'pk'
    permissions_classes = [IsAuthenticatedAndOwner]

But it should be:

class StudentUpdateAPIView(RetrieveUpdateAPIView, IsAuthenticatedAndOwner):
    serializer_class = StudentCreateUpdateSerializer
    queryset = Student.objects.all()
    lookup_field = 'pk'
    permissions_classes = [IsAuthenticatedAndOwner]

Note the custom permission IsAuthenticatedAndOwner as an argument in the class header.

PS: I hope this helps, I am a beginner in DRF but this is one of the things I just learned.