Skip to content

Commit 968cb65

Browse files
inoryyweaverryan
authored andcommitted
add & update doc entries on AbstractVoter implementation
1 parent ae27dd1 commit 968cb65

File tree

3 files changed

+101
-78
lines changed

3 files changed

+101
-78
lines changed
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.. code-block:: php
2+
3+
abstract class AbstractVoter implements VoterInterface
4+
{
5+
public function supportsAttribute($attribute);
6+
public function supportsClass($class);
7+
public function vote(TokenInterface $token, $object, array $attributes);
8+
9+
abstract protected function getSupportedClasses();
10+
abstract protected function getSupportedAttributes();
11+
abstract protected function isGranted($attribute, $object, $user = null);
12+
}
13+
14+
Behind the scenes this class implements the
15+
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`,
16+
which has this structure:
17+
18+
.. include:: /cookbook/security/voter_interface.rst.inc
19+
20+
The basic functionality covering common use cases is provided
21+
and end developer is expected to implement the abstract methods.
22+
23+
The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedClasses`
24+
method is used to provide an array of supported classes, i.e. ['\Acme\DemoBundle\Model\Product']
25+
26+
The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedAttributes`
27+
method is used to provide an array of supported attributes, i.e. ['CREATE', 'READ']
28+
29+
The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::isGranted`
30+
method must implement the business logic that verifies whether or not a given
31+
user is allowed a given attribute on a given object. This method must return a boolean.

cookbook/security/voters.rst

+7
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ the security layer. This can be done easily through the service container.
9292
methods in your implementation of the ``vote()`` method and return ``ACCESS_ABSTAIN``
9393
if your voter does not support the class or attribute.
9494

95+
96+
.. tip::
97+
98+
An
99+
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter`
100+
is provided to cover the common use cases when implementing security voters.
101+
95102
Declaring the Voter as a Service
96103
--------------------------------
97104

cookbook/security/voters_data_permission.rst

+63-78
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ How Symfony Uses Voters
2525

2626
In order to use voters, you have to understand how Symfony works with them.
2727
All voters are called each time you use the ``isGranted()`` method on Symfony's
28-
authorization checker (i.e. the ``security.authorization_checker`` service). Each
28+
authorization checker (i.e. the ``security.authorization_checker`` service). Each
2929
one decides if the current user should have access to some resource.
3030

3131
Ultimately, Symfony uses one of three different approaches on what to do
@@ -37,11 +37,19 @@ For more information take a look at
3737
The Voter Interface
3838
-------------------
3939

40-
A custom voter must implement
41-
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`,
42-
which has this structure:
40+
A custom voter needs to implement
41+
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`
42+
or extend :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter`,
43+
which makes creating a voter even easier.
4344

