首页 > 解决方案 > ApiPlatform - 基于 apiplatform 过滤器实现授权

问题描述

我正在使用ApiPlatformSymfony5

我在实体上放置了一个过滤器,User以按名为的类的布尔值对它们进行排序$expose

用例:

这是我的User课:


/**
 * @ApiResource(
 *     attributes={
 *          "normalization_context"={"groups"={"user:read", "user:list"}},
 *          "order"={"somefield.value": "ASC"}
 *     },
 *     collectionOperations={
 *          "get"={
 *              "mehtod"="GET",
 *              "security"="is_granted('LIST', object)",
 *              "normalization_context"={"groups"={"user:list"}},
 *          }
 *     }
 * )
 * @ApiFilter(ExistsFilter::class, properties={"expose"})
 * @ApiFilter(SearchFilter::class, properties={
 *     "somefield.name": "exact"
 * })
 * @ORM\Entity(repositoryClass=UserRepository::class)
 */

我通过以下方式实施我的授权规则UserVoter

protected function supports($attribute, $subject): bool
    {
        return parent::supports($attribute, $subject) &&
            ($subject instanceof User ||
                $this->arrayOf($subject, User::class)  ||
                (is_a($subject, Paginator::class) &&
                    $this->arrayOf($subject->getQuery()->getResult(), User::class))
            );
    }

    protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
    {
        /** @var User $user */
        $user = $token->getUser();

        if (!$user instanceof User) {
            return false;
        }

        if ($this->accessDecisionManager->decide($token, [GenericRoles::ROLE_ADMIN])) {
            return true;
        }

        switch ($attribute) {
            case Actions::LIST:
                break;
        }
        return false;
    }

为了恢复列表, User我恢复了通过LIST属性传递的分页器对象,并确保请求结果中的对象是 type User。这部分已经过测试并且可以正常工作。

现在我的问题来自这样一个事实,即这两条路线与 my 基本相同voter,因此我通过它实施的授权规则适用于它们。

我想做的是告诉我的选民这两个请求是不同的(我认为我可以在恢复Paginator对象时这样做,但似乎不可能)所以我可以在同一个开关盒中分别处理它们。

到目前为止,我还没有找到实现它的方法

有没有办法实现这种规则?

还是有另一种方式来实现这种授权?

谢谢!

标签: symfonyfilterauthorizationapi-platform.comsymfony4-voter

解决方案


如果您选择坚持使用要求具有 ROLE_USER 的用户使用 /users?expose=true 的用例,您可以创建一个引发 FilterValidationException 的自定义 CollectionDataProvider:

<?php

namespace App\DataProvider;

use Symfony\Component\Security\Core\Security;
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use ApiPlatform\Core\Exception\FilterValidationException;
use App\Entity\User;

class UserCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
{
    /** @var CollectionDataProviderInterface */
    private $dataProvider;

    private $security;

    /**
     * @param CollectionDataProviderInterface $dataProvider The built-in orm CollectionDataProvider of API Platform
     */
    public function __construct(CollectionDataProviderInterface $dataProvider, Security $security)
    {
        $this->dataProvider = $dataProvider;
        $this->security = $security;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
    {
        return User::class === $resourceClass;
    }

    /** throws FilterValidationException */
    private function validateFilters($context)
    {
    if ($this->security->isGranted('ROLE_ADMIN')) {
            // Allow any filters, including no filters
            return;
        }
        if (!$this->security->isGranted('ROLE_USER')) {
            throw new \LogicException('No use case has been defined for this situation');
        }

        $errorList = [];
        if (!isset($context["filters"]["expose"]) ||
            $context["filters"]["expose"] !== "true" && $context["filters"]["expose"] !== '1'
        ) {
            $errorList[] = 'expose=true filter is required.'
            throw new FilterValidationException($errorList);
        }
    }

    /**
     * {@inheritdoc}
     * @throws FilterValidationException;
     */
    public function getCollection(string $resourceClass, string $operationName = null, array $context = []): array
    {
        $this->validateFilters($context);

        return $this->dataProvider->getCollection($resourceClass, $operationName, $context);
    }

您确实需要将以下内容添加到 api/config/services.yaml:

'App\DataProvider\UserCollectionDataProvider':
    arguments:
        $dataProvider: '@api_platform.doctrine.orm.default.collection_data_provider'

顺便说一句,要通过布尔值进行过滤,通常使用 BooleanFilter:

 * @ApiFilter(BooleanFilter::class, properties={"expose"})

这是相关的,因为具有 ROLE_ADMIN 的用户可能会尝试通过 Expose=false 进行过滤。顺便说一句,如果 $expose 可以为空,您需要测试将 $expose 设置为 null 的用户会发生什么

警告:请注意,如果属性 $expose 不再被映射或属性 $expose 的名称已更改但在 UserCollectionDataProvider 中不是或过滤器中,您的安全性将静默失败,允许所有用户访问所有用户实体规范不是!


推荐阅读