为什么它返回 204 而不是 403?自定义权限不起作用

问题描述

我有一个存储父-子记录的表。今天,我在编写单元测试时注意到一个问题。即使请求者不是孩子的父母,他们也可以删除记录。但是在视图方面,如果用户不是记录的所有者,则删除按钮是不活动的。所以它在视图端工作。如果我使用邮递员提出请求,则无论谁拥有该记录都会被删除

如何检查用户是否是记录的所有者?我正在关注一个示例项目,该项目的创建者已经获得了类似的许可。

子列表模型

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",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

子列表销毁视图

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

权限

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,view,obj):
        return (obj.parent == request.user.user_parent) or request.user.is_superuser
    message = AccountStrings.PermissionStrings.is_own_child_message

单元测试

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",email = "email3@example.com",identity_number = "12345678912",user_type = 3)
        self.user_child = User.objects.create_user(username="janedoe",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)

解决方法

我认为这是因为您覆盖了 get_object 方法。在 get_object 类的 GenericApiView 方法中,有以下内容:

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

所以您添加了新权限,但删除了权限检查机制。添加回这一行应该可以解决您的问题。

,

如何检查用户是否是记录的所有者? 您的 ChildList 类有一个指向配置文件的 pk。所以这里记录的onwer 是account.ParentProfile。你在 test_child_list_item_delete_is_own_child 中得到 203 而不是 403 的原因

是因为在视图 permission_classes 中您传递的是 IsAuthenticated,这意味着任何具有有效访问令牌的用户都可以与此视图交互。

因此解决方案是自定义删除并获得这样的响应

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

    def delete(self,*args,**kwargs): 
        pass 
        # business logic goes here 

    def get(self,**kwargs): 
        pass 
        # business logic goes here