Django DRF permissions on create related objects

2019-08-30 22:19发布

问题:

I struggle to enforce security on objects creation on Django REST framework.

Basically, I can enforce security at the object level with 'has_object_permission' : the logged in user must be the owner of the object to manipulate it. Actually, as stated in the doc, I narrow objects retrieval in the queryset so, i got 404 instead of 403. That I think is not a problem (even better, as it hides the objects existences)

But I do not succeed at disallowing another user to create a related object....

I use ModelSerializer and ModelViewSet.

here are some naive snippets :

models.py :

class Daddy(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=20)
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

class Kiddy(models.Model):
    title = models.CharField(max_length=12)
    daddy = models.foreignKey(Daddy, on_delete=models.CASCADE)

serializers.py :

class KiddySerializer(serializers.ModelSerializer):
    class Meta:
        model = Kiddy
        fields = '__all__'

viewsets.py :

class KiddyViewSet(viewsets.ModelViewSet):
    serializer_class = KiddySerializer
    queryset = User.objects.none()

    permission_classes = (IsAuthenticated, IsOwner, )

    def get_queryset(self):
        dad_uuid = self.kwargs['dad']
        return Kiddy.objects.filter(daddy__pk=dad_uuid).filter(daddy__owner=self.request.user)

router.py :

router = routers.DefaultRouter()
router.register(r'dad', KiddyViewSet, base_name='kid')

urls.py :

path('api/<uuid:cv>/', include(router.urls))

Objects are accessed with those urls : http://..../api/4ecddcdd-1c0a-4d0b-8254-b0c0d2607e6d/ ---> list all kids

http://..../api/4ecddcdd-1c0a-4d0b-8254-b0c0d2607e6d/1 ---> object kid

Actually, I got some security because I use uuid, and this is hard to guess...

permissions.py :

class IsOwner(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `owner` attribute.
"""

def has_object_permission(self, request, view, obj):
    if request.user != obj.daddy.owner:
        return False
    else:
        return True

If logged with the wrong owner, I can create related kiddy objects!

I guess, I have to implement this permission in a 'has_permission' type. But I do not know how to access object in there as params are request and view...

Here, he found a solution. But this is not generic at all, cause I'll need a custom permission for every related objects... and I got many!!! Check permissions on a related object in Django REST Framework

Any idea?

回答1:

OK, I solved this issue :-)

In permissions.py, as I suspected I'll have to, I added this 'has_permission' :

class IsOwnerParent(permissions.BasePermission):
    def has_permission(self, request, view):
        daddy = Daddy.objects.get(pk=view.kwargs['dad'])
        return daddy.owner == request.user

and added it in my ModelViewSet class :

class KiddyViewSet(viewsets.ModelViewSet):
    serializer_class = KiddySerializer
    queryset = User.objects.none()

    permission_classes = (IsAuthenticated, IsOwner, IsOwnerParent)

    def get_queryset(self):
        dad_uuid = self.kwargs['dad']
        return Kiddy.objects.filter(daddy__pk=dad_uuid).filter(daddy__owner=self.request.user)

So, in fact the solution provided here Check permissions on a related object in Django REST Framework did work. And it's actually quite generic as many of my objects are related to the 'Daddy' objects.

Actually, now the behavior in retrieving a list of objects looks more 'normal' : I got 403 errors instead of 404.

I think this use case is quite common : users can only create related objects to objects they own and they can only update and delete objects they own too.

But maybe it can be done a better way?