api-platform / core

The server component of API Platform: hypermedia and GraphQL APIs in minutes
https://api-platform.com
MIT License
2.44k stars 869 forks source link

GraphQL Query operation ignores custom ItemProvider #5805

Open bkosun opened 1 year ago

bkosun commented 1 year ago

API Platform version(s) affected: 3.1, 3.2

Description
GraphQL Query operation ignores custom ItemProvider if resource does not have Http Get operation

src/Entity/Account.php

namespace App\Entity;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GraphQl\Query;
use App\Repository\AccountRepository;
use App\State\AccountItemProvider;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Uid\Uuid;

#[ORM\Entity(repositoryClass: AccountRepository::class)]
#[ApiResource(
    operations: [],
    normalizationContext: ['groups' => ['account:read_normalization']],
)]
#[Get(provider:  AccountItemProvider::class)] //<--- !!!
#[Query(provider: AccountItemProvider::class)]
class Account
{
    #[ORM\Id]
    #[ORM\Column(type: UuidType::NAME, unique: true)]
    #[ApiProperty(identifier: true)]
    #[Groups(['account:read_normalization'])]
    private Uuid $id;

    #[ORM\Column(type: "json")]
    #[Groups(['account:read_normalization'])]
    private array $credentials = [];
}

src/State/AccountItemProvider.php

namespace App\State;

use ApiPlatform\Doctrine\Orm\State\ItemProvider;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

class AccountItemProvider implements ProviderInterface
{
    private ItemProvider $decoratedProvider;
    private AuthorizationCheckerInterface $authorizationChecker;

    public function __construct(ItemProvider $decoratedProvider, AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->decoratedProvider = $decoratedProvider;
        $this->authorizationChecker = $authorizationChecker;
    }

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?object
    {
        $provider = $this->decoratedProvider->provide($operation, $uriVariables, $context);

        if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
            $provider->setCredentials([]);
        }

        return $provider;
    }
}

For example, I have an entry with ID 018a7bb5-4215-7363-82cf-b54c804c138e and a property credentials that contains the following data:

[
    {
        "key": "1234567890"
    }
]

GraphQL query:

query getAccount{
  account(id: "/accounts/018a7bb5-4215-7363-82cf-b54c804c138e"){
      id
      credentials
  }
}

The result will be as follows:

{
  "data": {
    "account": {
      "id": "/accounts/018a7bb5-4215-7363-82cf-b54c804c138e",
      "credentials": []
    }
  }
}

However, if you remove the HTTP Get operation attribute, the ApiPlatform\Doctrine\Orm\State\ItemProvider provider will be used instead of App\State\AccountProvider, resulting in the following result:

{
  "data": {
    "account": {
      "id": "/accounts/018a7bb5-4215-7363-82cf-b54c804c138e",
      "credentials": [
        {
          "key": "1234567890"
        }
      ]
    }
  }
}

How to reproduce
Implement your own ItemProvider and configure a GraphQL Query operation without an Http Get operation

Possible Solution
...

Additional Context

Perhaps I have to use QueryItemRessolver?

use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;

class AccountItemResolver implements QueryItemResolverInterface
{
    private AuthorizationCheckerInterface $authorizationChecker;

    public function __construct(AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->authorizationChecker = $authorizationChecker;
    }

    public function __invoke($item, array $context): object
    {
        if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
            $item->setCredentials([]);
        }

        return $item;
    }
}
#[Query(resolver: AccountItemResolver::class)]

This works, but, note that there is no similar problem if I use the GraphQL QueryCollection operation with custom CollectionProvider without the HTTP GetCollection operation

soyuka commented 1 year ago

can you try API Platform 3.2 ?

bkosun commented 1 year ago

This issue affects versions 3.1 and 3.2. I have not tested earlier versions.

Unfortunately, there is not enough time right now to fully understand this issue, so I decided to create this bug report

I just tested the newest version v3.2.0-beta.1 - the bug is still present