0

In my Symfony application I am using Voters for RBAC implementation.

I am using it within API platform. For now I managed to make it work both on Collections and Item operations.

The thing that concerns me is the code repetition. Could this be a bad practise? It is not bad to mention is that I am using DDD (Domain Driven Design). I have a lot of entities that needs to be covered by Voters and I read that using one Voter for multiple entities is NOT recommended (resource).

As I am following that advice I am questioning my approach as I have many custom voter classes that have the same code. The main difference is on the entity subject.

I will post an example and welcome all advices as I am on a breaking point where I do not know what is the best way to continue.

Thanks!

class FirstEntityVoter extends Voter
{
private CustomRepository $customRepository;

public function __construct (CustomRepository $customRepository) {
    $this->customRepository = $customRepository;
}

protected function supports(
    string $attribute,
           $subject
): bool
{
    // If the subject is a string check if class exists to support collectionOperations
    if(is_string($subject) && class_exists($subject)) {
        $subject = new $subject;
    }

    $supportsAttribute = in_array($attribute, ActionEnum::ACTION_LIST);
    $supportsSubject = $subject instanceof FirstEntity;

    return $supportsAttribute && $supportsSubject;
}

/**
 * @throws Exception
 */
protected function voteOnAttribute(
    string $attribute,
           $subject,
    TokenInterface $token
): bool
{
    $user = $token->getUser();
    if (!$user instanceof UserInterface) {
        return false;
    }

    return match ($attribute) {
        ActionEnum::READ   => $this->canRead($user),
        ActionEnum::CREATE => $this->canCreate($user),
        ActionEnum::EDIT   => $this->canEdit($user),
        ActionEnum::DELETE => $this->canDelete($user),
        default            => throw new Exception(sprintf('Unhandled attribute "%s"', $attribute))
    };
}

private function canRead(User $user): bool
{
    return $this->customRepository->hasRole(
        $user->getId(),
        ActionEnum::READ
    );
}

private function canCreate(User $user): bool
{
   // same as canRead()
}

private function canEdit(User $user): bool
{
    // same as canRead()
}

private function canDelete(User $user): bool
{
   // same as canRead()
}

Defined in xml files for api platfom config like:

 <itemOperation name="get">
      <attribute name="method">GET</attribute>
      <attribute name="security">is_granted('read', object)</attribute>
  </itemOperation>
yaraw69
  • 43
  • 5
  • The answer in your link does say that it's not recommended to use a voter for muliples entities. It says the entities supported by the voter must have something in common (use of interfaces or trait), and I think it's a good recommendation. In your exemple you do nothing with the subject, you don't really need a voter you can just use the roles. If you have a custom role system that is not compliant with symfony security and you need to load the roles on the fly, yes I think your voter can be used for as many entities as you want as long as there's no entity specific logic in it. – jona303 Jun 23 '22 at 11:30
  • Thank you for clarification. I have implemented a Trait that will have this reusable logic canRead(), canEdit() ... About the usage of subject, I have ability table related to specific roles where I need to have match User -> has Role -> has ability -> to create/edit subject. I will use subject but did not figure out the part of 'How' in my case. @jona303 – yaraw69 Jun 24 '22 at 01:24

0 Answers0