@@ -25,7 +25,7 @@ How Symfony Uses Voters
25
25
26
26
In order to use voters, you have to understand how Symfony works with them.
27
27
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
29
29
one decides if the current user should have access to some resource.
30
30
31
31
Ultimately, Symfony uses one of three different approaches on what to do
@@ -37,11 +37,19 @@ For more information take a look at
37
37
The Voter Interface
38
38
-------------------
39
39
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.
43
44
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
+ }
45
53
46
54
In this example, the voter will check if the user has access to a specific
47
55
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:
61
69
// src/AppBundle/Security/Authorization/Voter/PostVoter.php
62
70
namespace AppBundle\Security\Authorization\Voter;
63
71
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;
66
73
use Symfony\Component\Security\Core\User\UserInterface;
67
74
68
- class PostVoter implements VoterInterface
75
+ class PostVoter extends AbstractVoter
69
76
{
70
77
const VIEW = 'view';
71
78
const EDIT = 'edit';
72
79
73
- public function supportsAttribute($attribute )
80
+ protected function getSupportedAttributes( )
74
81
{
75
- return in_array($attribute, array(
76
- self::VIEW,
77
- self::EDIT,
78
- ));
82
+ return array(self::VIEW, self::EDIT);
79
83
}
80
84
81
- public function supportsClass($class )
85
+ protected function getSupportedClasses( )
82
86
{
83
- $supportedClass = 'AppBundle\Entity\Post';
84
-
85
- return $supportedClass === $class || is_subclass_of($class, $supportedClass);
87
+ return array('AppBundle\Entity\Post');
86
88
}
87
89
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)
92
91
{
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
-
118
92
// make sure there is a user object (i.e. that the user is logged in)
119
93
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;
121
101
}
122
102
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;
139
107
}
140
108
141
- return VoterInterface::ACCESS_DENIED ;
109
+ return false ;
142
110
}
143
111
}
144
112
145
113
That's it! The voter is done. The next step is to inject the voter into
146
114
the security layer.
147
115
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
+
148
140
Declaring the Voter as a Service
149
141
--------------------------------
150
142
@@ -203,6 +195,7 @@ from the authorization checker is called.
203
195
204
196
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
205
197
use Symfony\Component\HttpFoundation\Response;
198
+ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
206
199
207
200
class PostController extends Controller
208
201
{
@@ -212,25 +205,17 @@ from the authorization checker is called.
212
205
$post = ...;
213
206
214
207
// 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
+ }
223
211
224
212
return new Response('<h1 >'.$post->getName().'</h1 >');
225
213
}
226
214
}
227
215
228
216
.. 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.
235
220
236
221
It's that easy!
0 commit comments