|
| 1 | +.. index:: |
| 2 | + single: Security; Data Permission Voters |
| 3 | + |
| 4 | +How to implement your own Voter to check the permission for a object agains a user |
| 5 | +================================================================================== |
| 6 | + |
| 7 | +In Symfony2 you can check the permission to access data by the |
| 8 | +:doc:`ACL module </cookbook/security/acl>` which is a bit overhelming |
| 9 | +for many applications. A much easier solution is working with custom |
| 10 | +voters, which are like simple conditional statements. Voters can be |
| 11 | +also used to check for permission as a part or even the whole |
| 12 | +application: :doc:`cookbook/security/voters`. |
| 13 | + |
| 14 | +.. tip:: |
| 15 | + |
| 16 | + It is good to understand the basics about what and how |
| 17 | + :doc:`authorization </components/security/authorization>` works. |
| 18 | + |
| 19 | +How symfony works with voters |
| 20 | +----------------------------- |
| 21 | + |
| 22 | +In order to use voters you have to understand how symfony works with them. |
| 23 | +In general all registered custom voters will be called every time you ask |
| 24 | +symfony about permission (ACL). In general there are three different |
| 25 | +approaches on how to handle the feedback from all voters: |
| 26 | +:ref:`components-security-access-decision-manager`. |
| 27 | + |
| 28 | +The Voter Interface |
| 29 | +------------------- |
| 30 | + |
| 31 | +A custom voter must implement |
| 32 | +:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, |
| 33 | +which requires the following three methods: |
| 34 | + |
| 35 | +.. code-block:: php |
| 36 | +
|
| 37 | + interface VoterInterface |
| 38 | + { |
| 39 | + public function supportsAttribute($attribute); |
| 40 | + public function supportsClass($class); |
| 41 | + public function vote(TokenInterface $token, $object, array $attributes); |
| 42 | + } |
| 43 | +
|
| 44 | +The ``supportsAttribute()`` method is used to check if the voter supports |
| 45 | +the given user attribute (i.e: a role, an acl, etc.). |
| 46 | + |
| 47 | +The ``supportsClass()`` method is used to check if the voter supports the |
| 48 | +current user token class. |
| 49 | + |
| 50 | +The ``vote()`` method must implement the business logic that verifies whether |
| 51 | +or not the user is granted access. This method must return one of the following |
| 52 | +values: |
| 53 | + |
| 54 | +* ``VoterInterface::ACCESS_GRANTED``: The user is allowed to access the application |
| 55 | +* ``VoterInterface::ACCESS_ABSTAIN``: The voter cannot decide if the user is granted or not |
| 56 | +* ``VoterInterface::ACCESS_DENIED``: The user is not allowed to access the application |
| 57 | + |
| 58 | +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 |
| 59 | +``VoterInterface::ACCESS_DENIED``, otherwise you'll return |
| 60 | +``VoterInterface::ACCESS_GRANTED``. In case the responsebility for this decision belong not to this voter, he will return |
| 61 | +``VoterInterface::ACCESS_ABSTAIN``. |
| 62 | + |
| 63 | +Creating the Custom Voter |
| 64 | +------------------------- |
| 65 | + |
<
10000
td data-grid-cell-id="diff-14494dfa80edc4ef709454ecf3dbe4051f7657917381f321b65aae39c87c7956-empty-66-0" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-additionNum-bgColor, var(--diffBlob-addition-bgColor-num));text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative left-side">
66 | +You could store your Voter for the view and edit method of a post within ACME/DemoBundle/Security/Authorization/Document/PostVoter.php. |
| 67 | + |
| 68 | +.. code-block:: php |
| 69 | +
|
| 70 | + // src/Acme/DemoBundle/Security/Authorization/Document/PostVoter.php |
| 71 | + namespace Acme\DemoBundle\Security\Authorization\Document; |
| 72 | +
|
| 73 | + use Symfony\Component\DependencyInjection\ContainerInterface; |
| 74 | + use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; |
| 75 | + use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; |
| 76 | +
|
| 77 | + class PostVoter implements VoterInterface |
| 78 | + { |
| 79 | + private $container; |
| 80 | + |
| 81 | + public function __construct(ContainerInterface $container) |
| 82 | + { |
| 83 | + $this->container = $container; |
| 84 | + } |
| 85 | + |
| 86 | + public function supportsAttribute($attribute) |
| 87 | + { |
| 88 | + return in_array($attribute, array( |
| 89 | + 'view', |
| 90 | + 'edit' |
| 91 | + )); |
| 92 | + } |
| 93 | + |
| 94 | + public function supportsClass($class) |
| 95 | + { |
| 96 | + // could be "ACME\DemoBundle\Entity\Post" as well |
| 97 | + $array = array("ACME\DemoBundle\Document\Post"); |
| 98 | + |
| 99 | + foreach ($array as $item) { |
| 100 | + // check with stripos in case doctrine is using a proxy class for this object |
| 101 | + if (stripos($s, $item) !== FALSE) { |
| 102 | + return true; |
| 103 | + } |
| 104 | + } |
| 105 | + return false; |
| 106 | + } |
| 107 | + |
| 108 | + public function vote(TokenInterface $token, $object, array $attributes) |
| 109 | + { |
| 110 | + // get current logged in user |
| 111 | + $user = $token->getUser(); |
| 112 | + |
| 113 | + // check if class of this object is supported by this voter |
| 114 | + if ( !($this->supportsClass(get_class($object))) ) { |
| 115 | + return VoterInterface::ACCESS_ABSTAIN; |
| 116 | + } |
| 117 | + |
| 118 | + // check if the given attribute is covered by this voter |
| 119 | + foreach ($attributes as $attribute) { |
| 120 | + if ( !$this->supportsAttribute($attribute) ) { |
| 121 | + return VoterInterface::ACCESS_ABSTAIN; |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + // check if given user is instance of user interface |
| 126 | + if ( !($user instanceof UserInterface) ) { |
| 127 | + return VoterInterface::ACCESS_DENIED; |
| 128 | + } |
| 129 | + |
| 130 | + switch($this->attributes[0]) { |
| 131 | + |
| 132 | + case 'view': |
| 133 | + if($object->isPrivate() === false) { |
| 134 | + return VoterInterface::ACCESS_GRANTED; |
| 135 | + } |
| 136 | + break; |
| 137 | + |
| 138 | + case 'edit': |
| 139 | + if($object->getOwner()->getId() === $user->getId()) { |
| 140 | + return VoterInterface::ACCESS_GRANTED; |
| 141 | + } |
| 142 | + break; |
| 143 | + |
| 144 | + default: |
| 145 | + // otherwise denied access |
| 146 | + return VoterInterface::ACCESS_DENIED; |
| 147 | + } |
| 148 | +
|
| 149 | + } |
| 150 | + } |
| 151 | +
|
| 152 | +That's it! The voter is done. The next step is to inject the voter into |
| 153 | +the security layer. This can be done easily through the service container. |
| 154 | + |
| 155 | +Declaring the Voter as a Service |
| 156 | +-------------------------------- |
| 157 | + |
| 158 | +To inject the voter into the security layer, you must declare it as a service, |
| 159 | +and tag it as a "security.voter": |
| 160 | + |
| 161 | +.. configuration-block:: |
| 162 | + |
| 163 | + .. code-block:: yaml |
| 164 | +
|
| 165 | + # src/Acme/AcmeBundle/Resources/config/services.yml |
| 166 | + services: |
| 167 | + security.access.post_document_voter: |
| 168 | + class: Acme\DemoBundle\Security\Authorization\Document\PostVoter |
| 169 | + public: false |
| 170 | + arguments: [@service_container] |
| 171 | + # we need to assign this service to be a security voter |
| 172 | + tags: |
| 173 | + - { name: security.voter } |
0 commit comments