44-
.. include:: /cookbook/security/voter_interface.rst.inc
45+
.. code-block:: php
46+
47+
abstract class AbstractVoter implements VoterInterface
48+
{
49+
abstract protected function getSupportedClasses();
50+
abstract protected function getSupportedAttributes();
51+
abstract protected function isGranted($attribute, $object, $user = null);
52+
}
4553
4654
In this example, the voter will check if the user has access to a specific
4755
object according to your custom conditions (e.g. they must be the owner of
@@ -61,90 +69,74 @@ edit a particular object. Here's an example implementation:
6169
// src/AppBundle/Security/Authorization/Voter/PostVoter.php
6270
namespace AppBundle\Security\Authorization\Voter;
6371
64-
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
65-
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
72+
use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
6673
use Symfony\Component\Security\Core\User\UserInterface;
6774
68-
class PostVoter implements VoterInterface
75+
class PostVoter extends AbstractVoter
6976
{
7077
const VIEW = 'view';
7178
const EDIT = 'edit';
7279
73-
public function supportsAttribute($attribute)
80+
protected function getSupportedAttributes()
7481
{
75-
return in_array($attribute, array(
76-
self::VIEW,
77-
self::EDIT,
78-
));
82+
return array(self::VIEW, self::EDIT);
7983
}
8084
81-
public function supportsClass($class)
85+
protected function getSupportedClasses()
8286
{
83-
$supportedClass = 'AppBundle\Entity\Post';
84-
85-
return $supportedClass === $class || is_subclass_of($class, $supportedClass);
87+
return array('AppBundle\Entity\Post');
8688
}
8789
88-
/**
89-
* @var \AppBundle\Entity\Post $post
90-
*/
91-
public function vote(TokenInterface $token, $post, array $attributes)
90+
protected function isGranted($attribute, $post, $user = null)
9291
{
93-
// check if class of this object is supported by this voter
94-
if (!$this->supportsClass(get_class($post))) {
95-
return VoterInterface::ACCESS_ABSTAIN;
96-
}
97-
98-
// check if the voter is used correct, only allow one attribute
99-
// this isn't a requirement, it's just one easy way for you to
100-
// design your voter
101-
if (1 !== count($attributes)) {
102-
throw new \InvalidArgumentException(
103-
'Only one attribute is allowed for VIEW or EDIT'
104-
);
105-
}
106-
107-
// set the attribute to check against
108-
$attribute = $attributes[0];
109-
110-
// check if the given attribute is covered by this voter
111-
if (!$this->supportsAttribute($attribute)) {
112-
return VoterInterface::ACCESS_ABSTAIN;
113-
}
114-
115-
// get current logged in user
116-
$user = $token->getUser();
117-
11892
// make sure there is a user object (i.e. that the user is logged in)
11993
if (!$user instanceof UserInterface) {
120-
return VoterInterface::ACCESS_DENIED;
94+
return false;
95+
}
96+
97+
// the data object could have for example a method isPrivate()
98+
// which checks the Boolean attribute $private
99+
if ($attribute == self::VIEW && !$post->isPrivate()) {
100+
return true;
121101
}
122102
123-
switch($attribute) {
124-
case self::VIEW:
125-
// the data object could have for example a method isPrivate()
126-
// which checks the boolean attribute $private
127-
if (!$post->isPrivate()) {
128-
return VoterInterface::ACCESS_GRANTED;
129-
}
130-
break;
131-
132-
case self::EDIT:
133-
// we assume that our data object has a method getOwner() to
134-
// get the current owner user entity for this data object
135-
if ($user->getId() === $post->getOwner()->getId()) {
136-
return VoterInterface::ACCESS_GRANTED;
137-
}
138-
break;
103+
// we assume that our data object has a method getOwner() to
104+
// get the current owner user entity for this data object
105+
if ($attribute == self::EDIT && $user->getId() === $post->getOwner()->getId()) {
106+
return true;
139107
}
140108
141-
return VoterInterface::ACCESS_DENIED;
109+
return false;
142110
}
143111
}
144112
145113
That's it! The voter is done. The next step is to inject the voter into
146114
the security layer.
147115

116+
To recap, here's what's expected from the three abstract methods:
117+
118+
:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedClasses`
119+
It tells Symfony that your voter should be called whenever an object of one
120+
of the given classes is passed to ``isGranted()`` For example, if you return
121+
``array('AppBundle\Model\Product')``, Symfony will call your voter when a
122+
``Product`` object is passed to ``isGranted()``.
123+
124+
:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedAttributes`
125+
It tells Symfony that your voter should be called whenever one of these
126+
strings is passes as the first argument to ``isGranted()``. For example, if
127+
you return ``array('CREATE', 'READ')``, then Symfony will call your voter
128+
when one of these is passed to ``isGranted()``.
129+
130+
:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::isGranted`
131+
It implements the business logic that verifies whether or not a given user is
132+
allowed access to a given attribute (e.g. ``CREATE`` or ``READ``) on a given
133+
object. This method must return a boolean.
134+
135+
.. note::
136+
137+
Currently, to use the ``AbstractVoter`` base class, you must be creating a
138+
voter where an object is always passed to ``isGranted()``.
139+
148140
Declaring the Voter as a Service
149141
--------------------------------
150142

@@ -203,6 +195,7 @@ from the authorization checker is called.
203195
204196
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
205197
use Symfony\Component\HttpFoundation\Response;
198+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
206199
207200
class PostController extends Controller
208201
{
@@ -212,25 +205,17 @@ from the authorization checker is called.
212205
$post = ...;
213206
214207
// keep in mind, this will call all registered security voters
215-
$this->denyAccessUnlessGranted('view', $post, 'Unauthorized access!');
216-
217-
// the equivalent code without using the denyAccessUnlessGranted() shortcut
218-
// use Symfony\Component\Security\Core\Exception\AccessDeniedException;
219-
//
220-
// if (false === $this->get('security.authorization_checker')->isGranted('view', $post)) {
221-
// throw new AccessDeniedException('Unauthorized access!');
222-
// }
208+
if (false === $this->get('security.authorization_checker')->isGranted('view', $post)) {
209+
throw new AccessDeniedException('Unauthorised access!');
210+
}
223211
224212
return new Response('<h1>'.$post->getName().'</h1>');
225213
}
226214
}
227215
228216
.. versionadded:: 2.6
229-
The ``security.authorization_checker`` service was introduced in Symfony 2.6. Prior
230-
to Symfony 2.6, you had to use the ``isGranted()`` method of the ``security.context`` service.
231-
232-
.. versionadded:: 2.6
233-
The ``denyAccessUnlessGranted()`` method was introduced in Symfony 2.6 as a shortcut.
234-
It uses ``security.authorization_checker`` and throws an ``AccessDeniedException`` if needed.
217+
The ``security.authorization_checker`` service was introduced in Symfony 2.6.
218+
Prior to Symfony 2.6, you had to use the ``isGranted()`` method of the
219+
``security.context`` service.
235220

236221
It's that easy!

0 commit comments

Comments
 (0)