Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d3bafc6

Browse filesBrowse files
inoryyfabpot
authored andcommitted
[Security] add an AbstractVoter implementation
1 parent 9752a76 commit d3bafc6
Copy full SHA for d3bafc6

File tree

Expand file treeCollapse file tree

2 files changed

+203
-0
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+203
-0
lines changed
+113Lines 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+
}
+90Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.