0
votes

I'm working with API Platform and I'm looking to make something. I made a Person Entity like this :

class Person
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $firstname;

    /**
     * @ORM\Column(type="datetime")
     */
    private $birthdate;
}

With defined groups for the fields :

App\Entity\Person:
  attributes:
    id:
      groups: ['private']
    name:
      groups: ['public']
    firstname:
      groups: ['public']
    birthdate:
      groups: ['public']

I also precised that if I want all the collection of that resource, only public fields should be serialized :

App\Entity\Person:
    collectionOperations:
      get:
        filters: ['search_filter']
        normalization_context:
          groups: ['public']
        formats: ['json']

As you can see, I applied a search filter. In that case I can retrieve resources from their fields precised as query parameters.

However, I want to apply this filter only with public fields. So I don't want that the http://localhost/api/people?id=1 request works, since the id field is private.

I see that it is possible to precise the fields wanted as arguments for SearchFilter, but it would be more useful to precise the group name instead, because I intend to work with more groups.

I tried to look in GroupFilters, but it doesn't help me, because it is a serializer filter...

What do you recommend me ?

1

1 Answers

1
votes

After a few hours of digging, I finally found my answer :

  • I created my own filter and it has a SearchFilter instance injected.
  • In order to compare fields'groups sent into the QueryParam, I had to extend my filter with the AbstractContextAwareFilter class.

  • I compare these groups with the resource / Entity metadata information provided by the ClassMetadataFactory class. I had to use the annotation syntax instead in order to write my groups instead of yaml otherwise they won't be detected.

    If a group is not in the normalization ones, I throw an exception, else I leave the SearchFilter to do the filter process.

Here is my work :

use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\QueryBuilder;
use http\Exception\RuntimeException;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;

/**
 * Class RestrictGroupFilter
 * @package App\Filters
 */
class RestrictGroupFilter extends AbstractContextAwareFilter
{
    /**
     * @var $decorated AbstractFilter
     */
    private $filter;
    private $metadataFactory;

    public function __construct(AbstractFilter $filter, ClassMetadataFactory $metadataFactory,ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null)
    {
        parent::__construct($managerRegistry, $requestStack, $logger, $properties);
        $this->filter = $filter;
        $this->metadataFactory = $metadataFactory;
    }

    protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
    {
        /**
         * @var $classMetadata ClassMetadata
         */
        $classMetadata = $this->metadataFactory->getMetadataFor($resourceClass); #retrieve of Entity's class's attribute metadata
        #prepare to check context's group with normalization ones
        foreach ($context["groups"] as $group)
        {
            if(!in_array($group,$classMetadata->attributesMetadata[$property]->getGroups())){ //if one group is not found in normalization groups

                throw new RuntimeException("$property's group denied." /*Groups authorized : ".implode(", ",$context["groups"])*/);
            }
        }
        //Filter is enabled if all is good
        $this->filter->filterProperty($property,$value,$queryBuilder,$queryNameGenerator,$resourceClass,$operationName);

    }

    public function getDescription(string $resourceClass): array
    {
        // TODO: Implement getDescription() method.
        return $this->filter->getDescription($resourceClass);
    }
}

For the services :

    search_filter:
        parent: 'api_platform.doctrine.orm.search_filter'
        tags:   ['api_platform.filter']
        autowire: false
        autoconfigure: false


    'App\Filters\RestrictGroupFilter':
        arguments: [ '@search_filter','@serializer.mapping.class_metadata_factory']