Home > Mobile >  Best way in API-Platform for role based results
Best way in API-Platform for role based results

Time:08-12

I am using Symfony 5.4 and API-Platform 2.6 and would like to filter the returns based on user roles. It is about data created by different institutions. Each of these institutions is allowed to see all their own data, but not the data of the other institutions. But there is also a role (I call it administrator in the following) that is allowed to see all data, but in anonymized form. Here some fields are missing like for example the name. For data protection reasons it is necessary that the data is already filtered.

Now I am looking for the best way to implement this.

It would be nice if the routes do not have to provide the institution ID, but they are automatically added internally and respected on the server side.

For the administrator role I don't see a really good solution yet.

I am open for solutions, as well as alternatives.

Also please excuse my bad English.

CodePudding user response:

I see many questions in one question here ^^

Identify the institution of the connected user

You could add a relation user->institution, that's a simple solution and you'll be able to retrieve the user's institution from the connected user. From now how do you know if a user is part of an institution?

Filter the data per institution

To illustrate let's imagine you have a Product with a getter for each property:

  • id
  • name
  • user
  • institution

You could create a ApiPlatform extension, there is a good example that is similar to your usecase.

Example

class FilterByInstitutionExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
    public function __construct(private Security $security)
    {
    }

    /**
     * {@inheritdoc}
     */
    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
    {
        $this->addWhere($queryBuilder, $resourceClass, $queryNameGenerator);
    }

    /**
     * {@inheritdoc}
     */
    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = [])
    {
        $this->addWhere($queryBuilder, $resourceClass, $queryNameGenerator);
    }

    private function addWhere(QueryBuilder $queryBuilder, string $resourceClass, QueryNameGeneratorInterface $queryNameGenerator)
    {
        $toFilterClasses = [Product::class];

        if (!in_array($resourceClass, $toFilterClasses)) {
            return;
        }
        
        // user isn't connected or anonymous
        // UserRoles::ADMIN is pseudo code, replace by whatever your admin role is 
        if (null === $user =  $this->security->getUser() || $this->security->isGranted(UserRoles::ADMIN)) {
            return;
        }

        $institution = $user->getInstitution();
        $rootAlias = $queryBuilder->getRootAliases()[0];
        $queryBuilder->andWhere(sprintf('%s.institution = :institution', $rootAlias));
        $queryBuilder->setParameter('institution', $institution);
    }
}

Remove user-sensitive data for admin

In Symfony when you want to modify returned data from the server you generally use denormalizer, you should really check the documentation is pretty well explained. Basically, you want to create a denormalizer for each of your apiplatform resources that contains sensitive user data.

Your denormalizer could look like this. Of course, you'll need to tweak it, it's pseudo code right now :)

class AnonymizedUserDataNormalizer implements NormalizerInterface
{
    public function __construct(private NormalizerInterface $normalizer, private Security $security)
    {
    }

    public function normalize($entity, string $format = null, array $context = [])
    {
        $data = $this->normalizer->normalize($entity, $format, $context);
        if (!$this->security->isGranted(UserRoles::ADMIN)) {
            return $data;
        }

        if (isset($data['user'])) {
            unset($data['user']['firstName'], $data['user']['lastName']);
        }

        return $data;
    }

    public function supportsNormalization($data, string $format = null): bool
    {
        return $data instanceof Product;
    }
}

Then you need to register your service because you're using an ApiPlatform service.

App\Serializer\Normalizer\AnonymizedUserDataNormalizer:
        arguments:
            $normalizer: '@api_platform.serializer.normalizer.item'

All of this is not the only way to go, it's just a proposal to put you on the road. Feel free to ask if something is not clear :)

  • Related