8000 [WIP] add example for using a voter to restrict switch_user · symfony/symfony-docs@cf3bd00 · GitHub
[go: up one dir, main page]

Skip to content

Commit cf3bd00

Browse files
jwmickeyjaviereguiluz
authored andcommitted
[WIP] add example for using a voter to restrict switch_user
1 parent 06bc487 commit cf3bd00

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed

security/impersonating_user.rst

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,178 @@ also adjust the query parameter name via the ``parameter`` setting:
187187
),
188188
));
189189
190+
If you need more control over user switching, but don't require the complexity
191+
of a full ACL implementation, you can use a security voter. For example, you
192+
may want to allow employees to be able to impersonate a user with the
193+
``ROLE_CUSTOMER`` role without giving them the ability to impersonate a more
194+
elevated user such as an administrator.
195+
196+
First, create the voter class::
197+
198+
namespace AppBundle\Security\Voter;
199+
200+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
201+
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
202+
use Symfony\Component\Security\Core\Role\RoleHierarchy;
203+
use Symfony\Component\Security\Core\User\UserInterface;
204+
205+
class SwitchToCustomerVoter extends Voter
206+
{
207+
private $roleHierarchy;
208+
209+
public function __construct(RoleHierarchy $roleHierarchy)
210+
{
211+
$this->roleHierarchy = $roleHierarchy;
212+
}
213+
214+
protected function supports($attribute, $subject)
215+
{
216+
return in_array($attribute, ['ROLE_ALLOWED_TO_SWITCH'])
217+
&& $subject instanceof UserInterface;
218+
}
219+
220+
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
221+
{
222+
$user = $token->getUser();
223+
// if the user is anonymous or if the subject is not a user, do not grant access
224+
if (!$user instanceof UserInterface || !$subject instanceof UserInterface) {
225+
return false;
226+
}
227+
228+
if (in_array('ROLE_CUSTOMER', $subject->getRoles())
229+
&& $this->hasSwitchToCustomerRole($token)) {
230+
return self::ACCESS_GRANTED;
231+
}
232+
233+
return false;
234+
}
235+
236+
private function hasSwitchToCustomerRole(TokenInterface $token)
237+
{
238+
$roles = $this->roleHierarchy->getReachableRoles($token->getRoles());
239+
foreach ($roles as $role) {
240+
if ($role->getRole() === 'ROLE_SWITCH_TO_CUSTOMER') {
241+
return true;
242+
}
243+
}
244+
245+
return false;
246+
}
247+
}
248+
249+
.. caution::
250+
251+
Notice that when checking for the ``ROLE_CUSTOMER`` role on the target user, only the roles
252+
explicitly assigned to the user are checked rather than checking all reachable roles from
253+
the role hierarchy. The reason for this is to avoid accidentally granting access to an
254+
elevated user that may have inherited the role via the hierarchy. This logic is specific
255+
to the example, but keep this in mind when writing your own voter.
256+
257+
Next, add the roles to the security configuration:
258+
259+
.. configuration-block::
260+
261+
.. code-block:: yaml
262+
263+
# config/packages/security.yaml
264+
security:
265+
# ...
266+
267+
role_hierarchy:
268+
ROLE_CUSTOMER: [ROLE_USER]
269+
ROLE_EMPLOYEE: [ROLE_USER, ROLE_SWITCH_TO_CUSTOMER]
270+
ROLE_SUPER_ADMIN: [ROLE_EMPLOYEE, ROLE_ALLOWED_TO_SWITCH]
271+
272+
.. code-block:: xml
273+
274+
<!-- config/packages/security.xml -->
275+
<?xml version="1.0" encoding="UTF-8"?>
276+
<srv:container xmlns="http://symfony.com/schema/dic/security"
277+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
278+
xmlns:srv="http://symfony.com/schema/dic/services"
279+
xsi:schemaLocation="http://symfony.com/schema/dic/services
280+
http://symfony.com/schema/dic/services/services-1.0.xsd">
281+
<config>
282+
<!-- ... -->
283+
284+
<role id="ROLE_CUSTOMER">ROLE_USER</role>
285+
<role id="ROLE_EMPLOYEE">ROLE_USER, ROLE_SWITCH_TO_CUSTOMER</role>
286+
<role id="ROLE_SUPER_ADMIN">ROLE_EMPLOYEE, ROLE_ALLOWED_TO_SWITCH</role>
287+
</config>
288+
</srv:container>
289+
290+
.. code-block:: php
291+
292+
// config/packages/security.php
293+
$container->loadFromExtension('security', array(
294+
// ...
295+
296+
'role_hierarchy' => array(
297+
'ROLE_CUSTOMER' => 'ROLE_USER',
298+
'ROLE_EMPLOYEE' => 'ROLE_USER, ROLE_SWITCH_TO_CUSTOMER',
299+
'ROLE_SUPER_ADMIN' => array(
300+
'ROLE_EMPLOYEE',
301+
'ROLE_ALLOWED_TO_SWITCH',
302+
),
303+
),
304+
));
305+
306+
Thanks to autowiring, we only need to configure the role hierarchy argument when registering
307+
the voter as a service:
308+
309+
.. configuration-block::
310+
F438 311+
.. code-block:: yaml
312+
313+
// config/services.yaml
314+
services:
315+
# ...
316+
317+
App\Security\Voter\SwitchToCustomerVoter:
318+
arguments:
319+
$roleHierarchy: "@security.role_hierarchy"
320+
321+
.. code-block:: xml
322+
323+
<!-- config/services.xml -->
324+
<?xml version="1.0" encoding="UTF-8"?>
325+
<container xmlns="http://symfony.com/schema/dic/services"
326+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
327+
xsi:schemaLocation="http://symfony.com/schema/dic/services
328+
http://symfony.com/schema/dic/services/services-1.0.xsd">
329+
330+
<services>
331+
<!-- ... -->
332+
<service id="App\Security\Voter\SwitchToCustomerVoter">
333+
<argument key="$roleHierarchy">"@security.role_hierarchy"</argument>
334+
</service>
335+
</services>
336+
</container>
337+
338+
.. code-block:: php
339+
340+
// config/services.php
341+
use App\Security\Voter\SwitchToCustomerVoter;
342+
use Symfony\Component\DependencyInjection\Definition;
343+
344+
// Same as before
345+
$definition = new Definition();
346+
347+
$definition
348+
->setAutowired(true)
349+
->setAutoconfigured(true)
350+
->setPublic(false)
351+
;
352+
353+
$this->registerClasses($definition, 'App\\', '../src/*', '../src/{Entity,Migrations,Tests}');
354+
355+
// Explicitly configure the service
356+
$container->getDefinition(SwitchToCustomerVoter::class)
357+
->setArgument('$roleHierarchy', '@security.role_hierarchy');
358+
359+
Now a user who has the ``ROLE_SWITCH_TO_CUSTOMER`` role can switch to a user who explicitly has the
360+
``ROLE_CUSTOMER`` role, but not other users.
361+
190362
Events
191363
------
192364

0 commit comments

Comments
 (0)
0