Skip to content

Commit 02904b3

Browse files
committed
minor #11653 Fixing docs about switch_user and custom voters (weaverryan)
This PR was merged into the 4.2 branch. Discussion ---------- Fixing docs about switch_user and custom voters @nicolas-grekas caught this. We need to use a custom attribute, instead of relying on `ROLE_ALLOWED_TO_SWITCH`. Otherwise, it's possible for the `RoleVoter` to say "Yes! This person *does* have `ROLE_ALLOWED_TO_SWITCH`"... but then your custom voter is never called, which would then say "Wait, no, they should not". A bit of an edge case - but this strategy gives the user 100% control, which is generally what we want with voters: only 1 voter should vote, not multiple. Was introduced originally in 4.1. Commits ------- 3f2e67d Fixing docs about switch_user and custom voters
2 parents 31d14b3 + 3f2e67d commit 02904b3

File tree

1 file changed

+76
-25
lines changed

1 file changed

+76
-25
lines changed

security/impersonating_user.rst

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -190,25 +190,81 @@ also adjust the query parameter name via the ``parameter`` setting:
190190
Limiting User Switching
191191
-----------------------
192192

193-
If you need more control over user switching, but don't require the complexity
194-
of a full ACL implementation, you can use a security voter. For example, you
195-
may want to allow employees to be able to impersonate a user with the
196-
``ROLE_CUSTOMER`` role without giving them the ability to impersonate a more
197-
elevated user such as an administrator.
193+
If you need more control over user switching, you can use a security voter. First,
194+
configure ``switch_user`` to check for some new, custom attribute. This can be
195+
anything, but *cannot* start with ``ROLE_`` (to enforce that only your voter will)
196+
be called:
198197

199-
Create the voter class::
198+
.. configuration-block::
199+
200+
.. code-block:: yaml
201+
202+
# config/packages/security.yaml
203+
security:
204+
# ...
205+
206+
firewalls:
207+
main:
208+
# ...
209+
switch_user: { role: CAN_SWITCH_USER }
210+
211+
.. code-block:: xml
212+
213+
<!-- config/packages/security.xml -->
214+
<?xml version="1.0" encoding="UTF-8"?>
215+
<srv:container xmlns="http://symfony.com/schema/dic/security"
216+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
217+
xmlns:srv="http://symfony.com/schema/dic/services"
218+
xsi:schemaLocation="http://symfony.com/schema/dic/services
219+
https://symfony.com/schema/dic/services/services-1.0.xsd">
220+
<config>
221+
<!-- ... -->
222+
223+
<firewall name="main">
224+
<!-- ... -->
225+
<switch-user role="CAN_SWITCH_USER" />
226+
</firewall>
227+
</config>
228+
</srv:container>
229+
230+
.. code-block:: php
231+
232+
// config/packages/security.php
233+
$container->loadFromExtension('security', [
234+
// ...
235+
236+
'firewalls' => [
237+
'main'=> [
238+
// ...
239+
'switch_user' => [
240+
'role' => 'CAN_SWITCH_USER',
241+
],
242+
],
243+
],
244+
]);
245+
246+
Then, create a voter class that responds to this role and includes whatever custom
247+
logic you want::
200248

201249
namespace App\Security\Voter;
202250

203251
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
204252
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
253+
use Symfony\Component\Security\Core\Security;
205254
use Symfony\Component\Security\Core\User\UserInterface;
206255

207256
class SwitchToCustomerVoter extends Voter
208257
{
258+
private $security;
259+
260+
public function __construct(Security $security)
261+
{
262+
$this->security = $security;
263+
}
264+
209265
protected function supports($attribute, $subject)
210266
{
211-
return in_array($attribute, ['ROLE_ALLOWED_TO_SWITCH'])
267+
return in_array($attribute, ['CAN_SWITCH_USER'])
212268
&& $subject instanceof UserInterface;
213269
}
214270

@@ -220,34 +276,29 @@ Create the voter class::
220276
return false;
221277
}
222278

223-
if (in_array('ROLE_CUSTOMER', $subject->getRoles())
224-
&& $this->hasSwitchToCustomerRole($token)) {
279+
// you can still check for ROLE_ALLOWED_TO_SWITCH
280+
if ($this->security->isGranted('ROLE_ALLOWED_TO_SWITCH')) {
225281
return true;
226282
}
227283

228-
return false;
229-
}
284+
// check for any roles you want
285+
if ($this->security->isGranted('ROLE_TECH_SUPPORT')) {
286+
return true;
287+
}
230288

231-
private function hasSwitchToCustomerRole(TokenInterface $token)
232-
{
233-
foreach ($token->getRoles() as $role) {
234-
if ($role->getRole() === 'ROLE_SWITCH_TO_CUSTOMER') {
235-
return true;
236-
}
289+
/*
290+
* or use some custom data from your User object
291+
if ($user->isAllowedToSwitch()) {
292+
return true;
237293
}
294+
*/
238295

239296
return false;
240297
}
241298
}
242299

243-
To enable the new voter in the app, register it as a service and
244-
:doc:`tag it </service_container/tags>` with the ``security.voter``
245-
tag. If you're using the
246-
:ref:`default services.yaml configuration <service-container-services-load-example>`,
247-
this is already done for you, thanks to :ref:`autoconfiguration <services-autoconfigure>`.
248-
249-
Now a user who has the ``ROLE_SWITCH_TO_CUSTOMER`` role can switch to a user who
250-
has the ``ROLE_CUSTOMER`` role, but not other users.
300+
That's it! When switching users, your voter now has full control over whether or
301+
not this is allowed. If your voter isn't called, see :ref:`declaring-the-voter-as-a-service`.
251302

252303
Events
253304
------

0 commit comments

Comments
 (0)