From 2bda150bbc696c528f17fcf189544cd6c302eb4d Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Thu, 31 Oct 2013 11:40:00 +0000 Subject: [PATCH 01/15] create voters_data_permission.rst article --- cookbook/security/voters_data_permission.rst | 173 +++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 cookbook/security/voters_data_permission.rst diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst new file mode 100644 index 00000000000..39d498f1621 --- /dev/null +++ b/cookbook/security/voters_data_permission.rst @@ -0,0 +1,173 @@ +.. index:: + single: Security; Data Permission Voters + +How to implement your own Voter to check the permission for a object agains a user +================================================================================== + +In Symfony2 you can check the permission to access data by the +:doc:`ACL module ` which is a bit overhelming +for many applications. A much easier solution is working with custom +voters, which are like simple conditional statements. Voters can be +also used to check for permission as a part or even the whole +application: :doc:`cookbook/security/voters`. + +.. tip:: + + It is good to understand the basics about what and how + :doc:`authorization ` works. + +How symfony works with voters +----------------------------- + +In order to use voters you have to understand how symfony works with them. +In general all registered custom voters will be called every time you ask +symfony about permission (ACL). In general there are three different +approaches on how to handle the feedback from all voters: +:ref:`components-security-access-decision-manager`. + +The Voter Interface +------------------- + +A custom voter must implement +:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, +which requires the following three methods: + +.. code-block:: php + + interface VoterInterface + { + public function supportsAttribute($attribute); + public function supportsClass($class); + public function vote(TokenInterface $token, $object, array $attributes); + } + +The ``supportsAttribute()`` method is used to check if the voter supports +the given user attribute (i.e: a role, an acl, etc.). + +The ``supportsClass()`` method is used to check if the voter supports the +current user token class. + +The ``vote()`` method must implement the business logic that verifies whether +or not the user is granted access. This method must return one of the following +values: + +* ``VoterInterface::ACCESS_GRANTED``: The user is allowed to access the application +* ``VoterInterface::ACCESS_ABSTAIN``: The voter cannot decide if the user is granted or not +* ``VoterInterface::ACCESS_DENIED``: The user is not allowed to access the application + +In this example, you'll check if the user will have access to a specific object according to your custom conditions (e.g. he must be the owner of the object). If the condition fails, you'll return +``VoterInterface::ACCESS_DENIED``, otherwise you'll return +``VoterInterface::ACCESS_GRANTED``. In case the responsebility for this decision belong not to this voter, he will return +``VoterInterface::ACCESS_ABSTAIN``. + +Creating the Custom Voter +------------------------- + +You could store your Voter for the view and edit method of a post within ACME/DemoBundle/Security/Authorization/Document/PostVoter.php. + +.. code-block:: php + + // src/Acme/DemoBundle/Security/Authorization/Document/PostVoter.php + namespace Acme\DemoBundle\Security\Authorization\Document; + + use Symfony\Component\DependencyInjection\ContainerInterface; + use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + + class PostVoter implements VoterInterface + { + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function supportsAttribute($attribute) + { + return in_array($attribute, array( + 'view', + 'edit' + )); + } + + public function supportsClass($class) + { + // could be "ACME\DemoBundle\Entity\Post" as well + $array = array("ACME\DemoBundle\Document\Post"); + + foreach ($array as $item) { + // check with stripos in case doctrine is using a proxy class for this object + if (stripos($s, $item) !== FALSE) { + return true; + } + } + return false; + } + + public function vote(TokenInterface $token, $object, array $attributes) + { + // get current logged in user + $user = $token->getUser(); + + // check if class of this object is supported by this voter + if ( !($this->supportsClass(get_class($object))) ) { + return VoterInterface::ACCESS_ABSTAIN; + } + + // check if the given attribute is covered by this voter + foreach ($attributes as $attribute) { + if ( !$this->supportsAttribute($attribute) ) { + return VoterInterface::ACCESS_ABSTAIN; + } + } + + // check if given user is instance of user interface + if ( !($user instanceof UserInterface) ) { + return VoterInterface::ACCESS_DENIED; + } + + switch($this->attributes[0]) { + + case 'view': + if($object->isPrivate() === false) { + return VoterInterface::ACCESS_GRANTED; + } + break; + + case 'edit': + if($object->getOwner()->getId() === $user->getId()) { + return VoterInterface::ACCESS_GRANTED; + } + break; + + default: + // otherwise denied access + return VoterInterface::ACCESS_DENIED; + } + + } + } + +That's it! The voter is done. The next step is to inject the voter into +the security layer. This can be done easily through the service container. + +Declaring the Voter as a Service +-------------------------------- + +To inject the voter into the security layer, you must declare it as a service, +and tag it as a "security.voter": + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/AcmeBundle/Resources/config/services.yml + services: + security.access.post_document_voter: + class: Acme\DemoBundle\Security\Authorization\Document\PostVoter + public: false + arguments: [@service_container] + # we need to assign this service to be a security voter + tags: + - { name: security.voter } \ No newline at end of file From 99b1b0f49a2da5448ab7f72a778111ffbdb816b6 Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Thu, 31 Oct 2013 21:31:50 +0000 Subject: [PATCH 02/15] a couple of changes according to the comments, not finished now --- cookbook/security/voters_data_permission.rst | 84 ++++++++++---------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 39d498f1621..98fc91b286f 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -1,36 +1,36 @@ .. index:: single: Security; Data Permission Voters -How to implement your own Voter to check the permission for a object agains a user -================================================================================== +How to implement your own Voter to check user permissions for accessing a given object +====================================================================================== In Symfony2 you can check the permission to access data by the -:doc:`ACL module ` which is a bit overhelming -for many applications. A much easier solution is working with custom +:doc:`ACL module `, which is a bit overwhelming +for many applications. A much easier solution is to work with custom voters voters, which are like simple conditional statements. Voters can be also used to check for permission as a part or even the whole -application: :doc:`cookbook/security/voters`. +application: :doc:`"/cookbook/security/voters"`. .. tip:: It is good to understand the basics about what and how :doc:`authorization ` works. -How symfony works with voters ------------------------------ +How Symfony Uses Voters +----------------------- -In order to use voters you have to understand how symfony works with them. -In general all registered custom voters will be called every time you ask -symfony about permission (ACL). In general there are three different +In order to use voters, you have to understand how Symfony works with them. +In general, all registered custom voters will be called every time you ask +Symfony about permissions (ACL). In general there are three different approaches on how to handle the feedback from all voters: -:ref:`components-security-access-decision-manager`. +:ref:`"components-security-access-decision-manager"`. The Voter Interface ------------------- A custom voter must implement :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, -which requires the following three methods: +which has this structure: .. code-block:: php @@ -55,53 +55,52 @@ values: * ``VoterInterface::ACCESS_ABSTAIN``: The voter cannot decide if the user is granted or not * ``VoterInterface::ACCESS_DENIED``: The user is not allowed to access the application -In this example, you'll check if the user will have access to a specific object according to your custom conditions (e.g. he must be the owner of the object). If the condition fails, you'll return +In this example, you'll check if the user will have access to a specific +object according to your custom conditions (e.g. he must be the owner of +the object). If the condition fails, you'll return ``VoterInterface::ACCESS_DENIED``, otherwise you'll return -``VoterInterface::ACCESS_GRANTED``. In case the responsebility for this decision belong not to this voter, he will return -``VoterInterface::ACCESS_ABSTAIN``. +``VoterInterface::ACCESS_GRANTED``. In case the responsibility for this decision +belongs not to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN``. Creating the Custom Voter ------------------------- -You could store your Voter for the view and edit method of a post within ACME/DemoBundle/Security/Authorization/Document/PostVoter.php. +You could store your Voter to check permission for the view and edit action like following. .. code-block:: php - // src/Acme/DemoBundle/Security/Authorization/Document/PostVoter.php - namespace Acme\DemoBundle\Security\Authorization\Document; + // src/Acme/DemoBundle/Security/Authorization/Entity/PostVoter.php + namespace Acme\DemoBundle\Security\Authorization\Entity; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Core\User\UserInterface; class PostVoter implements VoterInterface { - private $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } public function supportsAttribute($attribute) { - return in_array($attribute, array( - 'view', - 'edit' - )); + return in_array($attribute, array( + 'view', + 'edit', + )); } public function supportsClass($class) { - // could be "ACME\DemoBundle\Entity\Post" as well - $array = array("ACME\DemoBundle\Document\Post"); + // could be "Acme\DemoBundle\Entity\Post" as well + $array = array("Acme\DemoBundle\Entity\Post"); foreach ($array as $item) { // check with stripos in case doctrine is using a proxy class for this object - if (stripos($s, $item) !== FALSE) { + if (stripos($s, $item) !== false) { + return true; } } + return false; } @@ -111,32 +110,36 @@ You could store your Voter for the view and edit method of a post within ACME/De $user = $token->getUser(); // check if class of this object is supported by this voter - if ( !($this->supportsClass(get_class($object))) ) { + if (!($this->supportsClass(get_class($object)))) { + return VoterInterface::ACCESS_ABSTAIN; } // check if the given attribute is covered by this voter foreach ($attributes as $attribute) { - if ( !$this->supportsAttribute($attribute) ) { + if (!$this->supportsAttribute($attribute)) { + return VoterInterface::ACCESS_ABSTAIN; } } // check if given user is instance of user interface - if ( !($user instanceof UserInterface) ) { + if (!($user instanceof UserInterface)) { + return VoterInterface::ACCESS_DENIED; } switch($this->attributes[0]) { - case 'view': - if($object->isPrivate() === false) { + if ($object->isPrivate() === false) { + return VoterInterface::ACCESS_GRANTED; } break; case 'edit': - if($object->getOwner()->getId() === $user->getId()) { + if ($user->getId() === $object->getOwner()->getId()) { + return VoterInterface::ACCESS_GRANTED; } break; @@ -164,10 +167,9 @@ and tag it as a "security.voter": # src/Acme/AcmeBundle/Resources/config/services.yml services: - security.access.post_document_voter: - class: Acme\DemoBundle\Security\Authorization\Document\PostVoter + security.access.post_voter: + class: Acme\DemoBundle\Security\Authorization\Entity\PostVoter public: false - arguments: [@service_container] - # we need to assign this service to be a security voter + # the service gets tagged as a voter tags: - { name: security.voter } \ No newline at end of file From 1466fa77f36c8ba0f26d31b396845061cabc4d05 Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Tue, 5 Nov 2013 09:05:46 +0000 Subject: [PATCH 03/15] improvements according to the reviews --- cookbook/security/voters_data_permission.rst | 139 +++++++++++++------ 1 file changed, 96 insertions(+), 43 deletions(-) diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 98fc91b286f..cce46f58765 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -1,28 +1,29 @@ .. index:: single: Security; Data Permission Voters -How to implement your own Voter to check user permissions for accessing a given object +How to implement your own Voter to check User Permissions for accessing a Given Object ====================================================================================== -In Symfony2 you can check the permission to access data by the -:doc:`ACL module `, which is a bit overwhelming -for many applications. A much easier solution is to work with custom voters -voters, which are like simple conditional statements. Voters can be -also used to check for permission as a part or even the whole +In Symfony2 you can check the permission to access data by the +:doc:`ACL module `, which is a bit overwhelming +for many applications. A much easier solution is to work with custom voters, +which are like simple conditional statements. Voters can be +also be used to check for permission as a part or even the whole application: :doc:`"/cookbook/security/voters"`. .. tip:: It is good to understand the basics about what and how - :doc:`authorization ` works. + :doc:`authorization ` works. // correct link in book? -How Symfony Uses Voters +How Symfony uses Voters ----------------------- In order to use voters, you have to understand how Symfony works with them. -In general, all registered custom voters will be called every time you ask -Symfony about permissions (ACL). In general there are three different -approaches on how to handle the feedback from all voters: +In general, all registered custom voters will be called every time you ask +Symfony about permissions (ACL). You can use one of three different +approaches on how to handle the feedback from all voters: affirmative, +consensus and unanimous. For more information have a look at :ref:`"components-security-access-decision-manager"`. The Voter Interface @@ -32,13 +33,13 @@ A custom voter must implement :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, which has this structure: -.. code-block:: php +.. code-block:: php // :: shortcut? and put the snippet (to line 56) in a single file an reference ? interface VoterInterface { public function supportsAttribute($attribute); public function supportsClass($class); - public function vote(TokenInterface $token, $object, array $attributes); + public function vote(TokenInterface $token, $post, array $attributes); } The ``supportsAttribute()`` method is used to check if the voter supports @@ -60,7 +61,7 @@ object according to your custom conditions (e.g. he must be the owner of the object). If the condition fails, you'll return ``VoterInterface::ACCESS_DENIED``, otherwise you'll return ``VoterInterface::ACCESS_GRANTED``. In case the responsibility for this decision -belongs not to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN``. +does not belong to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN``. Creating the Custom Voter ------------------------- @@ -72,81 +73,86 @@ You could store your Voter to check permission for the view and edit action like // src/Acme/DemoBundle/Security/Authorization/Entity/PostVoter.php namespace Acme\DemoBundle\Security\Authorization\Entity; + use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; + use Doctrine\Common\Util\ClassUtils; class PostVoter implements VoterInterface { - - public function supportsAttribute($attribute) + public function supportsAttribute($attribute) { return in_array($attribute, array( 'view', 'edit', )); } - - public function supportsClass($class) + + public function supportsClass($obj) { - // could be "Acme\DemoBundle\Entity\Post" as well - $array = array("Acme\DemoBundle\Entity\Post"); - + $array = array('Acme\DemoBundle\Entity\Post'); + foreach ($array as $item) { // check with stripos in case doctrine is using a proxy class for this object - if (stripos($s, $item) !== false) { - + // if (stripos($s, $item) !== false) { + if ($obj instanceof $item)) // check if this will also check for interfaces etc. like it should be in oop (inheritace) + // or return $targetClass === $class || is_subclass_of($class, $targetClass); return true; } } return false; } - - public function vote(TokenInterface $token, $object, array $attributes) + + /** @var \Acme\DemoBundle\Entity\Post $post */ + public function vote(TokenInterface $token, $post, array $attributes) // remove array { + // always get the first attribute + $attribute = $attributes[0]; + // get current logged in user $user = $token->getUser(); - + // check if class of this object is supported by this voter - if (!($this->supportsClass(get_class($object)))) { + if (!($this->supportsClass($post))) { // maybe without ClassUtils::getRealClass( return VoterInterface::ACCESS_ABSTAIN; } - + // check if the given attribute is covered by this voter - foreach ($attributes as $attribute) { - if (!$this->supportsAttribute($attribute)) { + if (!$this->supportsAttribute($attribute)) { - return VoterInterface::ACCESS_ABSTAIN; - } + return VoterInterface::ACCESS_ABSTAIN; } - + // check if given user is instance of user interface if (!($user instanceof UserInterface)) { return VoterInterface::ACCESS_DENIED; } - - switch($this->attributes[0]) { + + switch($attribute) { case 'view': - if ($object->isPrivate() === false) { + // the data object could have for e.g. a method isPrivate() which checks the the boolean attribute $private + if (!$post->isPrivate()) { return VoterInterface::ACCESS_GRANTED; } break; - + case 'edit': - if ($user->getId() === $object->getOwner()->getId()) { + // 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; - + default: - // otherwise denied access - return VoterInterface::ACCESS_DENIED; + // otherwise throw an exception + throw new PreconditionFailedHttpException('The Attribute "'.$attribute.'"" was not found.') } } @@ -170,6 +176,53 @@ and tag it as a "security.voter": security.access.post_voter: class: Acme\DemoBundle\Security\Authorization\Entity\PostVoter public: false - # the service gets tagged as a voter tags: - - { name: security.voter } \ No newline at end of file + - { name: security.voter } + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + $container + ->register('security.access.post_document_voter', 'Acme\DemoBundle\Security\Authorization\Document\PostVoter') + ->addTag('security.voter') + ; + +How to use the Voter in a Controller +------------------------------------ + +.. code-block:: php + + // src/Acme/DemoBundle/Controller/PostController.php + namespace Acme\DemoBundle\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Security\Core\Exception\AccessDeniedException; + + class PostController + { + public function showAction($id) + { + // keep in mind, this will call all registered security voters + if (false === $this->get('security.context')->isGranted('view')) { + throw new AccessDeniedException('Unauthorised access!'); + } + + $product = $this->getDoctrine() + ->getRepository('AcmeStoreBundle:Post') + ->find($id); + + return new Response('Headline for Post: '.$post->getName().''); + } + } From 731dcad72ea7e0e2b0f27cc693d01e47b6e805f3 Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Tue, 12 Nov 2013 12:16:32 +0000 Subject: [PATCH 04/15] updated page with suggestion from the review --- cookbook/security/voters_data_permission.rst | 29 ++++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index cce46f58765..07921c873e2 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -13,8 +13,8 @@ application: :doc:`"/cookbook/security/voters"`. .. tip:: - It is good to understand the basics about what and how - :doc:`authorization ` works. // correct link in book? + Have a look at the referenced page if you are not familiar with + :doc:`authorization `. How Symfony uses Voters ----------------------- @@ -33,7 +33,9 @@ A custom voter must implement :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, which has this structure: -.. code-block:: php // :: shortcut? and put the snippet (to line 56) in a single file an reference ? +// how to put this following snippet (to line 56) in a single file an embed it? as it is used in voters.rst as well. + +.. code-block:: php interface VoterInterface { @@ -95,10 +97,8 @@ You could store your Voter to check permission for the view and edit action like $array = array('Acme\DemoBundle\Entity\Post'); foreach ($array as $item) { - // check with stripos in case doctrine is using a proxy class for this object - // if (stripos($s, $item) !== false) { - if ($obj instanceof $item)) // check if this will also check for interfaces etc. like it should be in oop (inheritace) - // or return $targetClass === $class || is_subclass_of($class, $targetClass); + if ($obj instanceof $item)) + return true; } } @@ -107,16 +107,21 @@ You could store your Voter to check permission for the view and edit action like } /** @var \Acme\DemoBundle\Entity\Post $post */ - public function vote(TokenInterface $token, $post, array $attributes) // remove array + public function vote(TokenInterface $token, $post, array $attributes) { - // always get the first attribute + // check if voter is used correct, only allow one attribute for a check + if(count($attributes) !== 1 || !is_string($attributes[0])) { + throw new PreconditionFailedHttpException('The Attribute was not set correct. Maximum 1 attribute.'); + } + + // set the attribute to check against $attribute = $attributes[0]; // get current logged in user $user = $token->getUser(); // check if class of this object is supported by this voter - if (!($this->supportsClass($post))) { // maybe without ClassUtils::getRealClass( + if (!($this->supportsClass($post))) { return VoterInterface::ACCESS_ABSTAIN; } @@ -151,8 +156,8 @@ You could store your Voter to check permission for the view and edit action like break; default: - // otherwise throw an exception - throw new PreconditionFailedHttpException('The Attribute "'.$attribute.'"" was not found.') + // otherwise throw an exception, which will break the request + throw new PreconditionFailedHttpException('The Attribute "'.$attribute.'" was not found.') } } From ee0def1cca8e6e936d0152696612a02109c13a6e Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Tue, 12 Nov 2013 12:28:35 +0000 Subject: [PATCH 05/15] improved tip box with additional link to /cookbook/security --- cookbook/security/voters_data_permission.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 07921c873e2..67d36da3fd9 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -13,8 +13,10 @@ application: :doc:`"/cookbook/security/voters"`. .. tip:: - Have a look at the referenced page if you are not familiar with - :doc:`authorization `. + Have a look at the pages + :doc:`security ` and + :doc:`authorization ` + if you are not familiar with these topics. How Symfony uses Voters ----------------------- From f4eb5f3482158bdd5715759dca542ea9d56949c0 Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Tue, 26 Nov 2013 18:56:58 +0000 Subject: [PATCH 06/15] updated docs according to the reviews --- cookbook/security/voter_interface.rst.inc | 22 +++++ cookbook/security/voters.rst | 23 +----- cookbook/security/voters_data_permission.rst | 87 ++++++++------------ 3 files changed, 56 insertions(+), 76 deletions(-) create mode 100644 cookbook/security/voter_interface.rst.inc diff --git a/cookbook/security/voter_interface.rst.inc b/cookbook/security/voter_interface.rst.inc new file mode 100644 index 00000000000..17bf06ece17 --- /dev/null +++ b/cookbook/security/voter_interface.rst.inc @@ -0,0 +1,22 @@ +.. code-block:: php + + interface VoterInterface + { + public function supportsAttribute($attribute); + public function supportsClass($class); + public function vote(TokenInterface $token, $post, array $attributes); + } + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsAttribute` method is used to check if the voter supports +the given user attribute (i.e: a role, an ACL, etc.). + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsClass` method is used to check if the voter supports the +class of the object whose access is being checked (doesn't apply to this entry). + +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::vote` method must implement the business logic that verifies whether +or not the user is granted access. This method must return one of the following +values: + +* ``VoterInterface::ACCESS_GRANTED``: The authorization will be granted by this voter; +* ``VoterInterface::ACCESS_ABSTAIN``: The voter cannot decide if authorization should be granted; +* ``VoterInterface::ACCESS_DENIED``: The authorization will be denied by this voter. diff --git a/cookbook/security/voters.rst b/cookbook/security/voters.rst index b66606eee1f..9f07ba49b33 100644 --- a/cookbook/security/voters.rst +++ b/cookbook/security/voters.rst @@ -21,28 +21,7 @@ A custom voter must implement :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, which requires the following three methods: -.. code-block:: php - - interface VoterInterface - { - public function supportsAttribute($attribute); - public function supportsClass($class); - public function vote(TokenInterface $token, $object, array $attributes); - } - -The ``supportsAttribute()`` method is used to check if the voter supports -the given user attribute (i.e: a role, an ACL, etc.). - -The ``supportsClass()`` method is used to check if the voter supports the -class of the object whose access is being checked (doesn't apply to this entry). - -The ``vote()`` method must implement the business logic that verifies whether -or not the user is granted access. This method must return one of the following -values: - -* ``VoterInterface::ACCESS_GRANTED``: The authorization will be granted by this voter; -* ``VoterInterface::ACCESS_ABSTAIN``: The voter cannot decide if authorization should be granted; -* ``VoterInterface::ACCESS_DENIED``: The authorization will be denied by this voter. +.. include:: /cookbook/security/voter_interface.rst.inc In this example, you'll check if the user's IP address matches against a list of blacklisted addresses and "something" will be the application. If the user's IP is blacklisted, you'll return diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 67d36da3fd9..b674c6a5951 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -1,24 +1,23 @@ .. index:: single: Security; Data Permission Voters -How to implement your own Voter to check User Permissions for accessing a Given Object -====================================================================================== +How to Use Voters to Check User Permissions +=========================================== -In Symfony2 you can check the permission to access data by the +In Symfony2 you can check the permission to access data by using the :doc:`ACL module `, which is a bit overwhelming for many applications. A much easier solution is to work with custom voters, which are like simple conditional statements. Voters can be also be used to check for permission as a part or even the whole -application: :doc:`"/cookbook/security/voters"`. +application: ":doc:`/cookbook/security/voters`". .. tip:: - Have a look at the pages - :doc:`security ` and + Have a look at the chapter :doc:`authorization ` - if you are not familiar with these topics. + for a better understanding on voters. -How Symfony uses Voters +How Symfony Uses Voters ----------------------- In order to use voters, you have to understand how Symfony works with them. @@ -26,7 +25,7 @@ In general, all registered custom voters will be called every time you ask Symfony about permissions (ACL). You can use one of three different approaches on how to handle the feedback from all voters: affirmative, consensus and unanimous. For more information have a look at -:ref:`"components-security-access-decision-manager"`. +":ref:`components-security-access-decision-manager`". The Voter Interface ------------------- @@ -35,30 +34,7 @@ A custom voter must implement :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, which has this structure: -// how to put this following snippet (to line 56) in a single file an embed it? as it is used in voters.rst as well. - -.. code-block:: php - - interface VoterInterface - { - public function supportsAttribute($attribute); - public function supportsClass($class); - public function vote(TokenInterface $token, $post, array $attributes); - } - -The ``supportsAttribute()`` method is used to check if the voter supports -the given user attribute (i.e: a role, an acl, etc.). - -The ``supportsClass()`` method is used to check if the voter supports the -current user token class. - -The ``vote()`` method must implement the business logic that verifies whether -or not the user is granted access. This method must return one of the following -values: - -* ``VoterInterface::ACCESS_GRANTED``: The user is allowed to access the application -* ``VoterInterface::ACCESS_ABSTAIN``: The voter cannot decide if the user is granted or not -* ``VoterInterface::ACCESS_DENIED``: The user is not allowed to access the application +.. include:: /cookbook/security/voter_interface.rst.inc In this example, you'll check if the user will have access to a specific object according to your custom conditions (e.g. he must be the owner of @@ -70,9 +46,7 @@ does not belong to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN`` Creating the Custom Voter ------------------------- -You could store your Voter to check permission for the view and edit action like following. - -.. code-block:: php +You could store your Voter to check permission for the view and edit action like the following:: // src/Acme/DemoBundle/Security/Authorization/Entity/PostVoter.php namespace Acme\DemoBundle\Security\Authorization\Entity; @@ -100,7 +74,6 @@ You could store your Voter to check permission for the view and edit action like foreach ($array as $item) { if ($obj instanceof $item)) - return true; } } @@ -113,7 +86,9 @@ You could store your Voter to check permission for the view and edit action like { // check if voter is used correct, only allow one attribute for a check if(count($attributes) !== 1 || !is_string($attributes[0])) { - throw new PreconditionFailedHttpException('The Attribute was not set correct. Maximum 1 attribute.'); + throw new PreconditionFailedHttpException( + 'Only one attribute is allowed for VIEW or EDIT' + ); } // set the attribute to check against @@ -123,43 +98,42 @@ You could store your Voter to check permission for the view and edit action like $user = $token->getUser(); // check if class of this object is supported by this voter - if (!($this->supportsClass($post))) { - + if (!$this->supportsClass($post)) { return VoterInterface::ACCESS_ABSTAIN; } // check if the given attribute is covered by this voter if (!$this->supportsAttribute($attribute)) { - return VoterInterface::ACCESS_ABSTAIN; } // check if given user is instance of user interface - if (!($user instanceof UserInterface)) { - + if (!$user instanceof UserInterface) { return VoterInterface::ACCESS_DENIED; } switch($attribute) { case 'view': - // the data object could have for e.g. a method isPrivate() which checks the the boolean attribute $private + // the data object could have for e.g. a method isPrivate() + // which checks the Boolean attribute $private if (!$post->isPrivate()) { - return VoterInterface::ACCESS_GRANTED; } break; case 'edit': - // we assume that our data object has a method getOwner() to get the current owner user entity for this data object + // 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; default: // otherwise throw an exception, which will break the request - throw new PreconditionFailedHttpException('The Attribute "'.$attribute.'" was not found.') + throw new PreconditionFailedHttpException( + 'The Attribute "'.$attribute.'" was not found.' + ); } } @@ -171,8 +145,8 @@ the security layer. This can be done easily through the service container. Declaring the Voter as a Service -------------------------------- -To inject the voter into the security layer, you must declare it as a service, -and tag it as a "security.voter": +To inject the voter into the security layer, you must declare it as a service +and tag it as a ´security.voter´: .. configuration-block:: @@ -202,12 +176,17 @@ and tag it as a "security.voter": .. code-block:: php $container - ->register('security.access.post_document_voter', 'Acme\DemoBundle\Security\Authorization\Document\PostVoter') + ->register( + 'security.access.post_document_voter', + 'Acme\DemoBundle\Security\Authorization\Document\PostVoter' + ) ->addTag('security.voter') ; -How to use the Voter in a Controller +How to Use the Voter in a Controller ------------------------------------ +The registered voter will then always be asked as soon the method isGranted from +the security context is called. .. code-block:: php @@ -222,7 +201,7 @@ How to use the Voter in a Controller public function showAction($id) { // keep in mind, this will call all registered security voters - if (false === $this->get('security.context')->isGranted('view')) { + if (false === $this->get('security.context')->isGranted('view', $post)) { throw new AccessDeniedException('Unauthorised access!'); } @@ -230,6 +209,6 @@ How to use the Voter in a Controller ->getRepository('AcmeStoreBundle:Post') ->find($id); - return new Response('Headline for Post: '.$post->getName().''); + return new Response('

