@@ -187,30 +187,28 @@ also adjust the query parameter name via the ``parameter`` setting:
187187 ),
188188 ));
189189
190+ Limiting User Switching
191+ -----------------------
192+
190193If 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
194+ of a full ACL implementation, you can use a security voter. For example, you
192195may want to allow employees to be able to impersonate a user with the
193196``ROLE_CUSTOMER `` role without giving them the ability to impersonate a more
194197elevated user such as an administrator.
195198
196- First, create the voter class::
199+ .. versionadded :: 4.1
200+ The target user was added as the voter subject parameter in Symfony 4.1.
201+
202+ Create the voter class::
197203
198204 namespace App\Security\Voter;
199205
200206 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
201207 use Symfony\Component\Security\Core\Authorization\Voter\Voter;
202- use Symfony\Component\Security\Core\Role\RoleHierarchy;
203208 use Symfony\Component\Security\Core\User\UserInterface;
204209
205210 class SwitchToCustomerVoter extends Voter
206211 {
207- private $roleHierarchy;
208-
209- public function __construct(RoleHierarchy $roleHierarchy)
210- {
211- $this->roleHierarchy = $roleHierarchy;
212- }
213-
214212 protected function supports($attribute, $subject)
215213 {
216214 return in_array($attribute, ['ROLE_ALLOWED_TO_SWITCH'])
@@ -235,8 +233,7 @@ First, create the voter class::
235233
236234 private function hasSwitchToCustomerRole(TokenInterface $token)
237235 {
238- $roles = $this->roleHierarchy->getReachableRoles($token->getRoles());
239- foreach ($roles as $role) {
236+ foreach ($token->getRoles() as $role) {
240237 if ($role->getRole() === 'ROLE_SWITCH_TO_CUSTOMER') {
241238 return true;
242239 }
@@ -246,116 +243,8 @@ First, create the voter class::
246243 }
247244 }
248245
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-
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- use Symfony\Component\DependencyInjection\Reference;
344-
345- // Same as before
346- $definition = new Definition();
347-
348- $definition
349- ->setAutowired(true)
350- ->setAutoconfigured(true)
351- ->setPublic(false)
352- ;
353-
354- $this->registerClasses($definition, 'App\\', '../src/*', '../src/{Entity,Migrations,Tests}');
355-
356- // Explicitly configure the service
357- $container->getDefinition(SwitchToCustomerVoter::class)
358- ->setArgument('$roleHierarchy', new Reference('security.role_hierarchy'));
246+ Thanks to service autoconfiguration and autowiring, this new voter is automatically
247+ registered as a service and tagged as a security voter.
359248
360249Now a user who has the ``ROLE_SWITCH_TO_CUSTOMER `` role can switch to a user who explicitly has the
361250``ROLE_CUSTOMER `` role, but not other users.
0 commit comments