From 31041fa09a2ba8d71a4c335c5a98ad654d680da3 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/6] 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 | 141 +++++++++---------- 3 files changed, 101 insertions(+), 78 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 835d15e409b..b343482ed05 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 e17565ea218..27601c7bc82 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -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 + ``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 + 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('

'.$post->getName().'

'); } } .. 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! From 79a220422afed8fa4b9d2142478f73a61cc967aa 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/6] fix problems pointed out by @javiereguiluz and @cordoval --- cookbook/security/abstract_voter.rst.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 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. From 7619944f88965264da754d7d4ad7a70e12c0c84a 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 3/6] 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 From f47cf37a1318c67535aa06333530461e1da74fea Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 22 Jun 2015 13:30:59 +0200 Subject: [PATCH 4/6] Removed the abstract_voter.rst.inc file --- cookbook/security/abstract_voter.rst.inc | 35 ------------------------ 1 file changed, 35 deletions(-) delete mode 100644 cookbook/security/abstract_voter.rst.inc diff --git a/cookbook/security/abstract_voter.rst.inc b/cookbook/security/abstract_voter.rst.inc deleted file mode 100644 index 3f1319bd01b..00000000000 --- a/cookbook/security/abstract_voter.rst.inc +++ /dev/null @@ -1,35 +0,0 @@ -.. 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. From e680e7768e92abc89daf10280119264ea14f2bf6 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 23 Jun 2015 10:08:05 +0200 Subject: [PATCH 5/6] Fixed some typos --- cookbook/security/voters.rst | 5 ++--- cookbook/security/voters_data_permission.rst | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cookbook/security/voters.rst b/cookbook/security/voters.rst index b343482ed05..531971025e5 100644 --- a/cookbook/security/voters.rst +++ b/cookbook/security/voters.rst @@ -92,7 +92,6 @@ 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 @@ -204,8 +203,8 @@ application configuration file with the following code. That's it! Now, when deciding whether or not a user should have access, the new voter will deny access to any user in the list of blacklisted IPs. -Note that the voters are only called, if any access is actually checked. So -you need at least something like +Note that the voters are only called, if any access is actually checked. So +you need at least something like .. configuration-block:: diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 27601c7bc82..0f6ac1a0d3d 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -117,13 +117,13 @@ 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 + of the given classes is passed to ``isGranted()``. For example, if you return ``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 + strings is passed 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()``. From 695466c8be49e25d211425ebfbd5663a6f0cff11 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 24 Jun 2015 08:58:41 +0200 Subject: [PATCH 6/6] Added a link to the AbstractVoter cookbook --- cookbook/security/voters.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cookbook/security/voters.rst b/cookbook/security/voters.rst index 531971025e5..df575951f0e 100644 --- a/cookbook/security/voters.rst +++ b/cookbook/security/voters.rst @@ -94,9 +94,8 @@ the security layer. This can be done easily through the service container. .. tip:: - An - :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AbstractVoter` - is provided to cover the common use cases when implementing security voters. + An :doc:`AbstractVoter ` is + provided to cover the common use cases when implementing security voters. Declaring the Voter as a Service --------------------------------