'.$post->getName().'

'); } } From 52752309eb2472a0563e395c369700fac6c7e5ca Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Tue, 26 Nov 2013 19:00:02 +0000 Subject: [PATCH 07/15] updated with missing fixes --- cookbook/security/voters_data_permission.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index b674c6a5951..63000d5a865 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -36,7 +36,7 @@ which has this structure: .. include:: /cookbook/security/voter_interface.rst.inc -In this example, you'll check if the user will have access to a specific +In this example, it'll check if the user will have access to a specific object according to your custom conditions (e.g. he must be the owner of the object). If the condition fails, you'll return ``VoterInterface::ACCESS_DENIED``, otherwise you'll return @@ -140,7 +140,7 @@ You could store your Voter to check permission for the view and edit action like } That's it! The voter is done. The next step is to inject the voter into -the security layer. This can be done easily through the service container. +the security layer. Declaring the Voter as a Service -------------------------------- From 1fd3b0eb8d12ac8cf341497bcb59bf26d38ea89d Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Wed, 22 Jan 2014 11:12:07 +0000 Subject: [PATCH 08/15] updated docs according to the review --- cookbook/security/voter_interface.rst.inc | 15 +++--- cookbook/security/voters_data_permission.rst | 49 +++++++++----------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/cookbook/security/voter_interface.rst.inc b/cookbook/security/voter_interface.rst.inc index 17bf06ece17..c6719a59ccf 100644 --- a/cookbook/security/voter_interface.rst.inc +++ b/cookbook/security/voter_interface.rst.inc @@ -7,15 +7,16 @@ public function vote(TokenInterface $token, $post, array $attributes); } -The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsAttribute` method is used to check if the voter supports -the given user attribute (i.e: a role, an ACL, etc.). +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsAttribute` +method is used to check if the voter supports the given user attribute (i.e: a role, an ACL, etc.). -The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsClass` method is used to check if the voter supports the -class of the object whose access is being checked (doesn't apply to this entry). +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsClass` +method is used to check if the voter supports the class of the object whose +access is being checked (doesn't apply to this entry). -The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::vote` method must implement the business logic that verifies whether -or not the user is granted access. This method must return one of the following -values: +The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::vote` +method must implement the business logic that verifies whether or not the +user is granted access. This method must return one of the following values: * ``VoterInterface::ACCESS_GRANTED``: The authorization will be granted by this voter; * ``VoterInterface::ACCESS_ABSTAIN``: The voter cannot decide if authorization should be granted; diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 63000d5a865..a54717d63b0 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -51,7 +51,7 @@ You could store your Voter to check permission for the view and edit action like // src/Acme/DemoBundle/Security/Authorization/Entity/PostVoter.php namespace Acme\DemoBundle\Security\Authorization\Entity; - use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; + use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -60,23 +60,20 @@ You could store your Voter to check permission for the view and edit action like class PostVoter implements VoterInterface { + const VIEW = 'view'; + const EDIT = 'edit'; + public function supportsAttribute($attribute) { return in_array($attribute, array( - 'view', - 'edit', + self::VIEW, + self::EDIT, )); } public function supportsClass($obj) { - $array = array('Acme\DemoBundle\Entity\Post'); - - foreach ($array as $item) { - if ($obj instanceof $item)) - return true; - } - } + if ($obj instanceof 'Acme\DemoBundle\Entity\Post') return true; return false; } @@ -84,9 +81,14 @@ You could store your Voter to check permission for the view and edit action like /** @var \Acme\DemoBundle\Entity\Post $post */ public function vote(TokenInterface $token, $post, array $attributes) { + // check if class of this object is supported by this voter + if (!$this->supportsClass($post)) { + return VoterInterface::ACCESS_ABSTAIN; + } + // check if voter is used correct, only allow one attribute for a check if(count($attributes) !== 1 || !is_string($attributes[0])) { - throw new PreconditionFailedHttpException( + throw new InvalidArgumentException( 'Only one attribute is allowed for VIEW or EDIT' ); } @@ -97,11 +99,6 @@ You could store your Voter to check permission for the view and edit action like // get current logged in user $user = $token->getUser(); - // check if class of this object is supported by this voter - if (!$this->supportsClass($post)) { - return VoterInterface::ACCESS_ABSTAIN; - } - // check if the given attribute is covered by this voter if (!$this->supportsAttribute($attribute)) { return VoterInterface::ACCESS_ABSTAIN; @@ -128,12 +125,6 @@ You could store your Voter to check permission for the view and edit action like return VoterInterface::ACCESS_GRANTED; } break; - - default: - // otherwise throw an exception, which will break the request - throw new PreconditionFailedHttpException( - 'The Attribute "'.$attribute.'" was not found.' - ); } } @@ -146,7 +137,7 @@ Declaring the Voter as a Service -------------------------------- To inject the voter into the security layer, you must declare it as a service -and tag it as a ´security.voter´: +and tag it as a 'security.voter': .. configuration-block:: @@ -185,8 +176,9 @@ and tag it as a ´security.voter´: How to Use the Voter in a Controller ------------------------------------ -The registered voter will then always be asked as soon the method isGranted from -the security context is called. + +The registered voter will then always be asked as soon as the method 'isGranted' +from the security context is called. .. code-block:: php @@ -198,7 +190,12 @@ the security context is called. class PostController { - public function showAction($id) + + /** + * @Route("/blog/{id}") + * @ParamConverter("post", class="SensioBlogBundle:Post") + */ + public function showAction(Post $post) { // keep in mind, this will call all registered security voters if (false === $this->get('security.context')->isGranted('view', $post)) { From 872a05f4f47b71ec85415cbe1236d6c56aa38695 Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Wed, 22 Jan 2014 11:28:14 +0000 Subject: [PATCH 09/15] updated the link from ACL to the data permission voters --- cookbook/security/acl.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbook/security/acl.rst b/cookbook/security/acl.rst index 96f22a93d35..b7dbc71382a 100644 --- a/cookbook/security/acl.rst +++ b/cookbook/security/acl.rst @@ -14,7 +14,7 @@ the ACL system comes in. Using ACL's isn't trivial, and for simpler use cases, it may be overkill. If your permission logic could be described by just writing some code (e.g. to check if a Blog is owned by the current User), then consider using - :doc:`voters `. A voter is passed the object + :doc:`voters `. A voter is passed the object being voted on, which you can use to make complex decisions and effectively implement your own ACL. Enforcing authorization (e.g. the ``isGranted`` part) will look similar to what you see in this entry, but your voter From 9b915018adabdcc750785c0b011dfc14dcb9f455 Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Wed, 22 Jan 2014 17:57:57 +0000 Subject: [PATCH 10/15] updated the docs according to the last review --- cookbook/map.rst.inc | 1 + cookbook/security/acl.rst | 2 +- cookbook/security/index.rst | 1 + cookbook/security/voters_data_permission.rst | 47 +++++++++++--------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index b2777f42f5e..b4090d02854 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -127,6 +127,7 @@ * :doc:`/cookbook/security/remember_me` * :doc:`/cookbook/security/impersonating_user` * :doc:`/cookbook/security/voters` + * :doc:`/cookbook/security/voters_data_permission` * :doc:`/cookbook/security/acl` * :doc:`/cookbook/security/acl_advanced` * :doc:`/cookbook/security/force_https` diff --git a/cookbook/security/acl.rst b/cookbook/security/acl.rst index b7dbc71382a..53433337a22 100644 --- a/cookbook/security/acl.rst +++ b/cookbook/security/acl.rst @@ -14,7 +14,7 @@ the ACL system comes in. Using ACL's isn't trivial, and for simpler use cases, it may be overkill. If your permission logic could be described by just writing some code (e.g. to check if a Blog is owned by the current User), then consider using - :doc:`voters `. A voter is passed the object + :doc:`voters `. A voter is passed the object being voted on, which you can use to make complex decisions and effectively implement your own ACL. Enforcing authorization (e.g. the ``isGranted`` part) will look similar to what you see in this entry, but your voter diff --git a/cookbook/security/index.rst b/cookbook/security/index.rst index 4ba14308abb..9abede5ed68 100644 --- a/cookbook/security/index.rst +++ b/cookbook/security/index.rst @@ -8,6 +8,7 @@ Security remember_me impersonating_user voters + voters_data_permission acl acl_advanced force_https diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index a54717d63b0..a215bf52951 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -7,15 +7,15 @@ How to Use Voters to Check User Permissions In Symfony2 you can check the permission to access data by using the :doc:`ACL module `, which is a bit overwhelming for many applications. A much easier solution is to work with custom voters, -which are like simple conditional statements. Voters can be -also be used to check for permission as a part or even the whole -application: ":doc:`/cookbook/security/voters`". +which are like simple conditional statements. Voters can also be used to +check for permission to a part or even of the whole application: +":doc:`/cookbook/security/voters`". .. tip:: - Have a look at the chapter + Have a look at the :doc:`authorization ` - for a better understanding on voters. + chapter for a better understanding on voters. How Symfony Uses Voters ----------------------- @@ -25,7 +25,7 @@ In general, all registered custom voters will be called every time you ask Symfony about permissions (ACL). You can use one of three different approaches on how to handle the feedback from all voters: affirmative, consensus and unanimous. For more information have a look at -":ref:`components-security-access-decision-manager`". +":ref:`the section about access decision managers `". The Voter Interface ------------------- @@ -37,7 +37,7 @@ which has this structure: .. include:: /cookbook/security/voter_interface.rst.inc In this example, it'll check if the user will have access to a specific -object according to your custom conditions (e.g. he must be the owner of +object according to your custom conditions (e.g. they must be the owner of the object). If the condition fails, you'll return ``VoterInterface::ACCESS_DENIED``, otherwise you'll return ``VoterInterface::ACCESS_GRANTED``. In case the responsibility for this decision @@ -46,17 +46,17 @@ does not belong to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN`` Creating the Custom Voter ------------------------- -You could store your Voter to check permission for the view and edit action like the following:: +You could implement your Voter to check permission for the view and edit action like the following:: - // src/Acme/DemoBundle/Security/Authorization/Entity/PostVoter.php - namespace Acme\DemoBundle\Security\Authorization\Entity; + // src/Acme/DemoBundle/Security/Authorization/Voter/PostVoter.php + namespace Acme\DemoBundle\Security\Authorization\Voter; use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; - use Doctrine\Common\Util\ClassUtils; + use Acme\DemoBundle\Entity\Post; class PostVoter implements VoterInterface { @@ -73,7 +73,9 @@ You could store your Voter to check permission for the view and edit action like public function supportsClass($obj) { - if ($obj instanceof 'Acme\DemoBundle\Entity\Post') return true; + if ($obj instanceof Post) { + return true; + } return false; } @@ -137,16 +139,16 @@ Declaring the Voter as a Service -------------------------------- To inject the voter into the security layer, you must declare it as a service -and tag it as a 'security.voter': +and tag it as a ``security.voter``: .. configuration-block:: .. code-block:: yaml - # src/Acme/AcmeBundle/Resources/config/services.yml + # src/Acme/DemoBundle/Resources/config/services.yml services: security.access.post_voter: - class: Acme\DemoBundle\Security\Authorization\Entity\PostVoter + class: Acme\DemoBundle\Security\Authorization\Voter\PostVoter public: false tags: - { name: security.voter } @@ -154,10 +156,12 @@ and tag it as a 'security.voter': .. code-block:: xml - + @@ -166,10 +170,11 @@ and tag it as a 'security.voter': .. code-block:: php + // src/Acme/DemoBundle/Resources/config/services.php $container ->register( 'security.access.post_document_voter', - 'Acme\DemoBundle\Security\Authorization\Document\PostVoter' + 'Acme\DemoBundle\Security\Authorization\Voter\PostVoter' ) ->addTag('security.voter') ; @@ -177,7 +182,7 @@ and tag it as a 'security.voter': How to Use the Voter in a Controller ------------------------------------ -The registered voter will then always be asked as soon as the method 'isGranted' +The registered voter will then always be asked as soon as the method ``isGranted()`` from the security context is called. .. code-block:: php @@ -185,10 +190,12 @@ from the security context is called. // src/Acme/DemoBundle/Controller/PostController.php namespace Acme\DemoBundle\Controller; + use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AccessDeniedException; + use Acme\DemoBundle\Entity\Post; - class PostController + class PostController extends Controller { /** From da7b97e36a006ba55422a6c451652fe596e9ba9c Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Wed, 22 Jan 2014 18:00:44 +0000 Subject: [PATCH 11/15] missed one comment --- cookbook/security/voters_data_permission.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index a215bf52951..825de66f42d 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -155,6 +155,7 @@ and tag it as a ``security.voter``: .. code-block:: xml + Date: Wed, 19 Feb 2014 10:23:43 +0000 Subject: [PATCH 13/15] simplified the example --- cookbook/security/voters_data_permission.rst | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 55fbefcfa12..17154dce848 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -192,18 +192,14 @@ from the security context is called. use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AccessDeniedException; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; - use Acme\DemoBundle\Entity\Post; class PostController extends Controller { - /** - * @Route("/blog/{id}") - * @ParamConverter("post", class="SensioBlogBundle:Post") - */ - public function showAction(Post $post) + public function showAction() { + // get a Post instance + $post = ...; + // keep in mind, this will call all registered security voters if (false === $this->get('security.context')->isGranted('view', $post)) { throw new AccessDeniedException('Unauthorised access!'); From 2391758a9ee5638f4cdd199ba03136b4b5773c5e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 20 Feb 2014 14:34:55 -0600 Subject: [PATCH 14/15] [#2877][#3138] Proofreading the new voter data permission entry --- cookbook/security/voter_interface.rst.inc | 7 ++-- cookbook/security/voters_data_permission.rst | 43 ++++++++++++-------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/cookbook/security/voter_interface.rst.inc b/cookbook/security/voter_interface.rst.inc index c6719a59ccf..2250ce0529c 100644 --- a/cookbook/security/voter_interface.rst.inc +++ b/cookbook/security/voter_interface.rst.inc @@ -8,15 +8,16 @@ } The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsAttribute` -method is used to check if the voter supports the given user attribute (i.e: a role, an ACL, etc.). +method is used to check if the voter supports the given user attribute (i.e: +a role like ``ROLE_USER``, an ACL ``EDIT``, etc.). The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsClass` method is used to check if the voter supports the class of the object whose -access is being checked (doesn't apply to this entry). +access is being checked. The :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::vote` method must implement the business logic that verifies whether or not the -user is granted access. This method must return one of the following values: +user has access. This method must return one of the following values: * ``VoterInterface::ACCESS_GRANTED``: The authorization will be granted by this voter; * ``VoterInterface::ACCESS_ABSTAIN``: The voter cannot decide if authorization should be granted; diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 17154dce848..e6b01d12f45 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -7,25 +7,31 @@ How to Use Voters to Check User Permissions In Symfony2 you can check the permission to access data by using the :doc:`ACL module `, which is a bit overwhelming for many applications. A much easier solution is to work with custom voters, -which are like simple conditional statements. Voters can also be used to -check for permission to a part or even of the whole application: -":doc:`/cookbook/security/voters`". +which are like simple conditional statements. + +.. seealso:: + + Voters can also be used in other ways, like, for example, blacklisting IP + addresses from the entire application: ":doc:`/cookbook/security/voters`". .. tip:: - Have a look at the + Take a look at the :doc:`authorization ` - chapter for a better understanding on voters. + chapter for an even deeper understanding on voters. How Symfony Uses Voters ----------------------- In order to use voters, you have to understand how Symfony works with them. -In general, all registered custom voters will be called every time you ask -Symfony about permissions (ACL). You can use one of three different -approaches on how to handle the feedback from all voters: affirmative, -consensus and unanimous. For more information have a look at -":ref:`the section about access decision managers `". +All voters are called each time you use the ``isGranted()`` method on Symfony's +security context (i.e. the ``security.context`` service). Each decides if +the current user should have access to some resource. + +Ultimately, Symfony uses one of three different approaches on what to do +with the feedback from all voters: affirmative, consensus and unanimous. + +For more information take a look at ":ref:`the section about access decision managers `". The Voter Interface ------------------- @@ -36,7 +42,7 @@ which has this structure: .. include:: /cookbook/security/voter_interface.rst.inc -In this example, it'll check if the user will have access to a specific +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 the object). If the condition fails, you'll return ``VoterInterface::ACCESS_DENIED``, otherwise you'll return @@ -46,7 +52,8 @@ does not belong to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN`` Creating the Custom Voter ------------------------- -You could implement your Voter to check permission for the view and edit action like the following:: +The goal is to create a voter that checks to see if a user has access to +view or edit a particular object. Here's an example implementation: // src/Acme/DemoBundle/Security/Authorization/Voter/PostVoter.php namespace Acme\DemoBundle\Security\Authorization\Voter; @@ -87,7 +94,8 @@ You could implement your Voter to check permission for the view and edit action } // check if voter is used correct, only allow one attribute for a check - if(1 !== count($attributes) || !is_string($attributes[0])) { + // this isn't a requirement, it's just the way we want our voter to work + if(1 !== count($attributes)) { throw new InvalidArgumentException( 'Only one attribute is allowed for VIEW or EDIT' ); @@ -104,14 +112,14 @@ You could implement your Voter to check permission for the view and edit action return VoterInterface::ACCESS_ABSTAIN; } - // check if given user is instance of user interface + // make sure there is a user object (i.e. that the user is logged in) if (!$user instanceof UserInterface) { return VoterInterface::ACCESS_DENIED; } switch($attribute) { case 'view': - // the data object could have for e.g. a method isPrivate() + // the data object could have for example a method isPrivate() // which checks the Boolean attribute $private if (!$post->isPrivate()) { return VoterInterface::ACCESS_GRANTED; @@ -126,7 +134,6 @@ You could implement your Voter to check permission for the view and edit action } break; } - } } @@ -137,7 +144,7 @@ Declaring the Voter as a Service -------------------------------- To inject the voter into the security layer, you must declare it as a service -and tag it as a ``security.voter``: +and tag it with ``security.voter``: .. configuration-block:: @@ -212,3 +219,5 @@ from the security context is called. return new Response('

'.$post->getName().'

'); } } + +It's that easy! From d3f9383bbbde6571105d7bffe4cacd169bb28d2b Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 3 Mar 2014 21:01:54 -0600 Subject: [PATCH 15/15] [#3594] Nice tweaks thanks to @WouterJ and @xabbuh --- cookbook/security/voters_data_permission.rst | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index e6b01d12f45..1873d4100bb 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -12,7 +12,7 @@ which are like simple conditional statements. .. seealso:: Voters can also be used in other ways, like, for example, blacklisting IP - addresses from the entire application: ":doc:`/cookbook/security/voters`". + addresses from the entire application: :doc:`/cookbook/security/voters`. .. tip:: @@ -25,13 +25,14 @@ 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 -security context (i.e. the ``security.context`` service). Each decides if -the current user should have access to some resource. +security context (i.e. the ``security.context`` 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 with the feedback from all voters: affirmative, consensus and unanimous. -For more information take a look at ":ref:`the section about access decision managers `". +For more information take a look at +:ref:`the section about access decision managers `. The Voter Interface ------------------- @@ -52,8 +53,8 @@ does not belong to this voter, it will return ``VoterInterface::ACCESS_ABSTAIN`` Creating the Custom Voter ------------------------- -The goal is to create a voter that checks to see if a user has access to -view or edit a particular object. Here's an example implementation: +The goal is to create a voter that checks if a user has access to view or +edit a particular object. Here's an example implementation: // src/Acme/DemoBundle/Security/Authorization/Voter/PostVoter.php namespace Acme\DemoBundle\Security\Authorization\Voter; @@ -93,8 +94,9 @@ view or edit a particular object. Here's an example implementation: return VoterInterface::ACCESS_ABSTAIN; } - // check if voter is used correct, only allow one attribute for a check - // this isn't a requirement, it's just the way we want our voter to work + // 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'