If I am logged in as user1 and I am accessing a ViewSet called RecipeSubmissionViewSet, the POST takes a recipe_id of the recipe the user wants to submit. How do I ensure that user1 does not submit user2's recipe, assuming the Recipe model has an owner field on it and I can just compare it to request.user? Should I use a permission class for this or is there a better way? I'm speaking from an backend point of view and not taking into account that the front end would of course filter out the recipes that belong to the user and only show them their own recipes.

3 Answers

Nafees Anwar On Best Solutions

There can be two ways. You can filter out queryset or define permission class.

If you override get_queryset method like this.

class RecipeSubmissionViewSet(...):
    def get_queryset(self):
        return Recipe.objects.filter(owner=self.request.user)
        # you can also use filtration based on action name like this

        # if self.action == 'update':
        #      return Recipe.objects.filter(owner=self.request.user)
        # return Recipe.objects.all()

User will get 404 response and will never be able to access objects other than he owns.

Second choice is permission class. You can define custom permission class and check ownership explicitly like this.

from rest_framework.permissions import BasePermission

class RecipeSubmissionPermission(BasePermission):
    def has_object_permission(self, request, view, obj):
        # you can also check permission here based on action
        # if view.action == 'update':
        #    pass
        return request.user.is_authenticated and obj.owner == request.user

class RecipeSubmissionViewSet(...):

In this case user will get 403 permission error.

If you use both of these methods. 404 will be preferred.

You can use whichever method you want or both of these. Permission class looks more programmatic and structured way of doing it but user will know that object with this id exists but he did not have permission to update it. But if you override queryset, user is not even be able to know if object exists or not thus more secure.

NKay On

Are you using the django authentication system? Then you should be able to access request.user in the views and set the owner field accordingly.

EDIT: I think I misunderstood the question.

But this could help and Nafees Anwar looks good.

Giannis Katsini On

Nafees answer is pretty much the way to go.

I have been developing microservices with multitenancy to users and the rules for them(as per my projecs spec) are:

  • Users cannot create items on behalf of another company/user
  • Users cannot view/edit items belonging to another company.

The way I do this is simple.

To prohibit viewing/editing of someone else's stuff

def get_queryset(self):
    return self.queryset.filter(user=request.user)

And to prohibit editing of someone else's stuff, this is done on a serializer

class SomeSerializer(...):
    def validate(self, data):
        data.pop('user', None)
        data['user'] = self.context['request'].user

With the above, the get_queryset in the viewset will always return a 404 if user1 requests user2 info.

And the validate function will prevent assignment. Ie. If user1 creates something assigned to user2, it will rather assign user1, this behaviour is what I needed in my application, you can always raise serializers.ValidationError("You cannot assign stuff to user2") instead if thats what you need instead of reassigning the user as I do in my use case. With having this logic in the validate you can be sure that any writable function will always carry the same behaviour for that serializer.

Hope that this helps.