8000 [Security] add an AbstractVoter implementation · symfony/symfony@d3bafc6 · GitHub
[go: up one dir, main page]

Skip to content

Commit d3bafc6

Browse files
inoryyfabpot
authored andcommitted
[Security] add an AbstractVoter implementation
1 parent 9752a76 commit d3bafc6

File tree

2 files changed

+203
-0
lines changed

2 files changed

+203
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Core\Authorization\Voter;
13+
14+
use Symfony\Component\Security\Core\User\UserInterface;
15+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16+
17+
/**
18+
* Abstract Voter implementation that reduces boilerplate code required to create a custom Voter
19+
*
20+
* @author Roman Marintšenko <inoryy@gmail.com>
21+
*/
22+
abstract class AbstractVoter implements VoterInterface
23+
{
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
public function supportsAttribute($attribute)
28+
{
29+
return in_array($attribute, $this->getSupportedAttributes());
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function supportsClass($class)
36+
{
37+
foreach ($this->getSupportedClasses() as $supportedClass) {
38+
if ($supportedClass === $class || is_subclass_of($class, $supportedClass)) {
39+
return true;
40+
}
41+
}
42+
43+
return false;
44+
}
45+
46+
/**
47+
* Iteratively check all given attributes by calling isGranted
48+
*
49+
* This method terminates as soon as it is able to return ACCESS_GRANTED
50+
* If at least one attribute is supported, but access not granted, then ACCESS_DENIED is returned
51+
* Otherwise it will return ACCESS_ABSTAIN
52+
*
53+
* @param TokenInterface $token A TokenInterface instance
54+
* @param object $object The object to secure
55+
* @param array $attributes An array of attributes associated with the method being invoked
56+
*
57+
* @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED
58+
*/
59+
public function vote(TokenInterface $token, $object, array $attributes)
60+
{
61+
if (!$object || !$this->supportsClass(get_class($object))) {
62+
return self::ACCESS_ABSTAIN;
63+
}
64+
65+
// abstain vote by default in case none of the attributes are supported
66+
$vote = self::ACCESS_ABSTAIN;
67+
68+
foreach ($attributes as $attribute) {
69+
if (!$this->supportsAttribute($attribute)) {
70+
continue;
71+
}
72+
73+
// as soon as at least one attribute is supported, default is to deny access
74+
$vote = self::ACCESS_DENIED;
75+
76+
if ($this->isGranted($attribute, $object, $token->getUser())) {
77+
// grant access as soon as at least one voter returns a positive response
78+
return self::ACCESS_GRANTED;
79+
}
80+
}
81+
82+
return $vote;
83+
}
84+
85+
/**
86+
* Return an array of supported classes. This will be called by supportsClass
87+
*
88+
* @return array an array of supported classes, i.e. ['\Acme\DemoBundle\Model\Product']
89+
*/
90+
abstract protected function getSupportedClasses();
91+
92+
/**
93+
* Return an array of supported attributes. This will be called by supportsAttribute
94+
*
95+
* @return array an array of supported attributes, i.e. ['CREATE', 'READ']
96+
*/
97+
abstract protected function getSupportedAttributes();
98+
99+
/**
100+
* Perform a single access check operation on a given attribute, object and (optionally) user
101+
* It is safe to assume that $attribute and $object's class pass supportsAttribute/supportsClass
102+
* $user can be one of the following:
103+
* a UserInterface object (fully authenticated user)
104+
* a string (anonymously authenticated user)
105+
*
106+
* @param string $attribute
107+
* @param object $object
108+
* @param UserInterface|string $user
109+
*
110+
* @return bool
111+
*/
112+
abstract protected function isGranted($attribute, $object, $user = null);
113+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Tests\Core\Authentication\Voter;
13+
14+
use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
15+
16+
/**
17+
* @author Roman Marintšenko <inoryy@gmail.com>
18+
*/
19+
class AbstractVoterTest extends \PHPUnit_Framework_TestCase
20+
{
21+
/**
22+
* @var AbstractVoter
23+
*/
24+
private $voter;
25+
26+
private $token;
27+
28+
public function setUp()
29+
{
30+
$this->voter = new VoterFixture();
31+
32+
$tokenMock = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
33+
$tokenMock
34+
->expects($this->any())
35+
->method('getUser')
36+
->will($this->returnValue('user'));
37+
38+
$this->token = $tokenMock;
39+
}
40+
41+
/**
42+
* @dataProvider getData
43+
*/
44+
public function testVote($expectedVote, $object, $attributes, $message)
45+
{
46+
$this->assertEquals($expectedVote, $this->voter->vote($this->token, $object, $attributes), $message);
47+
}
48+
49+
public function getData()
50+
{
51+
return array(
52+
array(AbstractVoter::ACCESS_ABSTAIN, null, array(), 'ACCESS_ABSTAIN for null objects'),
53+
array(AbstractVoter::ACCESS_ABSTAIN, new UnsupportedObjectFixture(), array(), 'ACCESS_ABSTAIN for objects with unsupported class'),
54+
array(AbstractVoter::ACCESS_ABSTAIN, new ObjectFixture(), array(), 'ACCESS_ABSTAIN for no attributes'),
55+
array(AbstractVoter::ACCESS_ABSTAIN, new ObjectFixture(), array('foobar'), 'ACCESS_ABSTAIN for unsupported attributes'),
56+
array(AbstractVoter::ACCESS_GRANTED, new ObjectFixture(), array('foo'), 'ACCESS_GRANTED if attribute grants access'),
57+
array(AbstractVoter::ACCESS_GRANTED, new ObjectFixture(), array('bar', 'foo'), 'ACCESS_GRANTED if *at least one* attribute grants access'),
58+
array(AbstractVoter::ACCESS_GRANTED, new ObjectFixture(), array('foobar', 'foo'), 'ACCESS_GRANTED if *at least one* attribute grants access'),
59+
array(AbstractVoter::ACCESS_DENIED, new ObjectFixture(), array('bar', 'baz'), 'ACCESS_DENIED for if no attribute grants access'),
60+
);
61+
}
62+
}
63+
64+
class VoterFixture extends AbstractVoter
65+
{
66+
protected function getSupportedClasses()
67+
{
68+
return array(
69+
'Symfony\Component\Security\Tests\Core\Authentication\Voter\ObjectFixture',
70+
);
71+
}
72+
73+
protected function getSupportedAttributes()
74+
{
75+
return array( 'foo', 'bar', 'baz');
76+
}
77+
78+
protected function isGranted($attribute, $object, $user = null)
79+
{
80+
return $attribute === 'foo';
81+
}
82+
}
83+
84+
class ObjectFixture
85+
{
86+
}
87+
88+
class UnsupportedObjectFixture
89+
{
90+
}

0 commit comments

Comments
 (0)
0