From 4a7bfe2e786335b9546ace72e4396d3785b3da1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Marint=C5=A1enko?= Date: Fri, 20 Jun 2014 14:24:18 +0300 Subject: [PATCH 1/3] add abstract voter implementation, reducing boilerplate required for custom voter --- .../Authorization/Voter/AbstractVoter.php | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php new file mode 100644 index 0000000000000..0c16ca7c6d922 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php @@ -0,0 +1,93 @@ + + */ +abstract class AbstractVoter implements VoterInterface +{ + /** + * {@inheritdoc} + */ + public function supportsAttribute($attribute) + { + return in_array($attribute, $this->getSupportedAttributes()); + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class) + { + foreach ($this->getSupportedClasses() as $supportedClass) { + if ($supportedClass === $class || is_subclass_of($class, $supportedClass)) { + return true; + } + } + + return false; + } + + /** + * Iteratively check all given attributes by calling voteOnAttribute + * This method terminates as soon as it is able to return either ACCESS_GRANTED or ACCESS_DENIED vote + * Otherwise it will return ACCESS_ABSTAIN + * + * @param TokenInterface $token A TokenInterface instance + * @param object $object The object to secure + * @param array $attributes An array of attributes associated with the method being invoked + * + * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED + */ + public function vote(TokenInterface $token, $object, array $attributes) + { + if (!$this->supportsClass(get_class($object))) { + return VoterInterface::ACCESS_ABSTAIN; + } + + $user = $token->getUser(); + + foreach ($attributes as $attribute) { + if ($this->supportsAttribute($attribute)) { + $vote = $this->voteOnAttribute($attribute, $object, $user); + if (VoterInterface::ACCESS_ABSTAIN !== $vote) { + return $vote; + } + } + } + + return VoterInterface::ACCESS_ABSTAIN; + } + + /** + * Return an array of supported classes. This will be called by supportsClass + * + * @return array an array of supported classes, i.e. ['\Acme\DemoBundle\Model\Product'] + */ + abstract protected function getSupportedClasses(); + + /** + * Return an array of supported attributes. This will be called by supportsAttribute + * + * @return array an array of supported attributes, i.e. ['CREATE', 'READ'] + */ + abstract protected function getSupportedAttributes(); + + /** + * Perform a single vote operation on a given attribute, object and (optionally) user + * It is safe to assume that $attribute and $object's class pass supportsAttribute/supportsClass + * + * @param string $attribute + * @param object $object + * @param UserInterface $user + * + * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED + */ + abstract protected function voteOnAttribute($attribute, $object, UserInterface $user = null); +} \ No newline at end of file From 8c774e85cff33f534e00a9dd377ea69ab570ba41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Marint=C5=A1enko?= Date: Sat, 23 Aug 2014 09:32:23 +0300 Subject: [PATCH 2/3] update AbstractVoter: vote returns as soon as access is granted; improve/update API based on PR comments --- .../Authorization/Voter/AbstractVoter.php | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php index 0c16ca7c6d922..39c52f4d06929 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php @@ -35,8 +35,10 @@ public function supportsClass($class) } /** - * Iteratively check all given attributes by calling voteOnAttribute - * This method terminates as soon as it is able to return either ACCESS_GRANTED or ACCESS_DENIED vote + * Iteratively check all given attributes by calling isGranted + * + * This method terminates as soon as it is able to return ACCESS_GRANTED + * If at least one attribute is supported, but access not granted, then ACCESS_DENIED is returned * Otherwise it will return ACCESS_ABSTAIN * * @param TokenInterface $token A TokenInterface instance @@ -47,22 +49,28 @@ public function supportsClass($class) */ public function vote(TokenInterface $token, $object, array $attributes) { - if (!$this->supportsClass(get_class($object))) { - return VoterInterface::ACCESS_ABSTAIN; + if (!$object || !$this->supportsClass(get_class($object))) { + return self::ACCESS_ABSTAIN; } - $user = $token->getUser(); + // abstain vote by default in case none of the attributes are supported + $vote = self::ACCESS_ABSTAIN; foreach ($attributes as $attribute) { - if ($this->supportsAttribute($attribute)) { - $vote = $this->voteOnAttribute($attribute, $object, $user); - if (VoterInterface::ACCESS_ABSTAIN !== $vote) { - return $vote; - } + if (!$this->supportsAttribute($attribute)) { + continue; + } + + // as soon as at least one attribute is supported, default is to deny access + $vote = self::ACCESS_DENIED; + + if ($this->isGranted($attribute, $object, $token->getUser())) { + // grant access as soon as at least one voter returns a positive response + return self::ACCESS_GRANTED; } } - return VoterInterface::ACCESS_ABSTAIN; + return $vote; } /** @@ -80,14 +88,18 @@ abstract protected function getSupportedClasses(); abstract protected function getSupportedAttributes(); /** - * Perform a single vote operation on a given attribute, object and (optionally) user + * Perform a single access check operation on a given attribute, object and (optionally) user * It is safe to assume that $attribute and $object's class pass supportsAttribute/supportsClass + * $user can be one of the following: + * a UserInterface object (fully authenticated user) + * a string (anonymously authenticated user) + * null (non-authenticated user) * - * @param string $attribute - * @param object $object - * @param UserInterface $user + * @param string $attribute + * @param object $object + * @param UserInterface|string|null $user * - * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED + * @return bool */ - abstract protected function voteOnAttribute($attribute, $object, UserInterface $user = null); -} \ No newline at end of file + abstract protected function isGranted($attribute, $object, $user = null); +} From 33413fc8e506a2c9385f17c86dee31801d461cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Marint=C5=A1enko?= Date: Sat, 23 Aug 2014 10:04:44 +0300 Subject: [PATCH 3/3] add AbstractVoter tests; add licensing information --- .../Authorization/Voter/AbstractVoter.php | 16 +++- .../Voter/AbstractVoterTest.php | 90 +++++++++++++++++++ 2 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Security/Tests/Core/Authentication/Voter/AbstractVoterTest.php diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php index 39c52f4d06929..61c928e80c2e1 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Security\Core\Authorization\Voter; use Symfony\Component\Security\Core\User\UserInterface; @@ -93,11 +102,10 @@ abstract protected function getSupportedAttributes(); * $user can be one of the following: * a UserInterface object (fully authenticated user) * a string (anonymously authenticated user) - * null (non-authenticated user) * - * @param string $attribute - * @param object $object - * @param UserInterface|string|null $user + * @param string $attribute + * @param object $object + * @param UserInterface|string $user * * @return bool */ diff --git a/src/Symfony/Component/Security/Tests/Core/Authentication/Voter/AbstractVoterTest.php b/src/Symfony/Component/Security/Tests/Core/Authentication/Voter/AbstractVoterTest.php new file mode 100644 index 0000000000000..955b610d2f382 --- /dev/null +++ b/src/Symfony/Component/Security/Tests/Core/Authentication/Voter/AbstractVoterTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Tests\Core\Authentication\Voter; + +use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; + +/** + * @author Roman Marintšenko + */ +class AbstractVoterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var AbstractVoter + */ + private $voter; + + private $token; + + public function setUp() + { + $this->voter = new VoterFixture(); + + $tokenMock = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + $tokenMock + ->expects($this->any()) + ->method('getUser') + ->will($this->returnValue('user')); + + $this->token = $tokenMock; + } + + /** + * @dataProvider getData + */ + public function testVote($expectedVote, $object, $attributes, $message) + { + $this->assertEquals($expectedVote, $this->voter->vote($this->token, $object, $attributes), $message); + } + + public function getData() + { + return array( + array(AbstractVoter::ACCESS_ABSTAIN, null, array(), 'ACCESS_ABSTAIN for null objects'), + array(AbstractVoter::ACCESS_ABSTAIN, new UnsupportedObjectFixture(), array(), 'ACCESS_ABSTAIN for objects with unsupported class'), + array(AbstractVoter::ACCESS_ABSTAIN, new ObjectFixture(), array(), 'ACCESS_ABSTAIN for no attributes'), + array(AbstractVoter::ACCESS_ABSTAIN, new ObjectFixture(), array('foobar'), 'ACCESS_ABSTAIN for unsupported attributes'), + array(AbstractVoter::ACCESS_GRANTED, new ObjectFixture(), array('foo'), 'ACCESS_GRANTED if attribute grants access'), + array(AbstractVoter::ACCESS_GRANTED, new ObjectFixture(), array('bar', 'foo'), 'ACCESS_GRANTED if *at least one* attribute grants access'), + array(AbstractVoter::ACCESS_GRANTED, new ObjectFixture(), array('foobar', 'foo'), 'ACCESS_GRANTED if *at least one* attribute grants access'), + array(AbstractVoter::ACCESS_DENIED, new ObjectFixture(), array('bar', 'baz'), 'ACCESS_DENIED for if no attribute grants access'), + ); + } +} + +class VoterFixture extends AbstractVoter +{ + protected function getSupportedClasses() + { + return array( + 'Symfony\Component\Security\Tests\Core\Authentication\Voter\ObjectFixture', + ); + } + + protected function getSupportedAttributes() + { + return array( 'foo', 'bar', 'baz'); + } + + protected function isGranted($attribute, $object, $user = null) + { + return $attribute === 'foo'; + } +} + +class ObjectFixture +{ +} + +class UnsupportedObjectFixture +{ +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy