0

I want to write a custom permission to restrict access to the display picture of a user. My user profile model is called Member and the implementation is as follows:

# imports
class Member(models.Model):
  created_at = models.DateTimeField(auto_now_add=True)

  user = models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, unique=True)
  sex = models.IntegerField(choices=[(0, 'Unknown'), (1, 'Male'), (2, 'Female'), (9, 'Not Applicable')])
  date_of_birth = models.DateField()

  bio = models.TextField(null=True)

  def __str__(self) -> str:
    return self.user.username


def _display_picture_upload_path(instance, filename: str):
  return f'members/display/{instance.member}.jpg'


class MemberDisplayPicture(models.Model):
  created_at = models.DateTimeField(auto_now_add=True)

  image = models.ImageField(upload_to=_display_picture_upload_path)
  member = models.OneToOneField(to=Member, on_delete=models.CASCADE, related_name='display_picture', unique=True)

The serializer for MemberDisplayPicture:

class MemberDisplayPictureSerializer(serializers.ModelSerializer):
  class Meta:
    model = MemberDisplayPicture
    fields = ['id', 'image']

  def create(self, validated_data):
    member_id = self.context['member_id']
    instance = MemberDisplayPicture.objects.create(member_id=member_id, **validated_data)
    return instance

A view at /{app_name}/members/{pk}/display-picture/ allows to retrieve, create and delete a display picture:

class MemberDisplayPictureAPI(RetrieveModelMixin, CreateModelMixin, DestroyModelMixin, GenericAPIView):
  http_method_names = ['get', 'post', 'delete']

  serializer_class = MemberDisplayPictureSerializer

  def get_member_id(self):
    member_id = self.kwargs['pk']
    return member_id

  def get_queryset(self):
    return MemberDisplayPicture.objects.filter(member_id=self.get_member_id())

  def get_object(self):
    queryset = self.filter_queryset(queryset=self.get_queryset())
    obj = get_object_or_404(queryset)
    self.check_object_permissions(self.request, obj)
    return obj

  def get_serializer_context(self):
    return {'member_id': self.get_member_id()}

  def get(self, request, *args, **kwargs):
    return self.retrieve(request, *args, **kwargs)

  def post(self, request, *args, **kwargs):
    return self.create(request, *args, **kwargs)

  def delete(self, request, *args, **kwargs):
    return self.destroy(request, *args, **kwargs)

The custom permission should:

  1. Only allow authenticated users
  2. Allow anyone to retrieve a display picture
  3. Allow logged in user to create or delete their own profile picture only
  4. Allow superuser to retrieve, create or delete any profile picture

How can I write and implement these permission/s following the best practices

Ahmed Mustafa
  • 119
  • 1
  • 11

1 Answers1

0

You can create your own Permission Class for this use case and for allowing only authenticated users, you can use the IsAuthenticated class in authentication classes.

In your folder, you can create permission.py as follows:

from rest_framework.permissions import BasePermission
from models import Member, MemberDisplayPicture

class ProfilePicturePermission(BasePermission):
    message = "Access Denied!"
    def has_permission(self, request, view):
        if request.type in ['GET', 'POST', 'DELETE']:
            return True
        return False
        

    def has_object_permission(self, request, view, obj):
        if request.type == 'POST':
            if request.user.is_superuser:
                return True
            if request.user == obj.member:
                return True
            return False
         if request.type == 'DELETE':
            if request.user.is_superuser:
                return True
            if request.user == obj.member:
                return True
            return False
         if request.type == 'GET':
             return True
         return False

In your views you can use it as follows:

from app.permissions import ProfilePicturePermission
from rest_framework.permissions import IsAuthenticated

class MemberDisplayPictureAPI(RetrieveModelMixin, CreateModelMixin, 

DestroyModelMixin, GenericAPIView):
  http_method_names = ['get', 'post', 'delete']

  serializer_class = MemberDisplayPictureSerializer
  permission_classes = [ProfilePicturePermission]
  authentication_classes = [IsAuthenticated]

  def get_member_id(self):
    member_id = self.kwargs['pk']
    return member_id

  def get_queryset(self):
    return MemberDisplayPicture.objects.filter(member_id=self.get_member_id())

  def get_object(self):
    queryset = self.filter_queryset(queryset=self.get_queryset())
    obj = get_object_or_404(queryset)
    self.check_object_permissions(self.request, obj)
    return obj

  def get_serializer_context(self):
    return {'member_id': self.get_member_id()}

  def get(self, request, *args, **kwargs):
    return self.retrieve(request, *args, **kwargs)

  def post(self, request, *args, **kwargs):
    return self.create(request, *args, **kwargs)

  def delete(self, request, *args, **kwargs):
    return self.destroy(request, *args, **kwargs)

I hope this solves your query

SimbaOG
  • 460
  • 2
  • 8
  • Thank you for the answer! As per my understanding, `get_object_permissions` is not called in a POST request so what can be done for that. – Ahmed Mustafa Feb 25 '23 at 12:20
  • It gets called in POST request. It is only called if has_permission checks are passed – SimbaOG Feb 25 '23 at 18:16