如何解决ApiPlatform - 基于 apiplatform 过滤器实现授权
我正在使用 ApiPlatform
和 Symfony5
我在 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
对象时这样做,但似乎不可能)所以我可以分别对待它们在同一个开关盒中。
到目前为止我还没有找到实现它的方法
有没有办法实现这种规则?
或者有其他方式实现这种授权吗?
谢谢!
解决方法
如果您可以使用相同的请求 /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。
,如果您选择坚持要求具有 ROLE_USER 的用户使用 /users?expose=true 的用例,您可以创建一个自定义的 CollectionDataProvider 来抛出 FilterValidationException:
<?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,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 的用户可能会尝试通过 Exposure=false 进行过滤。顺便说一句,如果 $expose 可以为空,您需要测试将 $expose 设置为 null 的用户会发生什么
警告:请注意,如果属性 $expose 不再映射,或者属性 $expose 的名称已更改但在 UserCollectionDataProvider 中,则您的安全性将静默失败,允许所有用户访问所有用户实体它不是或过滤器规范不是!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。