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);