@@ -25,7 +25,7 @@ How Symfony Uses Voters
2525
2626In order to use voters, you have to understand how Symfony works with them.
2727All voters are called each time you use the ``isGranted() `` method on Symfony's
28- authorization checker (i.e. the ``security.authorization_checker `` service). Each
28+ authorization checker (i.e. the ``security.authorization_checker `` service). Each
2929one decides if the current user should have access to some resource.
3030
3131Ultimately, Symfony uses one of three different approaches on what to do
@@ -37,11 +37,19 @@ For more information take a look at
3737The Voter Interface
3838-------------------
3939
40- A custom voter must implement
41- :class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ VoterInterface `,
42- which has this structure:
40+ A custom voter needs to implement
41+ :class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ VoterInterface `
42+ or extend :class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ AbstractVoter `,
43+ which makes creating a voter even easier.
4344
44- .. include :: /cookbook/security/voter_interface.rst.inc
45+ .. code-block :: php
46+
47+ abstract class AbstractVoter implements VoterInterface
48+ {
49+ abstract protected function getSupportedClasses();
50+ abstract protected function getSupportedAttributes();
51+ abstract protected function isGranted($attribute, $object, $user = null);
52+ }
4553
4654 In this example, the voter will check if the user has access to a specific
4755object according to your custom conditions (e.g. they must be the owner of
@@ -61,90 +69,74 @@ edit a particular object. Here's an example implementation:
6169 // src/AppBundle/Security/Authorization/Voter/PostVoter.php
6270 namespace AppBundle\Security\Authorization\Voter;
6371
64- use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
65- use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
72+ use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
6673 use Symfony\Component\Security\Core\User\UserInterface;
6774
68- class PostVoter implements VoterInterface
75+ class PostVoter extends AbstractVoter
6976 {
7077 const VIEW = 'view';
7178 const EDIT = 'edit';
7279
73- public function supportsAttribute($attribute )
80+ protected function getSupportedAttributes( )
7481 {
75- return in_array($attribute, array(
76- self::VIEW,
77- self::EDIT,
78- ));
82+ return array(self::VIEW, self::EDIT);
7983 }
8084
81- public function supportsClass($class )
85+ protected function getSupportedClasses( )
8286 {
83- $supportedClass = 'AppBundle\Entity\Post';
84-
85- return $supportedClass === $class || is_subclass_of($class, $supportedClass);
87+ return array('AppBundle\Entity\Post');
8688 }
8789
88- /**
89- * @var \AppBundle\Entity\Post $post
90- */
91- public function vote(TokenInterface $token, $post, array $attributes)
90+ protected function isGranted($attribute, $post, $user = null)
9291 {
93- // check if class of this object is supported by this voter
94- if (!$this->supportsClass(get_class($post))) {
95- return VoterInterface::ACCESS_ABSTAIN;
96- }
97-
98- // check if the voter is used correct, only allow one attribute
99- // this isn't a requirement, it's just one easy way for you to
100- // design your voter
101- if (1 !== count($attributes)) {
102- throw new \InvalidArgumentException(
103- 'Only one attribute is allowed for VIEW or EDIT'
104- );
105- }
106-
107- // set the attribute to check against
108- $attribute = $attributes[0];
109-
110- // check if the given attribute is covered by this voter
111- if (!$this->supportsAttribute($attribute)) {
112- return VoterInterface::ACCESS_ABSTAIN;
113- }
114-
115- // get current logged in user
116- $user = $token->getUser();
117-
11892 // make sure there is a user object (i.e. that the user is logged in)
11993 if (!$user instanceof UserInterface) {
120- return VoterInterface::ACCESS_DENIED;
94+ return false;
95+ }
96+
97+ // the data object could have for example a method isPrivate()
98+ // which checks the Boolean attribute $private
99+ if ($attribute == self::VIEW && !$post->isPrivate()) {
100+ return true;
121101 }
122102
123- switch($attribute) {
124- case self::VIEW:
125- // the data object could have for example a method isPrivate()
126- // which checks the boolean attribute $private
127- if (!$post->isPrivate()) {
128- return VoterInterface::ACCESS_GRANTED;
129- }
130- break;
131-
132- case self::EDIT:
133- // we assume that our data object has a method getOwner() to
134- // get the current owner user entity for this data object
135- if ($user->getId() === $post->getOwner()->getId()) {
136- return VoterInterface::ACCESS_GRANTED;
137- }
138- break;
103+ // we assume that our data object has a method getOwner() to
104+ // get the current owner user entity for this data object
105+ if ($attribute == self::EDIT && $user->getId() === $post->getOwner()->getId()) {
106+ return true;
139107 }
140108
141- return VoterInterface::ACCESS_DENIED ;
109+ return false ;
142110 }
143111 }
144112
145113 That's it! The voter is done. The next step is to inject the voter into
146114the security layer.
147115
116+ To recap, here's what's expected from the three abstract methods:
117+
118+ :method: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ AbstractVoter::getSupportedClasses `
119+ It tells Symfony that your voter should be called whenever an object of one
120+ of the given classes is passed to ``isGranted() ``. For example, if you return
121+ ``array('AppBundle\Model\Product') ``, Symfony will call your voter when a
122+ ``Product `` object is passed to ``isGranted() ``.
123+
124+ :method: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ AbstractVoter::getSupportedAttributes `
125+ It tells Symfony that your voter should be called whenever one of these
126+ strings is passed as the first argument to ``isGranted() ``. For example, if
127+ you return ``array('CREATE', 'READ') ``, then Symfony will call your voter
128+ when one of these is passed to ``isGranted() ``.
129+
130+ :method: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ AbstractVoter::isGranted `
131+ It implements the business logic that verifies whether or not a given user is
132+ allowed access to a given attribute (e.g. ``CREATE `` or ``READ ``) on a given
133+ object. This method must return a boolean.
134+
135+ .. note ::
136+
137+ Currently, to use the ``AbstractVoter `` base class, you must be creating a
138+ voter where an object is always passed to ``isGranted() ``.
139+
148140Declaring the Voter as a Service
149141--------------------------------
150142
@@ -203,6 +195,7 @@ from the authorization checker is called.
203195
204196 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
205197 use Symfony\Component\HttpFoundation\Response;
198+ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
206199
207200 class PostController extends Controller
208201 {
@@ -212,25 +205,17 @@ from the authorization checker is called.
212205 $post = ...;
213206
214207 // keep in mind, this will call all registered security voters
215- $this->denyAccessUnlessGranted('view', $post, 'Unauthorized access!');
216-
217- // the equivalent code without using the denyAccessUnlessGranted() shortcut
218- // use Symfony\Component\Security\Core\Exception\AccessDeniedException;
219- //
220- // if (false === $this->get('security.authorization_checker')->isGranted('view', $post)) {
221- // throw new AccessDeniedException('Unauthorized access!');
222- // }
208+ if (false === $this->get('security.authorization_checker')->isGranted('view', $post)) {
209+ throw new AccessDeniedException('Unauthorised access!');
210+ }
223211
224212 return new Response('<h1 >'.$post->getName().'</h1 >');
225213 }
226214 }
227215
228216 .. versionadded :: 2.6
229- The ``security.authorization_checker `` service was introduced in Symfony 2.6. Prior
230- to Symfony 2.6, you had to use the ``isGranted() `` method of the ``security.context `` service.
231-
232- .. versionadded :: 2.6
233- The ``denyAccessUnlessGranted() `` method was introduced in Symfony 2.6 as a shortcut.
234- It uses ``security.authorization_checker `` and throws an ``AccessDeniedException `` if needed.
217+ The ``security.authorization_checker `` service was introduced in Symfony 2.6.
218+ Prior to Symfony 2.6, you had to use the ``isGranted() `` method of the
219+ ``security.context `` service.
235220
236221It's that easy!
0 commit comments