Search code examples
djangodjango-rest-frameworkdjango-permissions

Why is it returning 204 instead of 403? Custom permissions does not working


I have a table that stores parent - child records. Today, I noticed a problem while writing a unit test. Even if the requester is not the parent of the child, they can delete the record. But on the view side, if the user is not the owner of the record, the delete button is not active. So it works in the view side. If I make the request using postman, the record is deleted regardless of who owns it.

How can I check that whether the user is the owner of the record? I am following an example project and the creator of that project has done a similar permission.

Child List Model

class ChildList(models.Model):
    parent = models.ForeignKey(
        "account.ParentProfile",
        on_delete=models.CASCADE,
        related_name="parent_children",
        verbose_name=AccountStrings.ChildListString.parent_verbose_name)
    child = models.OneToOneField(
        "account.ChildProfile",
        on_delete=models.CASCADE,
        related_name="child_list",
        verbose_name=AccountStrings.ChildListString.child_verbose_name)

    def __str__(self):
        return f"{self.parent.user.first_name} {self.parent.user.last_name} - {self.child.user.first_name} {self.child.user.last_name}"

    class Meta:
        verbose_name = AccountStrings.ChildListString.meta_verbose_name
        verbose_name_plural = AccountStrings.ChildListString.meta_verbose_name_plural

Child List Destroy View

class ChildListItemDestroyAPIView(RetrieveDestroyAPIView):
    """
        Returns a destroy view by child id value.
    """
    queryset = ChildList.objects.all()
    serializer_class = ChildListSerializer
    permission_classes = [IsAuthenticated, IsParent, IsOwnChild]
    
    def get_object(self):
        queryset = self.get_queryset()
        obj = get_object_or_404(ChildList,child = self.kwargs["child_id"])
        return obj

Permission

class IsOwnChild(BasePermission):
    """
        To edit or destroy a child list record, the user must be owner of that record.
    """
    def has_permission(self, request, view):
        return request.user.user_parent and request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        return (obj.parent == request.user.user_parent) or request.user.is_superuser
    message = AccountStrings.PermissionStrings.is_own_child_message

Unit Test

class ChildListItemDestroyTests(APITestCase):
    login_url = reverse("token_obtain_pair")

    def setUp(self):
        self.username = "johndoe"
        self.password = "test1234"
        self.user_parent = User.objects.create_user(username=self.username, password=self.password, email = "email1@example.com", identity_number = "12345678910", user_type = 3)
        self.user_parent2 = User.objects.create_user(username= "davedoe", password=self.password, email = "email3@example.com", identity_number = "12345678912", user_type = 3)
        self.user_child = User.objects.create_user(username="janedoe", password=self.password, email = "email2@example.com", identity_number = "12345678911", user_type = 2)
        self.child_list_item = ChildList.objects.create(parent = self.user_parent.user_parent, child = self.user_child.user_child)
        self.test_jwt_authentication()

    def test_jwt_authentication(self, username="johndoe", password="test1234"):
        response = self.client.post(self.login_url, data={"username": username, "password": password})
        self.assertEqual(200, response.status_code)
        self.assertTrue("access" in json.loads(response.content))
        self.token = response.data["access"]
        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
  
    def test_child_list_item_delete(self):
        url = reverse("account:child_list_item_destroy", kwargs={"child_id": self.user_child.user_child.user_id})
        response = self.client.delete(url)
        self.assertEqual(204, response.status_code)
   
   
    def test_child_list_item_delete_is_own_child(self):
        self.test_jwt_authentication(username = "davedoe")
        url = reverse("account:child_list_item_destroy", kwargs={"child_id": self.user_child.user_child.user_id})
        response = self.client.delete(url)
        self.assertEqual(403, response.status_code)

Solution

  • I think it's because you are overriding the get_object method. In the get_object method from the GenericApiView class, there is the following:

    # May raise a permission denied
    self.check_object_permissions(self.request, obj)
    

    So you added new permissions, but removed the permissions check mechanism. Adding back this line should solve your problem.