11.. index ::
22 single: Security; Data Permission Voters
33
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
55======================================================================================
66
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
1212application: :doc: `"/cookbook/security/voters" `.
1313
1414.. tip ::
1515
1616 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?
1818
19- How Symfony Uses Voters
19+ How Symfony uses Voters
2020-----------------------
2121
2222In 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
2627:ref: `"components-security-access-decision-manager" `.
2728
2829The Voter Interface
@@ -32,13 +33,13 @@ A custom voter must implement
3233:class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ VoterInterface `,
3334which has this structure:
3435
35- .. code-block :: php
36+ .. code-block :: php // :: shortcut? and put the snippet (to line 56) in a single file an reference ?
3637
3738 interface VoterInterface
3839 {
3940 public function supportsAttribute($attribute);
4041 public function supportsClass($class);
41- public function vote(TokenInterface $token, $object , array $attributes);
42+ public function vote(TokenInterface $token, $post , array $attributes);
4243 }
4344
4445The ``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
6061the object). If the condition fails, you'll return
6162``VoterInterface::ACCESS_DENIED ``, otherwise you'll return
6263``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 ``.
6465
6566Creating the Custom Voter
6667-------------------------
@@ -72,81 +73,86 @@ You could store your Voter to check permission for the view and edit action like
7273 // src/Acme/DemoBundle/Security/Authorization/Entity/PostVoter.php
7374 namespace Acme\DemoBundle\Security\Authorization\Entity;
7475
76+ use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
7577 use Symfony\Component\DependencyInjection\ContainerInterface;
7678 use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
7779 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
7880 use Symfony\Component\Security\Core\User\UserInterface;
81+ use Doctrine\Common\Util\ClassUtils;
7982
8083 class PostVoter implements VoterInterface
8184 {
82-
83- public function supportsAttribute($attribute)
85+ public function supportsAttribute($attribute)
8486 {
8587 return in_array($attribute, array(
8688 'view',
8789 'edit',
8890 ));
8991 }
90-
91- public function supportsClass($class )
92+
93+ public function supportsClass($obj )
9294 {
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+
9697 foreach ($array as $item) {
9798 // 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);
100102 return true;
101103 }
102104 }
103105
104106 return false;
105107 }
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
108111 {
112+ // always get the first attribute
113+ $attribute = $attributes[0];
114+
109115 // get current logged in user
110116 $user = $token->getUser();
111-
117+
112118 // 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(
114120
115121 return VoterInterface::ACCESS_ABSTAIN;
116122 }
117-
123+
118124 // 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)) {
121126
122- return VoterInterface::ACCESS_ABSTAIN;
123- }
127+ return VoterInterface::ACCESS_ABSTAIN;
124128 }
125-
129+
126130 // check if given user is instance of user interface
127131 if (!($user instanceof UserInterface)) {
128132
129133 return VoterInterface::ACCESS_DENIED;
130134 }
131-
132- switch($this->attributes[0] ) {
135+
136+ switch($attribute ) {
133137 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()) {
135140
136141 return VoterInterface::ACCESS_GRANTED;
137142 }
138143 break;
139-
144+
140145 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()) {
142148
143149 return VoterInterface::ACCESS_GRANTED;
144150 }
145151 break;
146-
152+
147153 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.')
150156 }
151157
152158 }
@@ -170,6 +176,53 @@ and tag it as a "security.voter":
170176 security.access.post_voter :
171177 class : Acme\DemoBundle\Security\Authorization\Entity\PostVoter
172178 public : false
173- # the service gets tagged as a voter
174179 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