为什么返回 204 而不是 403?自定义权限不起作用
Why is it returning 204 instead of 403? Custom permissions does not working
我有一个 table 存储 parent - child 条记录。今天,我在编写单元测试时注意到一个问题。即使请求者不是 child 的 parent,他们也可以删除该记录。但在视图方面,如果用户不是记录的所有者,则删除按钮不可用。所以它在视图方面有效。如果我使用 postman 发出请求,无论谁拥有它,记录都会被删除。
如何检查用户是否是记录的所有者?我正在关注一个示例项目,该项目的创建者已获得类似许可。
Child 列出型号
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 列出销毁视图
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, request, 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", 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)
我认为这是因为您重写了 get_object
方法。在 GenericApiView
class 的 get_object
方法中,有以下内容:
# May raise a permission denied
self.check_object_permissions(self.request, obj)
所以你添加了新的权限,但是删除了权限检查机制。重新添加此行应该可以解决您的问题。
如何检查用户是否是记录的所有者?
您的 ChildList class 有一个指向配置文件的 pk。所以这里的记录是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, *args, **kwargs):
pass
# business logic goes here
我有一个 table 存储 parent - child 条记录。今天,我在编写单元测试时注意到一个问题。即使请求者不是 child 的 parent,他们也可以删除该记录。但在视图方面,如果用户不是记录的所有者,则删除按钮不可用。所以它在视图方面有效。如果我使用 postman 发出请求,无论谁拥有它,记录都会被删除。
如何检查用户是否是记录的所有者?我正在关注一个示例项目,该项目的创建者已获得类似许可。
Child 列出型号
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 列出销毁视图
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, request, 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", 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)
我认为这是因为您重写了 get_object
方法。在 GenericApiView
class 的 get_object
方法中,有以下内容:
# May raise a permission denied
self.check_object_permissions(self.request, obj)
所以你添加了新的权限,但是删除了权限检查机制。重新添加此行应该可以解决您的问题。
如何检查用户是否是记录的所有者? 您的 ChildList class 有一个指向配置文件的 pk。所以这里的记录是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, *args, **kwargs):
pass
# business logic goes here