Martin Abraham Martin Abraham - 25 days ago 14
PHP Question

Why are symfony2 security voters always called?

I am using security voters as alternative to symfony's acl system.

example voter:



my voters look similar go the following one.

class FoobarVoter implements VoterInterface
{
public function supportsClass($class)
{
return in_array($class, array(
'Example\FoobarBundle\Entity\Foobar',
));
}

public function supportsAttribute($attribute)
{
return in_array(strtolower($attribute), array('foo', 'bar'));
}

public function vote(TokenInterface $token, $object, array $attributes)
{
$result = VoterInterface::ACCESS_ABSTAIN

if (!$this->supportsClass(get_class($object))) {
return VoterInterface::ACCESS_ABSTAIN;
}

foreach ($attributes as $attribute) {
$attribute = strtolower($attribute);

// skip not supported attributes
if (!$this->supportsAttribute($attribute)) {
continue;
}

[... some logic ...]
}

return $result;
}
}


questions:



reduce calls to Voter::vote()



my voters are included and called on every page load. even if they do not support decisions for a given class.
FoobarVoter::vote()
is always called. even if
FoobarVoter::supportsClass()
or
FoobarVoter::supportsAttribute
return false. thus i need to check class and attribute inside
FoobarVoter::vote()
. is this behaviour standard? how can i prevent this unnecessary call.

limit voters to bundles



some voters are only needed inside specific bundles. some are only needed to decide about specific classes. thus some voters are not needed in all parts of my application. is it possible to include voters per bundle/entity dynamically? e.g. only include voters into decision manager chain if a specific bundle or a specific entity is accessed/used?

Answer

Looking through the source code of Symfony, it appears to be because the AccessDecisionManager uses those methods (supportsClass and seupportsAttribute) to roll-up support to itself.

What this allows your voter to do is extend the cases when the manager will be applied. So you're not detailing the capability of your voter, but of the entire voting process. Whether or not that's what you want is something else...

As far as reducing the un-necessary calls, it's not un-necessary in the general case. The system is designed using one of three methods:

  1. Allow based (decideAffirmative). This uses an "allow based" voting. Which means that if one plugin says "allow" then you're allowed.

  2. Concensus Based (decideConsensus). This uses a concensus based permission, where if more voters agree to allow than to deny you're allowed...

  3. Deny Based (decideUnanimous). This uses a "deny based" voting. Which means that if one plugin says "deny", then you're denied. Otherwise you need at least one grant.

So considering that all of them rely on the explicit Deny vs Allow, running all of the plugins for every request actually makes sense. Because even if you don't specifically support a class, you may want to allow or deny that request.

In short, there's not much to gain by limiting the voters by the supports attributes.

Comments