8000 improvements according to the reviews · symfony/symfony-docs@1466fa7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1466fa7

Browse files
Michael Kleinweaverryan
Michael Klein
authored andcommitted
improvements according to the reviews
1 parent 99b1b0f commit 1466fa7

File tree

1 file changed

+96
-43
lines changed

1 file changed

+96
-43
lines changed
Lines 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