-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Security] add & update doc entries on AbstractVoter implementation #5423
Changes from 4 commits
31041fa
79a2204
7619944
f47cf37
e680e77
695466c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,6 +92,13 @@ the security layer. This can be done easily through the service container. | |
methods in your implementation of the ``vote()`` method and return ``ACCESS_ABSTAIN`` | ||
if your voter does not support the class or attribute. | ||
|
||
|
||
.. tip:: | ||
|
||
An | ||
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter` | ||
is provided to cover the common use cases when implementing security voters. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about linking "AbstractVoter" to the cookbook? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm afraid that I don't understand this suggestion. Which page should I link to? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about linking to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. I understand it now. I've just added the link. Thanks. |
||
|
||
Declaring the Voter as a Service | ||
-------------------------------- | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,7 @@ How Symfony Uses Voters | |
|
||
In order to use voters, you have to understand how Symfony works with them. | ||
All voters are called each time you use the ``isGranted()`` method on Symfony's | ||
authorization checker (i.e. the ``security.authorization_checker`` service). Each | ||
authorization checker (i.e. the ``security.authorization_checker`` service). Each | ||
one decides if the current user should have access to some resource. | ||
|
||
Ultimately, Symfony uses one of three different approaches on what to do | ||
|
@@ -37,11 +37,19 @@ For more information take a look at | |
The Voter Interface | ||
------------------- | ||
|
||
A custom voter must implement | ||
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, | ||
which has this structure: | ||
A custom voter needs to implement | ||
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface` | ||
or extend :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter`, | ||
which makes creating a voter even easier. | ||
|
||
.. include:: /cookbook/security/voter_interface.rst.inc | ||
.. code-block:: php | ||
|
||
abstract class AbstractVoter implements VoterInterface | ||
{ | ||
abstract protected function getSupportedClasses(); | ||
abstract protected function getSupportedAttributes(); | ||
abstract protected function isGranted($attribute, $object, $user = null); | ||
} | ||
|
||
In this example, the voter will check if the user has access to a specific | ||
object 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: | |
// src/AppBundle/Security/Authorization/Voter/PostVoter.php | ||
namespace AppBundle\Security\Authorization\Voter; | ||
|
||
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; | ||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||
use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; | ||
use Symfony\Component\Security\Core\User\UserInterface; | ||
|
||
class PostVoter implements VoterInterface | ||
class PostVoter extends AbstractVoter | ||
{ | ||
const VIEW = 'view'; | ||
const EDIT = 'edit'; | ||
|
||
public function supportsAttribute($attribute) | ||
protected function getSupportedAttributes() | ||
{ | ||
return in_array($attribute, array( | ||
self::VIEW, | ||
self::EDIT, | ||
)); | ||
return array(self::VIEW, self::EDIT); | ||
} | ||
|
||
public function supportsClass($class) | ||
protected function getSupportedClasses() | ||
{ | ||
$supportedClass = 'AppBundle\Entity\Post'; | ||
|
||
return $supportedClass === $class || is_subclass_of($class, $supportedClass); | ||
return array('AppBundle\Entity\Post'); | ||
} | ||
|
||
/** | ||
* @var \AppBundle\Entity\Post $post | ||
*/ | ||
public function vote(TokenInterface $token, $post, array $attributes) | ||
protected function isGranted($attribute, $post, $user = null) | ||
{ | ||
// check if class of this object is supported by this voter | ||
if (!$this->supportsClass(get_class($post))) { | ||
return VoterInterface::ACCESS_ABSTAIN; | ||
} | ||
|
||
// check if the voter is used correct, only allow one attribute | ||
// this isn't a requirement, it's just one easy way for you to | ||
// design your voter | ||
if (1 !== count($attributes)) { | ||
throw new \InvalidArgumentException( | ||
'Only one attribute is allowed for VIEW or EDIT' | ||
); | ||
} | ||
|
||
// set the attribute to check against | ||
$attribute = $attributes[0]; | ||
|
||
// check if the given attribute is covered by this voter | ||
if (!$this->supportsAttribute($attribute)) { | ||
return VoterInterface::ACCESS_ABSTAIN; | ||
} | ||
|
||
// get current logged in user | ||
$user = $token->getUser(); | ||
|
||
// make sure there is a user object (i.e. that the user is logged in) | ||
if (!$user instanceof UserInterface) { | ||
return VoterInterface::ACCESS_DENIED; | ||
return false; | ||
} | ||
|
||
// the data object could have for example a method isPrivate() | ||
// which checks the Boolean attribute $private | ||
if ($attribute == self::VIEW && !$post->isPrivate()) { | ||
return true; | ||
} | ||
|
||
switch($attribute) { | ||
case self::VIEW: | ||
// the data object could have for example a method isPrivate() | ||
// which checks the boolean attribute $private | ||
if (!$post->isPrivate()) { | ||
return VoterInterface::ACCESS_GRANTED; | ||
} | ||
break; | ||
|
||
case self::EDIT: | ||
// we assume that our data object has a method getOwner() to | ||
// get the current owner user entity for this data object | ||
if ($user->getId() === $post->getOwner()->getId()) { | ||
return VoterInterface::ACCESS_GRANTED; | ||
} | ||
break; | ||
// we assume that our data object has a method getOwner() to | ||
// get the current owner user entity for this data object | ||
if ($attribute == self::EDIT && $user->getId() === $post->getOwner()->getId()) { | ||
return true; | ||
} | ||
|
||
return VoterInterface::ACCESS_DENIED; | ||
return false; | ||
} | ||
} | ||
|
||
That's it! The voter is done. The next step is to inject the voter into | ||
the security layer. | ||
|
||
To recap, here's what's expected from the three abstract methods: | ||
|
||
:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedClasses` | ||
It tells Symfony that your voter should be called whenever an object of one | ||
of the given classes is passed to ``isGranted()`` For example, if you return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing period after |
||
``array('AppBundle\Model\Product')``, Symfony will call your voter when a | ||
``Product`` object is passed to ``isGranted()``. | ||
|
||
:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedAttributes` | ||
It tells Symfony that your voter should be called whenever one of these | ||
strings is passes as the first argument to ``isGranted()``. For example, if | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [...] is passed as [...] |
||
you return ``array('CREATE', 'READ')``, then Symfony will call your voter | ||
when one of these is passed to ``isGranted()``. | ||
|
||
:method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::isGranted` | ||
It implements the business logic that verifies whether or not a given user is | ||
allowed access to a given attribute (e.g. ``CREATE`` or ``READ``) on a given | ||
object. This method must return a boolean. | ||
|
||
.. note:: | ||
|
||
Currently, to use the ``AbstractVoter`` base class, you must be creating a | ||
voter where an object is always passed to ``isGranted()``. | ||
|
||
Declaring the Voter as a Service | ||
-------------------------------- | ||
|
||
|
@@ -203,6 +195,7 @@ from the authorization checker is called. | |
|
||
use Symfony\Bundle\FrameworkBundle\Controller\Controller; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Security\Core\Exception\AccessDeniedException; | ||
|
||
class PostController extends Controller | ||
{ | ||
|
@@ -212,25 +205,17 @@ from the authorization checker is called. | |
$post = ...; | ||
|
||
// keep in mind, this will call all registered security voters | ||
$this->denyAccessUnlessGranted('view', $post, 'Unauthorized access!'); | ||
|
||
// the equivalent code without using the denyAccessUnlessGranted() shortcut | ||
// use Symfony\Component\Security\Core\Exception\AccessDeniedException; | ||
// | ||
// if (false === $this->get('security.authorization_checker')->isGranted('view', $post)) { | ||
// throw new AccessDeniedException('Unauthorized access!'); | ||
// } | ||
if (false === $this->get('security.authorization_checker')->isGranted('view', $post)) { | ||
throw new AccessDeniedException('Unauthorised access!'); | ||
} | ||
|
||
return new Response('<h1>'.$post->getName().'</h1>'); | ||
} | ||
} | ||
|
||
.. versionadded:: 2.6 | ||
The ``security.authorization_checker`` service was introduced in Symfony 2.6. Prior | ||
to Symfony 2.6, you had to use the ``isGranted()`` method of the ``security.context`` service. | ||
|
||
.. versionadded:: 2.6 | ||
The ``denyAccessUnlessGranted()`` method was introduced in Symfony 2.6 as a shortcut. | ||
It uses ``security.authorization_checker`` and throws an ``AccessDeniedException`` if needed. | ||
The ``security.authorization_checker`` service was introduced in Symfony 2.6. | ||
Prior to Symfony 2.6, you had to use the ``isGranted()`` method of the | ||
``security.context`` service. | ||
|
||
It's that easy! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove one of the blank lines