aleksandr
aleksandr

Reputation: 17

Custom permissions in rests framework

I need permissions for my app in DRF.

Input:

Models:

class Publication(models.Model):
    pub_text = models.TextField(null=True, blank=True)
    pub_date = models.DateTimeField(auto_now_add=True)
    pub_author = models.ForeignKey(User, on_delete=models.CASCADE)

class Image(models.Model):
    image = models.ImageField(upload_to='images', null=True)
    image_to_pub = models.ForeignKey(Publication, on_delete=models.CASCADE, null=True, related_name='images')

Views:

class PublicationViewSet(viewsets.ModelViewSet):
    queryset = Publication.objects.all()
    serializer_class = PublicationSerializer
    permission_classes = [PublicPermission]


class ImageViewSet(viewsets.ModelViewSet):
    queryset = Image.objects.all()
    serializer_class = ImageSerializer
    permission_classes = [ImagePermission]

Permissions:

class ImagePermission(BasePermission):

    edit_methods = ['PUT', 'PATCH']

    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        if request.user.is_authenticated:
            return True

    def has_object_permission(self, request, view, obj):

        if request.user.is_superuser:
            return True

        if request.method in SAFE_METHODS:
            return True

        if request.user.id == Publication.objects.get(id=obj.image_to_pub_id).pub_author_id:
            return True

        if request.user.is_staff and request.method not in self.edit_methods:
            return True

        return False


class ImageAuthorPermission(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.user.id == Publication.objects.get(id=obj.image_to_pub_id).pub_author_id:
            return True
        return False

Now it works as i described above. But i'm mot sure if this is good practice.

I mean class <ImagePermission>. There are two times check if method in SAFE_METHODS.

If i delete that check out from <has_permission>, unauthenticated users do not have ReadOnly rights.

If i delet that check out from <has_object_permission>, authenticated users do not have Edit and Delete rights.

I'm sure there is beter way to customise this permissions. Isn't there?

Also i tryed to check if current user has object permission to images which related to publication for which user was author. This works but is there standard practice how to check permissions to related objects? I tryed to delete check

if request.user.id == Publication.objects.get(id=obj.image_to_pub_id).pub_author_id:

out of <ImagePermission> and combine both <ImagePermission> and <ImageAuthorPermission> classes in a <permission_classes> list. Used & and | operators, but did not get success.

Upvotes: 1

Views: 60

Answers (2)

Antoliny Lee
Antoliny Lee

Reputation: 571

I saw this part and implemented it.

  1. noauthenticated users can only read publication and images
  2. authenticated users can create publications and add images
  3. authorized users can edit and delete publications and images for which they are authors
  4. admin user can do all actions but edit content

First of all, the SAFE_METHOD request must be read only, and the action such as create can only be made to authenticated users.

For this part, use the IsAuthenticatedOrReadOnly class that DRF Permission provides as default.

rest_framework.permission

class IsAuthenticatedOrReadOnly(BasePermission):
    """
    The request is authenticated as a user, or is a read-only request.
    """

    def has_permission(self, request, view):
        return bool(
            request.method in SAFE_METHODS or
            request.user and
            request.user.is_authenticated
        )

To implement No. 3 and 4, only the owner can modify and delete it, and the administrator can only modify it("PUT", "PATCH").

from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission


class AdminObjectPermission(BasePermission):
  
  def has_object_permission(self, request, view, obj):
    if bool(request.user and request.user.is_staff) and request.method in ["PUT", "PATCH"]:
      return True


class PublicPermission(IsAuthenticatedOrReadOnly):
  
  def has_object_permission(self, request, view, obj):
    
    if request.user.id == obj.pub_author.id:
      return True
    
    return False


class ImagePermission(IsAuthenticatedOrReadOnly, AdminObjectPermission):
  
  def has_object_permission(self, request, view, obj):

    if request.user.id == obj.image_to_pub.pub_author.id:
      return True
    
    return False

For the Publication object, it is associated with the User with the pub_author field.

if request.user.id == obj.pub_author.id

Image objects were compared through pub_author on connected Publication objects.

if request.user.id == obj.image_to_pub.pub_author.id

views.py

class PublicationViewSet(viewsets.ModelViewSet):
    queryset = Publication.objects.all()
    serializer_class = PublicationSerializer
    authentication_classes = [YourAuthenticationClass]
    permission_classes = [PublicPermission|AdminObjectPermission]


class ImageViewSet(viewsets.ModelViewSet):
    queryset = Image.objects.all()
    serializer_class = ImageSerializer
    authentication_classes = [YourAuthenticationClass]
    permission_classes = [ImagePermission|AdminObjectPermission]

additionally

The permission check for the retrieve method failed.

I modified the source code to allow for this part.

from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission


class AdminObjectPermission(BasePermission):
  
  def has_object_permission(self, request, view, obj):

    if request.method in SAFE_METHODS:
      return True

    if bool(request.user and request.user.is_staff) and request.method in ["PUT", "PATCH"]:
      return True


class PublicPermission(IsAuthenticatedOrReadOnly):
  
  def has_object_permission(self, request, view, obj):
    
    if request.user.id == obj.pub_author.id:
      return True
    
    return False


class ImagePermission(IsAuthenticatedOrReadOnly, AdminObjectPermission):
  
  def has_object_permission(self, request, view, obj):

    if request.user.id == obj.image_to_pub.pub_author.id:
      return True
    
    return False

Upvotes: 1

aleksandr
aleksandr

Reputation: 17

This class works:

class PublicationPermission(BasePermission):

edit_methods = ['PUT', 'PATCH']

def has_permission(self, request, view):
    if request.method in SAFE_METHODS:
        return True
    if request.user.is_authenticated:
        return True

def has_object_permission(self, request, view, obj):
    if request.method in SAFE_METHODS:
        return True
    if request.user.id == obj.pub_author.id:
        return True
    if request.user.is_staff and request.method not in self.edit_methods:
        return True

has_permission checks if request is allowed or not based on user authorization and request methods. If request was allowed than object is getting from database and has_object_permission checks user's permissions to the object. My problem was that without this lines in has_object_permission:

        if request.method in SAFE_METHODS:
        return True

retrive method was not allowed, only list method was without authentication. Have no idea why. Probably retrive method needs permission on object level...

Upvotes: 0

Related Questions