Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework: Permissions based on Object Attributes/Ownership

I'd like to allow users to create and view resources, but only if:

  1. They are staff OR
  2. They are the 'owner' of the object they're trying to create/view

I've got read-only permissions worked out fine, since users only have permission to get lists of objects when their primary key is used to generate the viewset. For example: GET /api/users/1/notes returns just the notes for the user with pk=1.

However, in testing, I discovered that users can create an object 'owned' by another user by posting it to their own list endpoint. For example user 1 can send a POST to /api/users/1/notes, but specify the note data as {user: "http://host.tld/users/2/", text: "Look! I created a note in another person's account!"}

I've got a fix below that seems to work fine, though I get the feeling I'm swimming against the current. Right now, within a custom permission, I create an instance of the object that would be created and check that its owner is the user making the request.

Is there a cleaner way of doing this?

Current Fix:

def has_permission(self, request, view):
    if request.user.is_staff:
        return True
    elif request.method in permissions.SAFE_METHODS:
        # check that the user is looking for their own list
        return request.user == User.objects.get(pk=view.kwargs['user_pk'])
    elif request.method not in permissions.SAFE_METHODS:
        # the user can create/modify the object if the new object's user == the request user
        # roundabout way of figuring this out... probably a better way
        user_path = request.POST['user'].split(request.get_host())[1]
        func = resolve(user_path).func
        kwargs = resolve(user_path).kwargs
        user_for_object = func.cls.model.objects.get(pk=kwargs['pk'])
        return user_for_object == request.user
    else:
        return False
like image 668
mgojohn Avatar asked Oct 20 '22 07:10

mgojohn


1 Answers

It depends on the rest of your code.

Normally DRF does object-level checks in a method called check_object_permissions in your ViewSet or permission backend.

This method gets called by (the default implementation) of get_object to check the permissions when any of the generics tries to get an object to work on.

If you use only Generic ViewSets and /notes is an @action this would be the easiest way.

If the object in these ViewSets is the note, I would suggest to build something looking similar (for example in a mixin you add to every ViewSet sitting under /user/). There are many different approaches to build nested resources and the routing for them.

like image 56
Denis Cornehl Avatar answered Oct 23 '22 01:10

Denis Cornehl