@@ -35,120 +35,179 @@ The Voter Interface
3535
3636A custom voter needs to implement
3737:class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ VoterInterface `
38- or extend :class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ AbstractVoter `,
38+ or extend :class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ Voter `,
3939which makes creating a voter even easier.
4040
4141.. code-block :: php
4242
43- abstract class AbstractVoter implements VoterInterface
43+ abstract class Voter implements VoterInterface
4444 {
45- abstract protected function getSupportedClasses();
46- abstract protected function getSupportedAttributes();
47- abstract protected function isGranted($attribute, $object, $user = null);
45+ abstract protected function supports($attribute, $subject);
46+ abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token);
4847 }
4948
50- In this example, the voter will check if the user has access to a specific
51- object according to your custom conditions (e.g. they must be the owner of
52- the object). If the condition fails, you'll return
53- ``VoterInterface::ACCESS_DENIED ``, otherwise you'll return
54- ``VoterInterface::ACCESS_GRANTED ``. In case the responsibility for this decision
55- does not belong to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN ``.
49+ .. versionadded ::
50+ The ``Voter `` helper class was added in Symfony 2.8. In early versions, an
51+ ``AbstractVoter `` class with similar behavior was available.
52+
53+ .. _how-to-use-the-voter-in-a-controller :
54+
55+ Setup: Checking for Access in a Controller
56+ ------------------------------------------
57+
58+ Suppose you have a ``Post `` object and you need to decide whether or not the current
59+ user can *edit * or *view * the object. In your controller, you'll check access with
60+ code like this::
61+
62+ // src/AppBundle/Controller/PostController.php
63+ // ...
64+
65+ class PostController extends Controller
66+ {
67+ /**
68+ * @Route("/posts/{id}", name="post_show")
69+ */
70+ public function showAction($id)
71+ {
72+ // get a Post object - e.g. query for it
73+ $post = ...;
74+
75+ // check for "view" access: calls all voters
76+ $this->denyAccessUnlessGranted('view', $post);
77+
78+ // ...
79+ }
80+
81+ /**
82+ * @Route("/posts/{id}/edit", name="post_edit")
83+ */
84+ public function editAction($id)
85+ {
86+ // get a Post object - e.g. query for it
87+ $post = ...;
88+
89+ // check for "edit" access: calls all voters
90+ $this->denyAccessUnlessGranted('edit', $post);
91+
92+ // ...
93+ }
94+ }
95+
96+ The ``denyAccessUnlessGranted() `` method (and also, the simpler ``isGranted() `` method)
97+ calls out to the "voter" system. Right now, no voters will vote on whether or not
98+ the user can "view" or "edit" a ``Post ``. But you can create your *own * voter that
99+ decides this using whatever logic you want.
100+
101+ .. tip ::
102+
103+ The ``denyAccessUnlessGranted() `` function and the ``isGranted() `` functions
104+ are both just shortcuts to call ``isGranted() `` on the ``security.authorization_checker ``
105+ service.
56106
57107Creating the custom Voter
58108-------------------------
59109
60- The goal is to create a voter that checks if a user has access to view or
61- edit a particular object. Here's an example implementation:
110+ Suppose the logic to decide if a user can "view" or "edit" a ``Post `` object is
111+ pretty complex. For example, a ``User `` can always edit or view a ``Post `` they created.
112+ And if a ``Post `` is marked as "public", anyone can view it. A voter for this situation
113+ would look like this::
62114
63- .. code-block :: php
64-
65- // src/AppBundle/Security/Authorization/Voter/PostVoter.php
66- namespace AppBundle\Security\Authorization\Voter;
115+ // src/AppBundle/Security/PostVoter.php
116+ namespace AppBundle\Security;
67117
68- use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter ;
118+ use AppBundle\Entity\Post ;
69119 use AppBundle\Entity\User;
70- use Symfony\Component\Security\Core\User\UserInterface;
120+ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
121+ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
71122
72- class PostVoter extends AbstractVoter
123+ class PostVoter extends Voter
73124 {
125+ // these strings are just invented: you can use anything
74126 const VIEW = 'view';
75127 const EDIT = 'edit';
76128
77- protected function getSupportedAttributes( )
129+ protected function supports($attribute, $subject )
78130 {
79- return array(self::VIEW, self::EDIT);
80- }
131+ // if the attribute isn't one we support, return false
132+ if (!in_array($attribute, array(self::VIEW, self::EDIT))) {
133+ return false;
134+ }
81135
82- protected function getSupportedClasses()
83- {
84- return array('AppBundle\Entity\Post');
136+ // only vote on Post objects inside this voter
137+ if (!$subject instanceof Post) {
138+ return false;
139+ }
140+
141+ return true;
85142 }
86143
87- protected function isGranted ($attribute, $post, $user = null )
144+ protected function voteOnAttribute ($attribute, $subject, TokenInterface $token )
88145 {
89- // make sure there is a user object (i.e. that the user is logged in)
90- if (!$user instanceof UserInterface) {
91- return false;
92- }
146+ $user = $token->getUser();
93147
94- // double-check that the User object is the expected entity (this
95- // only happens when you did not configure the security system properly)
96148 if (!$user instanceof User) {
97- throw new \LogicException('The user is somehow not our User class!');
149+ // the user must not be logged in, so we deny access
150+ return false;
98151 }
99152
153+ // we know $subject is a Post object, thanks to supports
154+ /** @var Post $post */
155+ $post = $subject;
156+
100157 switch($attribute) {
101158 case self::VIEW:
102- // the data object could have for example a method isPrivate()
103- // which checks the Boolean attribute $private
104- if (!$post->isPrivate()) {
105- return true;
106- }
107-
108- break;
159+ return $this->canView($post, $user);
109160 case self::EDIT:
110- // this assumes that the data object has a getOwner() method
111- // to get the entity of the user who owns this data object
112- if ($user->getId() === $post->getOwner()->getId()) {
113- return true;
114- }
115-
116- break;
161+ return $this->canEdit($post, $user);
117162 }
118163
119- return false ;
164+ throw new \LogicException('This code should not be reached!') ;
120165 }
121- }
122166
123- That's it! The voter is done. The next step is to inject the voter into
124- the security layer.
167+ private function canView(Post $post, User $user)
168+ {
169+ // if they can edit, they can view
170+ if ($this->canEdit($post, $user)) {
171+ return true;
172+ }
173+
174+ // the Post object could have, for example, a method isPrivate()
175+ // that checks a Boolean $private property
176+ return !$post->isPrivate();
177+ }
125178
126- To recap, here's what's expected from the three abstract methods:
179+ private function canEdit(Post $post, User $user)
180+ {
181+ // this assumes that the data object has a getOwner() method
182+ // to get the entity of the user who owns this data object
183+ return $user === $post->getOwner();
184+ }
185+ }
127186
128- :method: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ AbstractVoter::getSupportedClasses `
129- It tells Symfony that your voter should be called whenever an object of one
130- of the given classes is passed to ``isGranted() ``. For example, if you return
131- ``array('AppBundle\Model\Product') ``, Symfony will call your voter when a
132- ``Product `` object is passed to ``isGranted() ``.
187+ That's it! The voter is done! Next, :ref: `configure it <declaring-the-voter-as-a-service >`.
133188
134- :method: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ AbstractVoter::getSupportedAttributes `
135- It tells Symfony that your voter should be called whenever one of these
136- strings is passed as the first argument to ``isGranted() ``. For example, if
137- you return ``array('CREATE', 'READ') ``, then Symfony will call your voter
138- when one of these is passed to ``isGranted() ``.
189+ To recap, here's what's expected from the two abstract methods:
139190
140- :method: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ AbstractVoter::isGranted `
141- It implements the business logic that verifies whether or not a given user is
142- allowed access to a given attribute (e.g. ``CREATE `` or ``READ ``) on a given
143- object. This method must return a boolean.
191+ ``Voter::supports($attribute, $subject) ``
192+ When ``isGranted() `` (or ``denyAccessUnlessGranted() ``) is called, the first
193+ argument is passed here as ``$attribute `` (e.g. ``ROLE_USER ``, ``edit ``) and
194+ the second argument (if any) is passed as ```$subject `` (e.g. ``null ``, a ``Post ``
195+ object). Your job is to determine if your voter should vote on the attribute/subject
196+ combination. If you return true, ``voteOnAttribute() `` will be called. Otherwise,
197+ your voter is done: some other voter should process this. In this example, you
198+ return ``true `` if the attribue is ``view `` or ``edit `` and if the object is
199+ a ``Post `` instance.
144200
145- .. note ::
201+ ``voteOnAttribute($attribute, $subject, TokenInterface $token) ``
202+ If you return ``true `` from ``supports() ``, then this method is called. Your
203+ job is simple: return ``true `` to allow access and ``false `` to deny access.
204+ The ``$token `` can be used to find the current user object (if any). In this
205+ example, all of the complex business logic is included to determine access.
146206
147- Currently, to use the ``AbstractVoter `` base class, you must be creating a
148- voter where an object is always passed to ``isGranted() ``.
207+ .. _declaring-the-voter-as-a-service :
149208
150- Declaring the Voter as a Service
151- --------------------------------
209+ Configuring the Voter
210+ ---------------------
152211
153212To inject the voter into the security layer, you must declare it as a service
154213and tag it with ``security.voter ``:
@@ -159,9 +218,8 @@ and tag it with ``security.voter``:
159218
160219 # app/config/services.yml
161220 services :
162- security.access.post_voter :
163- class : AppBundle\Security\Authorization\Voter\PostVoter
164- public : false
221+ app.post_voter :
222+ class : AppBundle\Security\PostVoter
165223 tags :
166224 - { name: security.voter }
167225
@@ -175,7 +233,7 @@ and tag it with ``security.voter``:
175233 http://symfony.com/schema/dic/services/services-1.0.xsd" >
176234
177235 <services >
178- <service id =" security.access .post_voter"
236+ <service id =" app .post_voter"
179237 class =" AppBundle\Security\Authorization\Voter\PostVoter"
180238 public =" false"
181239 >
@@ -190,61 +248,27 @@ and tag it with ``security.voter``:
190248 // app/config/services.php
191249 use Symfony\Component\DependencyInjection\Definition;
192250
193- $definition = new Definition('AppBundle\Security\Authorization\Voter\PostVoter');
194- $definition
251+ $container->register('app.post_voter', 'AppBundle\Security\Authorization\Voter\PostVoter')
195252 ->setPublic(false)
196253 ->addTag('security.voter')
197254 ;
198255
199- $container->setDefinition('security.access.post_voter', $definition);
200-
201- How to Use the Voter in a Controller
202- ------------------------------------
203-
204- The registered voter will then always be asked as soon as the method ``isGranted() ``
205- from the authorization checker is called. When extending the base ``Controller ``
206- class, you can simply call the
207- :method: `Symfony\\ Bundle\\ FrameworkBundle\\ Controller\\ Controller::denyAccessUnlessGranted() `
208- method::
209-
210- // src/AppBundle/Controller/PostController.php
211- namespace AppBundle\Controller;
212-
213- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
214- use Symfony\Component\HttpFoundation\Response;
215-
216- class PostController extends Controller
217- {
218- public function showAction($id)
219- {
220- // get a Post instance
221- $post = ...;
222-
223- // keep in mind that this will call all registered security voters
224- $this->denyAccessUnlessGranted('view', $post, 'Unauthorized access!');
225-
226- return new Response('<h1>'.$post->getName().'</h1>');
227- }
228- }
229-
230- .. versionadded :: 2.6
231- The ``denyAccessUnlessGranted() `` method was introduced in Symfony 2.6.
232- Prior to Symfony 2.6, you had to call the ``isGranted() `` method of the
233- ``security.context `` service and throw the exception yourself.
234-
235- It's that easy!
256+ You're done! Now, when you :ref: `call isGranted() with view/edit and a Post object <how-to-use-the-voter-in-a-controller >`,
257+ your voter will be executed and you can control access.
236258
237259.. _security-voters-change-strategy :
238260
239261Changing the Access Decision Strategy
240262-------------------------------------
241263
242- Imagine you have multiple voters for one action for an object. For instance,
243- you have one voter that checks if the user is a member of the site and a second
244- one checking if the user is older than 18.
264+ Normally, only one voter will vote at any given time (the rest will "abstain", which
265+ means they return ``false `` from ``supports() ``). But in theory, you could make multiple
266+ voters vote for one action and object. For instance, suppose you have one voter that
267+ checks if the user is a member of the site and a second one that checks if the user
268+ is older than 18.
245269
246270To handle these cases, the access decision manager uses an access decision
247- strategy. You can configure this to suite your needs. There are three
271+ strategy. You can configure this to suit your needs. There are three
248272strategies available:
249273
250274``affirmative `` (default)
0 commit comments