Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security] add an AbstractVoter implementation
- Loading branch information
Showing
2 changed files
with
203 additions
and
0 deletions.
There are no files selected for viewing
113 changes: 113 additions & 0 deletions
113
src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* 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; | ||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||
|
||
/** | ||
* Abstract Voter implementation that reduces boilerplate code required to create a custom Voter | ||
* | ||
* @author Roman Marintšenko <inoryy@gmail.com> | ||
*/ | ||
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 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 | ||
* @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 (!$object || !$this->supportsClass(get_class($object))) { | ||
return self::ACCESS_ABSTAIN; | ||
} | ||
|
||
// abstain vote by default in case none of the attributes are supported | ||
$vote = self::ACCESS_ABSTAIN; | ||
|
||
foreach ($attributes as $attribute) { | ||
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 $vote; | ||
} | ||
|
||
/** | ||
* 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 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) | ||
* | ||
* @param string $attribute | ||
* @param object $object | ||
* @param UserInterface|string $user | ||
* | ||
* @return bool | ||
*/ | ||
abstract protected function isGranted($attribute, $object, $user = null); | ||
} |
90 changes: 90 additions & 0 deletions
90
src/Symfony/Component/Security/Tests/Core/Authentication/Voter/AbstractVoterTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* 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 <inoryy@gmail.com> | ||
*/ | ||
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 | ||
{ | ||
} |