-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[WIP] create voters_data_permission.rst article #3138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e14eee5
f1217d7
b1923b7
b72bd67
ed86973
6817c53
a528e05
d56730d
f6db8c8
3957b3a
76a9db0
54b9877
c410082
0159097
639b188
aa8f2cc
8540498
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ Security | |
remember_me | ||
impersonating_user | ||
voters | ||
voters_data_permission | ||
acl | ||
acl_advanced | ||
force_https | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
.. code-block:: php | ||
|
||
interface VoterInterface | ||
{ | ||
public function supportsAttribute($attribute); | ||
public function supportsClass($class); | ||
public function vote(TokenInterface $token, object, 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 current user token class. | ||
|
||
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 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
.. index:: | ||
single: Security; Data Permission Voters | ||
|
||
How to Use Voters to Check User Permissions | ||
=========================================== | ||
|
||
In Symfony2 you can check the permission to access data by using the | ||
:doc:`ACL module </cookbook/security/acl>`, 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`". | ||
|
||
.. tip:: | ||
|
||
Have a look at the | ||
:doc:`authorization </components/security/authorization>` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd move .. tip::
Have a look at the
:doc:`authorization </components/security/authorization>`
chapter for a better understanding on voters. |
||
chapter for a better understanding on voters. | ||
|
||
How Symfony Uses Voters | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 <components-security-access-decision-manager>`". | ||
|
||
The Voter Interface | ||
------------------- | ||
|
||
A custom voter must implement | ||
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, | ||
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. they must be the owner of | ||
the object). If the condition fails, you'll return | ||
``VoterInterface::ACCESS_DENIED``, otherwise you'll return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you -> it? |
||
``VoterInterface::ACCESS_GRANTED``. In case the responsibility for this decision | ||
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:: | ||
|
||
// src/Acme/DemoBundle/Security/Authorization/Voter/PostVoter.php | ||
namespace Acme\DemoBundle\Security\Authorization\Voter; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use Acme\DemoBundle\Entity\Post (see below) |
||
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 Acme\DemoBundle\Entity\Post; | ||
|
||
class PostVoter implements VoterInterface | ||
{ | ||
const VIEW = 'view'; | ||
const EDIT = 'edit'; | ||
|
||
public function supportsAttribute($attribute) | ||
{ | ||
return in_array($attribute, array( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would have used constants like in built-in voters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would you also want to use the constant within the controller to check for? e.g. if (false === $this->get('security.context')->isGranted(PostVoter::VIEW, $post)) {
throw new AccessDeniedException('Unauthorised access!');
} |
||
self::VIEW, | ||
self::EDIT, | ||
)); | ||
} | ||
|
||
public function supportsClass($obj) | ||
{ | ||
return $obj instanceof Post; | ||
} | ||
|
||
/** | ||
* @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(1 !== count($attributes) || !is_string($attributes[0])) { | ||
throw new InvalidArgumentException( | ||
'Only one attribute is allowed for VIEW or EDIT' | ||
); | ||
} | ||
|
||
// set the attribute to check against | ||
$attribute = $attributes[0]; | ||
|
||
// get current logged in user | ||
$user = $token->getUser(); | ||
|
||
// check if the given attribute is covered by this voter | ||
if (!$this->supportsAttribute($attribute)) { | ||
return VoterInterface::ACCESS_ABSTAIN; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this implementation looks weird to me: you are abstaining even if some of the attributes are supported. None of the core voters are behaving this way (most of the time, you are checking a single attribute so it is not a common case though) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right, I am going to allow one Attribute only. |
||
|
||
// check if given user is instance of user interface | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove comment? |
||
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 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 | ||
if ($user->getId() === $post->getOwner()->getId()) { | ||
return VoterInterface::ACCESS_GRANTED; | ||
} | ||
break; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this line break and you did not even use the container 👎 |
||
} | ||
} | ||
|
||
That's it! The voter is done. The next step is to inject the voter into | ||
the security layer. | ||
|
||
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/DemoBundle/Resources/config/services.yml | ||
services: | ||
security.access.post_voter: | ||
class: Acme\DemoBundle\Security\Authorization\Voter\PostVoter | ||
public: false | ||
tags: | ||
- { name: security.voter } | ||
|
||
.. code-block:: xml | ||
|
||
<!-- src/Acme/DemoBundle/Resources/config/services.xml --> | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add |
||
<container xmlns="http://symfony.com/schema/dic/services" | ||
xsi:schemaLocation="http://symfony.com/schema/dic/services | ||
http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
<services> | ||
<service id="security.access.post_document_voter" | ||
class="Acme\DemoBundle\Security\Authorization\Voter\PostVoter" | ||
public="false"> | ||
<tag name="security.voter" /> | ||
</service> | ||
</services> | ||
</container> | ||
|
||
.. code-block:: php | ||
|
||
// src/Acme/DemoBundle/Resources/config/services.php | ||
$container | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add |
||
->register( | ||
'security.access.post_document_voter', | ||
'Acme\DemoBundle\Security\Authorization\Voter\PostVoter' | ||
) | ||
->addTag('security.voter') | ||
; | ||
|
||
How to Use the Voter in a Controller | ||
------------------------------------ | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing some text |
||
The registered voter will then always be asked as soon as the method ``isGranted()`` | ||
from the security context is called. | ||
|
||
.. code-block:: php | ||
|
||
// 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; | ||
|
||
class PostController extends Controller | ||
{ | ||
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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It appears that $post object should be recover before this check. (by a find($id) or with ParamConverter feature) |
||
throw new AccessDeniedException('Unauthorised access!'); | ||
} | ||
|
||
return new Response('<h1>'.$post->getName().'</h1>'); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new document needs to be referenced in
/cookbook/map.rst
and/cookbook/security/index.rst
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added them right under the voter article.