Skip to content

How do we check permissions for related entities? #450

Closed
@Anton-Shutik

Description

@Anton-Shutik

Let's say we have config like this:

views.py

class CustomerProfileViewSet(viewsets.ModelViewSet):
    permission_classes = (IsCustomerOrCreation,)


class ProductViewSet(viewsets.ModelViewSet):
    permission_classes = []

    def get_object(self,):
        customer_pk = self.kwargs.get('customer_pk', None)
            if customer_pk is not None:
                customer = get_object_or_None(Customer, pk=customer_pk)
                if customer is not None:
                    return customer.favourite_product
        return super(ProductViewSet, self).get_object()

urls.py

url(r'^api/customer/(?P<pk>\d+)/$', CustomerProfileViewSet.as_view({'get': 'retrieve'})),
url(r'^api/customer/(?P<customer_pk>\d+)/favorite-product$', ProductViewSet.as_view({'get': 'retrieve'})),

GET api/customer/1/ Will give 403 error for any user whose id differs from 1. And that is fine.
Then if anon user goes to api/customer/1/favourite-product/ they will get 200 response with product payload, because permissions for product checked in ProductViewSet, which have no permissions for any role. But I think it is wrong and should return 403, since we have to check permission for customer first. We actually just told everybody what is customer's 1 favorite product!

I understand that permissions it is out of json api spec, but may be there is a nice way I can handle permissions with this lib correctly?

For now I do it like below:
A RelatedMixin class will get a related entity and will find corresponding a serializer for that. And all that stuff will happen after we check permissions for parent ("customer" in the case) entity

class RelatedMixin(object):
    serializer_mapping = {}
    field_name_mapping = {}

    def get_related(self, request, *args, **kwargs):
        serializer_kwargs = {}
        instance = self.get_related_instance()

        if callable(instance):
            instance = instance()

        if hasattr(instance, 'all'):
            instance = instance.all()

        if instance is None:
            return Response(data=None)

        if isinstance(instance, Iterable):
            serializer_kwargs['many'] = True

        serializer = self.get_serializer(instance, **serializer_kwargs)
        return Response(serializer.data)

    def get_serializer_class(self):
        field_name = self.get_related_field_name()
        return self.serializer_mapping[field_name]

    def get_related_field_name(self):
        field_name = self.kwargs['related_field']
        if field_name in self.field_name_mapping:
            return self.field_name_mapping[field_name]
        return field_name

    def get_related_instance(self):
        try:
            return getattr(self.get_object(), self.get_related_field_name())
        except AttributeError:
            from rest_framework.exceptions import NotFound
            raise NotFound

and then just make a view that will handle all Customer's related entities:

class CustomerProfileRelatedViewSet(RelatedMixin, CustomerProfileViewSet):
    serializer_mapping = {
        'favourite_product': ProductSerializer,
        #'order_list': OrderSerializer
}

urls.py # Only one url route required for all related entities

url(r'^api/customer/(?P<customer_pk>\d+)/(?P<related_field>\w+)/$', CustomerProfileRelatedViewSet.as_view({'get': 'get_related'})),

What do you think about it ? If you like the idea I'm ready to come up with PR

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions