[Security] add an AbstractVoter implementation
This commit is contained in:
parent
9752a7600a
commit
d3bafc6b48
@ -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);
|
||||||
|
}
|
@ -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
|
||||||
|
{
|
||||||
|
}
|
Reference in New Issue
Block a user