-
-
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 1 commit
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 |
---|---|---|
@@ -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 | ||
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. to check user permissions for accessing a given object |
||
================================================================================== | ||
|
||
In Symfony2 you can check the permission to access data by the | ||
:doc:`ACL module </cookbook/security/acl>` which is a bit overhelming | ||
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. , which is a bit |
||
for many applications. A much easier solution is working with custom | ||
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. 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 | ||
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. can also be used ... for permissions ... |
||
application: :doc:`cookbook/security/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. use absolute paths (start with a slash) and put it inside double quotes 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. so like |
||
|
||
.. tip:: | ||
|
||
It is good to understand the basics about what and how | ||
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. why is it good? need to connect it |
||
:doc:`authorization </components/security/authorization>` works. | ||
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. please link to the correct section into the book/security article instead 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. Well the book covers not the same information like in /components/security/authorization, which is in my opinion much more relevant. While the book entry covers more the parts and bits to secure the whole application, does the components entry explain the tools that we are using here. |
||
|
||
How symfony works with 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. | ||
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. voters, you have to understand how Symfony works with them. |
||
In general all registered custom voters will be called every time you ask | ||
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. In general, all registered |
||
symfony about permission (ACL). In general there are three different | ||
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. permissions 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. Symfony |
||
approaches on how to handle the feedback from all voters: | ||
:ref:`components-security-access-decision-manager`. | ||
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. put also in double quotes |
||
|
||
The Voter Interface | ||
------------------- | ||
|
||
A custom voter must implement | ||
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, | ||
which requires the following three methods: | ||
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. which has this structure: |
||
|
||
.. code-block:: php | ||
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 the 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. Sorry, I don't know hat you mean exactly? |
||
|
||
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 | ||
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 :method: role 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. sorry I dont get it 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: 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 ``supportsClass()`` method is used to check if the voter supports the | ||
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. same here |
||
current user token class. | ||
|
||
The ``vote()`` method must implement the business logic that verifies whether | ||
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. and here |
||
or not the user is granted access. This method must return one of the following | ||
values: | ||
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. put this in a list 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 mean line 44 to 52, covering each point as a list entry? 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. Yes, and this can also be added to the included file, I guess. |
||
|
||
* ``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 | ||
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 line breaks after the first word that crosses the 72th character |
||
``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 responsebility for this decision belong not to this voter, he will 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. responsibility 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. belongs 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 is not a he, it |
||
``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. | ||
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. of a blog 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. Use Acme not ACME 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. also, use a literal (double quotes) and I don't think the filename needed, because you already add it to the example |
||
|
||
.. code-block:: php | ||
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. and then remove this 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. should I not declare somewhere that it is php? Is it maybe then, wourl that work?: ... view and edit action like following:: php 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. no, the default language is PHP for the Symfony docs. So just 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. right :-) |
||
|
||
// src/Acme/DemoBundle/Security/Authorization/Document/PostVoter.php | ||
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. we always use the ORM in the docs, so please use the Entity namespace instead of the Document namespace |
||
namespace Acme\DemoBundle\Security\Authorization\Document; | ||
|
||
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\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) | ||
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. teaching to inject the container is outrageous at this date and age |
||
{ | ||
$this->container = $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. remove this blank line |
||
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. use 4 spaces to indent |
||
'view', | ||
'edit' | ||
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. always add a comma after multi-line array items |
||
)); | ||
} | ||
|
||
public function supportsClass($class) | ||
{ | ||
// could be "ACME\DemoBundle\Entity\Post" as well | ||
$array = array("ACME\DemoBundle\Document\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. use instanceof check |
||
|
||
foreach ($array as $item) { | ||
// check with stripos in case doctrine is using a proxy class for this object | ||
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 is not the right way to check for Doctrine proxies (you can have lots of false positive with your stripos detection).
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. Ah cool, I didn't know that. I will change that to be more proper. 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 think there is even a util in doctrine common for this, but i may be wrong 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. yes this will be replaced by a better solution, please wait till tomorrow |
||
if (stripos($s, $item) !== FALSE) { | ||
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 lowercase php constants: false |
||
return true; | ||
} | ||
} | ||
return false; | ||
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 new line before return statement 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. why not simplify this to? return $obj instanceof 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. good point |
||
} | ||
|
||
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))) ) { | ||
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 space between () |
||
return VoterInterface::ACCESS_ABSTAIN; | ||
} | ||
|
||
// check if the given attribute is covered by this voter | ||
foreach ($attributes as $attribute) { | ||
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. Maybe I'm missing something but I don't see the point of checking the whole array if you are only using the first element after that. 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 agree. Personally, I only ever allow 1 attribute to be passed - passing an array is confusing to me, because we could internally implement it using OR or AND logic. So, I like just using the first attribute, but we should throw an exception if there is more than 1 attribute and then grab the first attribute so that we don't need this 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. Alright, as the interface is forcing us to have a array parameter as the last one, I will do use always the first array element as described earlier: #3138 (comment) |
||
if ( !$this->supportsAttribute($attribute) ) { | ||
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. same here |
||
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) ) { | ||
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. and here 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 use statement for UserInterface |
||
return VoterInterface::ACCESS_DENIED; | ||
} | ||
|
||
switch($this->attributes[0]) { | ||
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. undefined property |
||
|
||
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 |
||
case 'view': | ||
if($object->isPrivate() === false) { | ||
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. if (!$object->isPrivate()) {
} (space between if and open ( and removing === false) |
||
return VoterInterface::ACCESS_GRANTED; | ||
} | ||
break; | ||
|
||
case 'edit': | ||
if($object->getOwner()->getId() === $user->getId()) { | ||
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. space after if and switch |
||
return VoterInterface::ACCESS_GRANTED; | ||
} | ||
break; | ||
|
||
default: | ||
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 case can be remove because we are already out with !$this->supportsAttribute($attribute) from https://github.com/symfony/symfony-docs/pull/3138/files#diff-70c7f0c16e0c51e7dc991ab9b801ff73R107 |
||
// otherwise denied access | ||
return VoterInterface::ACCESS_DENIED; | ||
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 rather throw an exception here - because of the 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. good point, I am adding this. |
||
} | ||
|
||
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. This can be done easily through the service 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. remove the part for service container since you already removed it in the code example 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. As it was spotted: #3138 (comment), I simply reused parts from http://symfony.com/doc/current/cookbook/security/voters.html#creating-a-custom-voter |
||
|
||
Declaring the Voter as a Service | ||
-------------------------------- | ||
|
||
To inject the voter into the security layer, you must declare it as a service, | ||
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 last , |
||
and tag it as a "security.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. change " into double backticks |
||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
# src/Acme/AcmeBundle/Resources/config/services.yml | ||
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.
|
||
services: | ||
security.access.post_document_voter: | ||
class: Acme\DemoBundle\Security\Authorization\Document\PostVoter | ||
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. also it seems you are using mongodb from the Document folder name 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. let's change it to ORM |
||
public: false | ||
arguments: [@service_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. remove please |
||
# we need to assign this service to be a security 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. this is not needed, we can just say it gets tagged to be a 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. +1 |
||
tags: | ||
- { name: security.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. missing xml and php: <?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services">
<services>
<service id="security.access.post_document_voter"
class="Acme\DemoBundle\Security\Authorization\Document\PostVoter"
public="false">
<tag name="security.voter" />
</service>
</services>
</container> $container
->register('security.access.post_document_voter', 'Acme\DemoBundle\Security\Authorization\Document\PostVoter')
->addTag('security.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. a trailing newline is missing |
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.