Symfony 5 — Могу ли я использовать избиратель с другим пользователем, кроме текущего зарегистрированного?

Обычно я использую избиратель для пользователя, который подключен (я), чтобы увидеть, могу ли я выполнить действие или нет.

          if($this->isGranded('TRAVEL', $city)){
        $this->goToCity($this->getUser(), $city);
    }

Но на этот раз я хотел бы использовать своего избирателя для пользователя, которого я буду искать в базе данных, чтобы проверить, может ли этот пользователь выполнить то же самое действие.

          $another_user = $doctrineStuff->getRepo(Foo:bar)->getUser(123);
    if($this->isGranded('TRAVEL',$city, $another_user)){ // ??
        $this->goToCity($another_user, $city);
    }

Это возможно ? Как быть с этим, я ничего не могу найти об этой проблеме. Может быть, кроме имитации самодельного избирателя, который проверяет пользователя не по его токену, а по его инстансу...

2 ответа

Основная идея состоит в том, чтобы создать собственный маркер безопасности для пользователя, а затем использовать метод AccessDecisionManager::decide вместо метода AuthorizationChecker::isGranted.

      class SomeController {
    public function someAction(
        AccessDecisionManagerInterface $adm,
        EntityManagerInterface $em)
    {
        $user = $em->find(User::class,123);
        $token = new UsernamePasswordToken($user,'firewall',$user->getRoles());
        if ($adm->decide($token,['TRAVEL'],$city) {
            whatever;

Не беспокойтесь об имени брандмауэра. Он не будет использоваться системой избирателей, если у вас нет очень необычного избирателя. Он используется в этом примере, потому что TokenInterface удивительно сложен (13 методов!), поэтому проще всего использовать существующую реализацию. Вы можете немного улучшить ситуацию, создав свой собственный класс токенов:

      class UserToken extends AbstractToken {
    public function __construct(User $user) {
        parent::__construct($user->getRoles());
        $this->setUser($user);

Также стоит отметить, что предложение напрямую использовать диспетчер принятия решений о доступе исходит из кода isGranted:

          # AuthorizationChecker
    final public function isGranted(mixed $attribute, mixed $subject = null): bool
    {
        $token = $this->tokenStorage->getToken();

        if (!$token || !$token->getUser()) {
            $token = new NullToken();
        }

        return $this->accessDecisionManager->decide($token, [$attribute], $subject);
    }
}

Со временем вы, возможно, захотите следить за любыми изменениями здесь. Настройка компонента безопасности кажется чем-то вроде хобби для некоторых основных разработчиков Symfony. Но никаких кардинальных изменений я не жду.

Ответ @Cerad работает, если вариант использования очень прост. В моем случае это не сработало, поскольку у моего избирателя были внутренние звонки вSecurity::isGranted()метод. Итак, жетон был передан избирателю, но как только я достигisGranted()звонок, он всегда возвращалсяfalseпотому что он использовалNullTokenвместо моегоUsernamePasswordToken.

Чтобы решить эту проблему, мне пришлось обновить TokenStorage, чтобы быть уверенным, что он будет использоваться повсюду. Мой вариант использования прост: я опросил несколько пользователей и должен обработать только тех, кому «предоставлен» доступ к другому объекту, и отбросить остальных:

      // Don't forget to include those classes
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;



// First, I make sure to keep the current token somewhere, just in case
$originalToken = $this->tokenStorage->getToken();

foreach ($alert->getUsers() as $user) {
    // Create a token to use the Voter system
    // If user is not granted, then it's excluded from the process
    $this->tokenStorage->setToken(new UsernamePasswordToken($user, 'main', $user->getRoles()));

    // Now I can simply call the AuthorizationChecker instead of the AccessDecisionManager
    if (!$this->authorizationChecker->isGranted(OperationVoter::VIEW, $data)) {
        continue;
    }

    // Keep going and process user
    // ...
}

// At the end, I restore the TokenStorage to it's original state to prevent
// unwanted side-effects to another part of my source code
// I wouldn't want another Voter somewhere to use the wrong User
$this->tokenStorage->setToken($originalToken);
Другие вопросы по тегам