diff --git a/cookbook/security/abstract_voter.rst.inc b/cookbook/security/abstract_voter.rst.inc new file mode 100644 index 00000000000..3f1319bd01b --- /dev/null +++ b/cookbook/security/abstract_voter.rst.inc @@ -0,0 +1,35 @@ +.. code-block:: php + + abstract class AbstractVoter implements VoterInterface + { + public function supportsAttribute($attribute); + public function supportsClass($class); + public function vote(TokenInterface $token, $object, array $attributes); + + abstract protected function getSupportedClasses(); + abstract protected function getSupportedAttributes(); + abstract protected function isGranted($attribute, $object, $user = null); + } + +Behind the scenes this class implements the +:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, +which has this structure: + +.. include:: /cookbook/security/voter_interface.rst.inc + +The basic functionality covering common use cases is provided +and developer is expected to implement the abstract methods. + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedClasses` +method 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 ['\Acme\DemoBundle\Model\Product'], +Symfony will call your voter when a `Product` object is passed to `isGranted`. + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedAttributes` +method tells Symfony that your voter should be called whenever one of these strings is passes as the +first argument to `isGranted`. For example, if you return `array('CREATE', 'READ')`, then +Symfony will call your voter when one of these is passed to `isGranted`. + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::isGranted` +method must implement the business logic that verifies whether or not a given +user is allowed access to a given attribute on a given object. This method must return a boolean. diff --git a/cookbook/security/voters.rst b/cookbook/security/voters.rst index 236ff9c166e..94c5b343a53 100644 --- a/cookbook/security/voters.rst +++ b/cookbook/security/voters.rst @@ -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. + Declaring the Voter as a Service -------------------------------- diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 31bf0b3de99..2f101b1dc1d 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -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,66 @@ 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: + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedClasses` +method 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 ['\Acme\DemoBundle\Model\Product'], +Symfony will call your voter when a `Product` object is passed to `isGranted`. + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedAttributes` +method tells Symfony that your voter should be called whenever one of these strings is passes as the +first argument to `isGranted`. For example, if you return `array('CREATE', 'READ')`, then +Symfony will call your voter when one of these is passed to `isGranted`. + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::isGranted` +method must implement the business logic that verifies whether or not a given +user is allowed access to a given attribute on a given object. This method must return a boolean. + Declaring the Voter as a Service --------------------------------
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: