@@ -187,30 +187,28 @@ also adjust the query parameter name via the ``parameter`` setting:
187
187
),
188
188
));
189
189
190
+ Limiting User Switching
191
+ -----------------------
192
+
190
193
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
194
+ of a full ACL implementation, you can use a security voter. For example, you
192
195
may want to allow employees to be able to impersonate a user with the
193
196
``ROLE_CUSTOMER `` role without giving them the ability to impersonate a more
194
197
elevated user such as an administrator.
195
198
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::
197
203
198
204
namespace App\Security\Voter;
199
205
200
206
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
201
207
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
202
- use Symfony\Component\Security\Core\Role\RoleHierarchy;
203
208
use Symfony\Component\Security\Core\User\UserInterface;
204
209
205
210
class SwitchToCustomerVoter extends Voter
206
211
{
207
- private $roleHierarchy;
208
-
209
- public function __construct(RoleHierarchy $roleHierarchy)
210
- {
211
- $this->roleHierarchy = $roleHierarchy;
212
- }
213
-
214
212
protected function supports($attribute, $subject)
215
213
{
216
214
return in_array($attribute, ['ROLE_ALLOWED_TO_SWITCH'])
@@ -235,8 +233,7 @@ First, create the voter class::
235
233
236
234
private function hasSwitchToCustomerRole(TokenInterface $token)
237
235
{
238
- $roles = $this->roleHierarchy->getReachableRoles($token->getRoles());
239
- foreach ($roles as $role) {
236
+ foreach ($token->getRoles() as $role) {
240
237
if ($role->getRole() === 'ROLE_SWITCH_TO_CUSTOMER') {
241
238
return true;
242
239
}
@@ -246,116 +243,8 @@ First, create the voter class::
246
243
}
247
244
}
248
245
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.
359
248
360
249
Now a user who has the ``ROLE_SWITCH_TO_CUSTOMER `` role can switch to a user who explicitly has the
361
250
``ROLE_CUSTOMER `` role, but not other users.
0 commit comments