DRF post 请求测试失败,因为自定义权限声明“owner_id”字段(自定义字段)与经过身份验证的用户 ID 不匹配

问题描述 投票:0回答:1

我开始为我的端点“categories/”编写测试,但我无法通过已添加到正在测试的视图中的自定义权限。在此权限中,它会使用随 post 请求发送的有效负载中存在的“owner_id”字段来检查经过身份验证的用户的 ID。如果它们不匹配,则权限将拒绝对视图的访问。我编写此权限是为了阻止恶意请求(不是通过前端)向错误的用户添加类别。用户被视为使用 jwt 令牌进行了身份验证,该令牌是发布请求标头的一部分。理论上,Django 应该看到 jwt,从数据库获取经过身份验证的用户,然后在某个时候我的权限应该看到owner_id 字段等于检索到的用户的 id。也许我遗漏了创建测试用户和身份验证的工作原理?我可以使用 Postman 的经过身份验证的用户成功创建类别。如果您需要更多信息,请告诉我。

失败测试结果:

FAIL: test_create_category (categories.tests.test_categories_api.CategoryAPIViewTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/nwmus/units/backend/categories/tests/test_categories_api.py", line 35, in test_create_category
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 403 != 201

测试:

class CategoryAPIViewTests(APITestCase):
    categories_url = reverse("units_api:categories:category-list-create")

    def setUp(self):
        self.user = UnitsUser.objects.create_user(
            email="[email protected]", username="testuser", password="testpassword"
        )
        self.access_token = RefreshToken.for_user(self.user).access_token
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {self.access_token}")

    def test_create_category(self):
        self.client.force_authenticate(user=self.user)
        data = {
            "name": "test category",
            "description": "test description",
            "owner_id": self.user.id,
        }
        logger.debug(f"user: {self.user}")
        logger.debug(f"access_token: {self.access_token}")
        response = self.client.post(self.categories_url, data=data)
        logger.debug(f"data: {data}")
        logger.debug(f"response.data: {response.data}")
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data["name"], data["name"])
        self.assertEqual(response.data["description"], data["description"])
        self.assertEqual(response.data["owner_id"], self.user.id)

我已经注销了一些我认为测试用例中必要的数据点。

DEBUG user: id: 1, username: testuser, email: [email protected]
DEBUG access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzAyNjQ1NzczLCJpYXQiOjE3MDI2Mjc3NzMsImp0aSI6IjdjMWY5ZjdmYTI4NDRkMWJiMTEwYjg3ZjQxZjkxMDgzIiwidXNlcl9pZCI6MX0.7vwhvgRuclaVjJxWZ0nRLXgK4OI-fOg5G8AZwagFwY8
DEBUG data: {'name': 'test category', 'description': 'test description', 'owner_id': '1'}
DEBUG response.data: {'detail': ErrorDetail(string='owner_id field does not match authenticated user', code='permission_denied')}

解码后的access_token有效负载:

{
  "token_type": "access",
  "exp": 1702644373,
  "iat": 1702626373,
  "jti": "4f3fc61c5a174907923dd473dbb511d1",
  "user_id": 1
}

如您所见,有效负载字段“user_id”、用户 ID 和数据字段“owner_id”全部匹配。我不知道从这里该去哪里。也许我的许可有缺陷,有更好的方法吗?

许可:

class UserIsOwnerPermission(permissions.BasePermission):
    """Permission that checks if authenticated user is the owner of the object being requested or created"""

    def has_object_permission(self, request, view, obj):
        return obj.owner_id == request.user

    def has_permission(self, request, view):
        # Catch POST requests that have an owner_id that does not match the authenticated user
        if "owner_id" in request.data:
            self.message = "owner_id field does not match authenticated user"
            return request.user.id == request.data["owner_id"]
        return True

查看:

class CategoryListCreateView(UserIsOwnerMixin, generics.ListCreateAPIView):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    permission_classes = [UserIsOwnerPermission]

类别型号:

class Category(models.Model):
    name = models.CharField(max_length=50)
    description = models.TextField(blank=True, null=True)
    color_hexcode = models.CharField(max_length=7, blank=True, null=True)
    owner_id = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, null=True)
    created_at = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ("-created_at",)
        
    def __str__(self):
        return self.name

用户模型

class CustomUnitsUserManager(BaseUserManager):
    def create_user(self, email, username, password, **extra_fields):
        if not email:
            raise ValueError(_("The Email must be set"))

        if not username:
            raise ValueError(_("The Username must be set"))

        email = self.normalize_email(email)
        user = self.model(email=email, username=username, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, username, password, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        extra_fields.setdefault("is_active", True)

        if extra_fields.get("is_staff") is not True:
            raise ValueError(_("Superuser must have is_staff=True."))
        if extra_fields.get("is_superuser") is not True:
            raise ValueError(_("Superuser must have is_superuser=True."))

        return self.create_user(email, username, password, **extra_fields)


class UnitsUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_("email address"), unique=True)
    username = models.CharField(max_length=50, unique=True)
    first_name = models.CharField(max_length=50, blank=True)
    last_name = models.CharField(max_length=50, blank=True)
    date_joined = models.DateTimeField(default=timezone.now)
    about = models.TextField(_("about"), blank=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = CustomUnitsUserManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["username"]

    def __str__(self):
        return f"id: {self.id}, username: {self.username}, email: {self.email}"
django-rest-framework django-testing django-tests django-rest-framework-simplejwt django-rest-framework-permissions
1个回答
0
投票

看起来您正在尝试将字符串“1”与整数 1 匹配,所以这就是失败的原因

class UserIsOwnerPermission(permissions.BasePermission):
    """Permission that checks if authenticated user is the owner of the object being requested or created"""

    def has_object_permission(self, request, view, obj):
        return obj.owner_id == request.user.id # not related, but here you missed the .id

    def has_permission(self, request, view):
        # Catch POST requests that have an owner_id that does not match the authenticated user
        if "owner_id" in request.data:
            self.message = "owner_id field does not match authenticated user"
            return str(request.user.id) == str(request.data["owner_id"])
        return True

如果你的API应该使用整数,只需更改测试

    def test_create_category(self):
        self.client.force_authenticate(user=self.user)
        data = {
            "name": "test category",
            "description": "test description",
            "owner_id": self.user.id,
        }
© www.soinside.com 2019 - 2024. All rights reserved.