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 1466fa7

Browse filesBrowse files
Michael Kleinweaverryan
authored andcommitted
improvements according to the reviews
1 parent 99b1b0f commit 1466fa7
Copy full SHA for 1466fa7

File tree

Expand file treeCollapse file tree

1 file changed

+96
-43
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+96
-43
lines changed
+96-43Lines changed: 96 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
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
1212
application: :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

2222
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
2627
:ref:`"components-security-access-decision-manager"`.
2728

2829
The Voter Interface
@@ -32,13 +33,13 @@ A custom voter must implement
3233
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`,
3334
which 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

4445
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
6061
the 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

6566
Creating 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

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.