From 28c6536cfae7f995bd65d08725dabafe03b9dd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Marint=C5=A1enko?= Date: Tue, 23 Sep 2014 07:53:08 +0300 Subject: [PATCH 1/4] add & update doc entries on AbstractVoter implementation --- cookbook/security/abstract_voter.rst.inc | 31 ++++++++ cookbook/security/voters.rst | 7 ++ cookbook/security/voters_data_permission.rst | 84 ++++++-------------- 3 files changed, 61 insertions(+), 61 deletions(-) create mode 100644 cookbook/security/abstract_voter.rst.inc diff --git a/cookbook/security/abstract_voter.rst.inc b/cookbook/security/abstract_voter.rst.inc new file mode 100644 index 00000000000..f12d1d9619a --- /dev/null +++ b/cookbook/security/abstract_voter.rst.inc @@ -0,0 +1,31 @@ +.. 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 end developer is expected to implement the abstract methods. + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedClasses` +method is used to provide an array of supported classes, i.e. ['\Acme\DemoBundle\Model\Product'] + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedAttributes` +method is used to provide an array of supported attributes, i.e. ['CREATE', 'READ'] + +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 a given attribute on a given object. This method must return a boolean. \ No newline at end of file diff --git a/cookbook/security/voters.rst b/cookbook/security/voters.rst index 6d39cee8ec3..289d85411bd 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 35442dd5afb..566d53fad16 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -38,10 +38,11 @@ The Voter Interface ------------------- A custom voter must implement -:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, -which has this structure: +:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface` +and an :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter` +class is provided with following structure: -.. include:: /cookbook/security/voter_interface.rst.inc +.. include:: /cookbook/security/abstract_voter.rst.inc 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,84 +62,45 @@ edit a particular object. Here's an example implementation: // src/Acme/DemoBundle/Security/Authorization/Voter/PostVoter.php namespace Acme\DemoBundle\Security\Authorization\Voter; - use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 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 = 'Acme\DemoBundle\Entity\Post'; - - return $supportedClass === $class || is_subclass_of($class, $supportedClass); + return array('Acme\DemoBundle\Entity\Post'); } - /** - * @var \Acme\DemoBundle\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; } } From da32a5e6c1ad32b55a755d6447913308026c84b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Marint=C5=A1enko?= Date: Tue, 30 Sep 2014 07:54:27 +0300 Subject: [PATCH 2/4] fix problems pointed out by @javiereguiluz and @cordoval --- cookbook/security/abstract_voter.rst.inc | 4 ++-- cookbook/security/voters_data_permission.rst | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cookbook/security/abstract_voter.rst.inc b/cookbook/security/abstract_voter.rst.inc index f12d1d9619a..dffd90b4a00 100644 --- a/cookbook/security/abstract_voter.rst.inc +++ b/cookbook/security/abstract_voter.rst.inc @@ -18,7 +18,7 @@ which has this structure: .. include:: /cookbook/security/voter_interface.rst.inc The basic functionality covering common use cases is provided -and end developer is expected to implement the abstract methods. +and developer is expected to implement the abstract methods. The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter::getSupportedClasses` method is used to provide an array of supported classes, i.e. ['\Acme\DemoBundle\Model\Product'] @@ -28,4 +28,4 @@ method is used to provide an array of supported attributes, i.e. ['CREATE', 'REA 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 a given attribute on a given object. This method must return a boolean. \ No newline at end of file +user is allowed access to a given attribute on a given object. This method must return a boolean. diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 566d53fad16..c574f00ea77 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -63,7 +63,6 @@ edit a particular object. Here's an example implementation: namespace Acme\DemoBundle\Security\Authorization\Voter; use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; - use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; class PostVoter extends AbstractVoter From b1a90ba63409f113f5fbfe75cb5c7e7185264025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Marint=C5=A1enko?= Date: Sat, 31 Jan 2015 11:11:56 +0200 Subject: [PATCH 3/4] add fixes to data_permission cookbook --- cookbook/security/voters_data_permission.rst | 31 +++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index a23a18c9b40..2f101b1dc1d 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -37,12 +37,19 @@ For more information take a look at The Voter Interface ------------------- -A custom voter must implement +A custom voter needs to implement :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface` -and an :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter` -class is provided with following structure: +or extend :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter`, +which makes creating a voter even easier. -.. include:: /cookbook/security/abstract_voter.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 @@ -106,6 +113,22 @@ edit a particular object. Here's an example implementation: 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 -------------------------------- From 36eabcaaeae4f9bee721cbbc71834195fd4ae2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Marint=C5=A1enko?= Date: Sat, 31 Jan 2015 11:13:35 +0200 Subject: [PATCH 4/4] add fixes to abstract_voter include file --- cookbook/security/abstract_voter.rst.inc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cookbook/security/abstract_voter.rst.inc b/cookbook/security/abstract_voter.rst.inc index dffd90b4a00..3f1319bd01b 100644 --- a/cookbook/security/abstract_voter.rst.inc +++ b/cookbook/security/abstract_voter.rst.inc @@ -21,10 +21,14 @@ 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 is used to provide an array of supported classes, i.e. ['\Acme\DemoBundle\Model\Product'] +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 is used to provide an array of supported attributes, i.e. ['CREATE', 'READ'] +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