Search code examples
django-modelsdjango-rest-frameworkdjango-rest-viewsets

How to restrict users from creating a model that they don't have access to


I have Workspace and Request models. A Workspace can have multiple Users assigned to it. Any User can create a request. I want to restrict a user from creating a request on only those Workspaces that he/she has access to.

Currently, in my create method, I am explicitly checking if the user is assigned to the workspace as shown in the code attached. But I am curious to know if I can use django permissions to do this in a better way. I have attempted to implement the get_permissions method as shown in the code.

# models.py
from django.db import models
from django.contrib.auth.models import User

# Create your models here.

class Workspace(models.Model):
    name = models.CharField(max_length=50)
    creation_date = models.DateTimeField('creation_date', auto_now_add=True)
    users = models.ManyToManyField(User)

    def __repr__(self):
        return '<Workspace: %r, %r, %r>' % (self.id, self.name, self.users)


class Request(models.Model):
    name = models.CharField(max_length=50)
    status = models.CharField(max_length=10, default='NEW')
    status_msg = models.CharField(max_length=100, default=None, blank=True, null=True)
    query_str = models.CharField(max_length=1000)
    # user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
    workspace = models.ForeignKey(Workspace, on_delete=models.DO_NOTHING)
    request_type = models.CharField(max_length=20, default='AD')
    creation_date = models.DateTimeField('creation_date', auto_now_add=True)


    def __repr__(self):
        return '<Request %r, %r, %r, %r>' % (self.id, self.query_str, self.status, self.status_msg)

# views.py
class RequestViewSet(viewsets.ModelViewSet):
    def create(self, request, *args, **kwargs):
            workspace_id = request.data['workspace']
            # check if user has access to this workspace
            if Workspace.objects.filter(pk=workspace_id).filter(users=request.user.id).exists():
                print('workspace exists')
                request.data['user'] = request.user.id
                print(request.data)
                return super(RequestViewSet, self).create(request, *args, **kwargs)
            return Response(status=status.HTTP_403_FORBIDDEN)


    def get_permissions(self):
            """
            Instantiates and returns the list of permissions that this view requires.
            """
            print(self.action)
            if self.action == 'create':
                permission_classes = [IsAuthenticated,]
            else:
                permission_classes = [IsAuthenticated,]
            print(permission_classes)
            return [permission() for permission in permission_classes]

Solution

  • What you want to apply is object-level permission. you do not need to use django permissions to do that. Instead, you can do that by getting use of request.user, which holds the current user making that request. So you will need something like:

    def your_view(request, pk):
        workspace = models.Workspace.objects.get(pk=pk)
        if request.user not in workspace.users.all():
            # user is not related to that workspace, permission denied
            raise PermissionDenied
        else:
            # permission granted, do what you want
            pass
    

    Instead of raising an exception, you can change your behavior when user has no permission such as returning a 403 html template etc. It depends on your design.