【问题标题】:ApiPlatform - implement authorization based on apiplatform filtersApiPlatform - 基于 apiplatform 过滤器实现授权
【发布时间】:2021-04-10 04:41:09
【问题描述】:

我正在使用ApiPlatformSymfony5

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

用例:

  • 对于/users?expose=true 路由ROLE_USER 可以获得每个user 的列表,过滤器$expose 设置为true
  • 对于/users/ 路由ROLE_ADMIN 无论如何都可以获得每个user 的列表

这是我的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 属性传递的分页器对象,并确保请求结果中的对象是User 类型。 这部分已经过测试并且可以正常工作。

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

我想做的是告诉我的选民这两个请求是不同的(我认为我可以这样做,因为我恢复了 Paginator 对象但似乎不可能)所以我可以在相同的开关盒。

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

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

或者有没有其他方式来实现这种授权?

谢谢!

【问题讨论】:

    标签: symfony filter authorization api-platform.com symfony4-voter


    【解决方案1】:

    如果您选择坚持使用要求具有 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 中,您的安全性将静默失败,允许所有用户访问所有用户实体它不是或不是过滤器规范!

    【讨论】:

      【解决方案2】:

      如果您可以与普通用户和管理员用户一起使用相同的请求/users/ 但得到不同的结果, this docs page 描述了一种使 GET 收集操作的结果取决于登录用户的方法。我针对您的问题进行了调整:

      <?php
      // api/src/Doctrine/CurrentUserExtension.php
      
      namespace App\Doctrine;
      
      use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
      use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
      use App\Entity\Offer;
      use Doctrine\ORM\QueryBuilder;
      use Symfony\Component\Security\Core\Security;
      
      final class CurrentUserExtension implements QueryCollectionExtensionInterface
      {
          private $security;
      
          public function __construct(Security $security)
          {
              $this->security = $security;
          }
      
          public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null): void
          {
              if (User::class !== $resourceClass || $this->security->isGranted('ROLE_ADMIN')) {
                  return;
              }
      
              $rootAlias = $queryBuilder->getRootAliases()[0];
              $queryBuilder->andWhere("$rootAlias.expose = true");
          }
      }
      

      顺便说一句,任何没有 ROLE_ADMIN 的用户都会得到过滤结果,ROLE_USER 不是必需的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-04
        • 1970-01-01
        • 2022-11-10
        • 2023-02-17
        • 1970-01-01
        相关资源
        最近更新 更多