I'm creating an API with Django Rest Framework (DRF) and wondering where I should handle object level authorization.
So far I've created an Organization
model and a custom user model with email being the unique identifier instead of username. Organizations and users are currently connected through a many-to-many field.
What I'd like to do is make sure that when a user hits my API they're only able to do the standard CRUD actions on the models that are linked to the users respective organization. As an example here's my current UserViewSet
where I've overriden then get_queryset
method to filter the User
queryset to only return other users who are apart of the same organizations as the user calling the API:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
def get_queryset(self):
User = get_user_model()
user = self.request.user
organizations = user.organization.all()
return User.objects.filter(organization__in=organizations)
What's considered best practice to extend these limitations to other viewset actions? For example, if I want to make sure that the user can only create and add other users into the organizations they're linked to, should I override the create
method in the viewset and perform a validation there that the organization which was passed in the request data is the same as the organization that the user calling the API belongs to?
My gut feeling is that I'd end up breaking DRY this way, because I'd be overriding all the viewset actions and repeating nearly identical overrides. Is this intuition wrong? I guess I could separate the "validations" into a separate services.py
file and call them in the overriden actions. Should I instead offload these checks to custom permissions? Or should I disregard the views completely and put the validation into the serializers?
Actually, you need different tools for different DRF CRUD actions. Personaly, I love to use rules
package
name=XXX-list
: /XXX/
list
: permissions through get_queryset()
filteringcreate
: permissions through rules
and payload validation with serializer
name=XXX-detail
: /XXX/{id}
retrieve
: permissions through get_queryset()
filteringpartial_update
, update
and destroy
: permissions through rules
and get_queryset()
filteringYou will probably need to write a custom DjangoObjectPermission
class to integrate rules