1
1
.. index ::
2
2
single: Security; Data Permission Voters
3
3
4
- How to implement your own Voter to check user permissions for accessing a given object
4
+ How to implement your own Voter to check User Permissions for accessing a Given Object
5
5
======================================================================================
6
6
7
- In Symfony2 you can check the permission to access data by the
8
- :doc: `ACL module </cookbook/security/acl >`, which is a bit overwhelming
9
- for many applications. A much easier solution is to work with custom voters
10
- voters, which are like simple conditional statements. Voters can be
11
- also used to check for permission as a part or even the whole
7
+ In Symfony2 you can check the permission to access data by the
8
+ :doc: `ACL module </cookbook/security/acl >`, which is a bit overwhelming
9
+ for many applications. A much easier solution is to work with custom voters,
10
+ which are like simple conditional statements. Voters can be
11
+ also be used to check for permission as a part or even the whole
12
12
application: :doc: `"/cookbook/security/voters" `.
13
13
14
14
.. tip ::
15
15
16
16
It is good to understand the basics about what and how
17
- :doc: `authorization </components/security/authorization >` works.
17
+ :doc: `authorization </components/security/authorization >` works. // correct link in book?
18
18
19
- How Symfony Uses Voters
19
+ How Symfony uses Voters
20
20
-----------------------
21
21
22
22
In order to use voters, you have to understand how Symfony works with them.
23
- In general, all registered custom voters will be called every time you ask
24
- Symfony about permissions (ACL). In general there are three different
25
- approaches on how to handle the feedback from all voters:
23
+ In general, all registered custom voters will be called every time you ask
24
+ Symfony about permissions (ACL). You can use one of three different
25
+ approaches on how to handle the feedback from all voters: affirmative,
26
+ consensus and unanimous. For more information have a look at
26
27
:ref: `"components-security-access-decision-manager" `.
27
28
28
29
The Voter Interface
@@ -32,13 +33,13 @@ A custom voter must implement
32
33
:class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ VoterInterface `,
33
34
which has this structure:
34
35
35
- .. code-block :: php
36
+ .. code-block :: php // :: shortcut? and put the snippet (to line 56) in a single file an reference ?
36
37
37
38
interface VoterInterface
38
39
{
39
40
public function supportsAttribute($attribute);
40
41
public function supportsClass($class);
41
- public function vote(TokenInterface $token, $object , array $attributes);
42
+ public function vote(TokenInterface $token, $post , array $attributes);
42
43
}
43
44
44
45
The ``supportsAttribute() `` method is used to check if the voter supports
@@ -60,7 +61,7 @@ object according to your custom conditions (e.g. he must be the owner of
60
61
the object). If the condition fails, you'll return
61
62
``VoterInterface::ACCESS_DENIED ``, otherwise you'll return
62
63
``VoterInterface::ACCESS_GRANTED ``. In case the responsibility for this decision
63
- belongs not to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN ``.
64
+ does not belong to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN ``.
64
65
65
66
Creating the Custom Voter
66
67
-------------------------
@@ -72,81 +73,86 @@ You could store your Voter to check permission for the view and edit action like
72
73
// src/Acme/DemoBundle/Security/Authorization/Entity/PostVoter.php
73
74
namespace Acme\DemoBundle\Security\Authorization\Entity;
74
75
76
+ use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
75
77
use Symfony\Component\DependencyInjection\ContainerInterface;
76
78
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
77
79
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
78
80
use Symfony\Component\Security\Core\User\UserInterface;
81
+ use Doctrine\Common\Util\ClassUtils;
79
82
80
83
class PostVoter implements VoterInterface
81
84
{
82
-
83
- public function supportsAttribute($attribute)
85
+ public function supportsAttribute($attribute)
84
86
{
85
87
return in_array($attribute, array(
86
88
'view',
87
89
'edit',
88
90
));
89
91
}
90
-
91
- public function supportsClass($class )
92
+
93
+ public function supportsClass($obj )
92
94
{
93
- // could be "Acme\DemoBundle\Entity\Post" as well
94
- $array = array("Acme\DemoBundle\Entity\Post");
95
-
95
+ $array = array('Acme\DemoBundle\Entity\Post');
96
+
96
97
foreach ($array as $item) {
97
98
// check with stripos in case doctrine is using a proxy class for this object
98
- if (stripos($s, $item) !== false) {
99
-
99
+ // if (stripos($s, $item) !== false) {
100
+ if ($obj instanceof $item)) // check if this will also check for interfaces etc. like it should be in oop (inheritace)
101
+ // or return $targetClass === $class || is_subclass_of($class, $targetClass);
100
102
return true;
101
103
}
102
104
}
103
105
104
106
return false;
105
107
}
106
-
107
- public function vote(TokenInterface $token, $object, array $attributes)
108
+
109
+ /** @var \Acme\DemoBundle\Entity\Post $post */
110
+ public function vote(TokenInterface $token, $post, array $attributes) // remove array
108
111
{
112
+ // always get the first attribute
113
+ $attribute = $attributes[0];
114
+
109
115
// get current logged in user
110
116
$user = $token->getUser();
111
-
117
+
112
118
// check if class of this object is supported by this voter
113
- if (!($this->supportsClass(get_class($object )))) {
119
+ if (!($this->supportsClass($post ))) { // maybe without ClassUtils::getRealClass(
114
120
115
121
return VoterInterface::ACCESS_ABSTAIN;
116
122
}
117
-
123
+
118
124
// check if the given attribute is covered by this voter
119
- foreach ($attributes as $attribute) {
120
- if (!$this->supportsAttribute($attribute)) {
125
+ if (!$this->supportsAttribute($attribute)) {
121
126
122
- return VoterInterface::ACCESS_ABSTAIN;
123
- }
127
+ return VoterInterface::ACCESS_ABSTAIN;
124
128
}
125
-
129
+
126
130
// check if given user is instance of user interface
127
131
if (!($user instanceof UserInterface)) {
128
132
129
133
return VoterInterface::ACCESS_DENIED;
130
134
}
131
-
132
- switch($this->attributes[0] ) {
135
+
136
+ switch($attribute ) {
133
137
case 'view':
134
- if ($object->isPrivate() === false) {
138
+ // the data object could have for e.g. a method isPrivate() which checks the the boolean attribute $private
139
+ if (!$post->isPrivate()) {
135
140
136
141
return VoterInterface::ACCESS_GRANTED;
137
142
}
138
143
break;
139
-
144
+
140
145
case 'edit':
141
- if ($user->getId() === $object->getOwner()->getId()) {
146
+ // we assume that our data object has a method getOwner() to get the current owner user entity for this data object
147
+ if ($user->getId() === $post->getOwner()->getId()) {
142
148
143
149
return VoterInterface::ACCESS_GRANTED;
144
150
}
145
151
break;
146
-
152
+
147
153
default:
148
- // otherwise denied access
149
- return VoterInterface::ACCESS_DENIED;
154
+ // otherwise throw an exception
155
+ throw new PreconditionFailedHttpException('The Attribute "'.$attribute.'"" was not found.')
150
156
}
151
157
152
158
}
@@ -170,6 +176,53 @@ and tag it as a "security.voter":
170
176
security.access.post_voter :
171
177
class : Acme\DemoBundle\Security\Authorization\Entity\PostVoter
172
178
public : false
173
- # the service gets tagged as a voter
174
179
tags :
175
- - { name: security.voter }
180
+ - { name: security.voter }
181
+
182
+ .. code-block :: xml
183
+
184
+ <?xml version =" 1.0" encoding =" UTF-8" ?>
185
+ <container xmlns =" http://symfony.com/schema/dic/services" >
186
+ <services >
187
+ <service id =" security.access.post_document_voter"
188
+ class =" Acme\DemoBundle\Security\Authorization\Document\PostVoter"
189
+ public =" false" >
190
+ <tag name =" security.voter" />
191
+ </service >
192
+ </services >
193
+ </container >
194
+
195
+ .. code-block :: php
196
+
197
+ $container
198
+ ->register('security.access.post_document_voter', 'Acme\DemoBundle\Security\Authorization\Document\PostVoter')
199
+ ->addTag('security.voter')
200
+ ;
201
+
202
+ How to use the Voter in a Controller
203
+ ------------------------------------
204
+
205
+ .. code-block :: php
206
+
207
+ // src/Acme/DemoBundle/Controller/PostController.php
208
+ namespace Acme\DemoBundle\Controller;
209
+
210
+ use Symfony\Component\HttpFoundation\Response;
211
+ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
212
+
213
+ class PostController
214
+ {
215
+ public function showAction($id)
216
+ {
217
+ // keep in mind, this will call all registered security voters
218
+ if (false === $this->get('security.context')->isGranted('view')) {
219
+ throw new AccessDeniedException('Unauthorised access!');
220
+ }
221
+
222
+ $product = $this->getDoctrine()
223
+ ->getRepository('AcmeStoreBundle:Post')
224
+ ->find($id);
225
+
226
+ return new Response('<html ><body >Headline for Post: '.$post->getName().'</body ></html >');
227
+ }
228
+ }
0 commit comments