Я хотел бы иметь возможность проверить, предоставляются ли атрибуты (роли) любому произвольному объекту, реализующему UserInterface
в Symfony2. Это возможно?
UserInterface->getRoles()
не подходит для моих нужд, потому что он не учитывает иерархию ролей, и я бы предпочел не изобретать колесо в этом отделе, поэтому я бы хотел использовать диспетчер решений доступа, если возможное.
Благодарю.
В ответ на решение Оливье ниже, вот мой опыт:
Вы можете использовать службу security.context с методом isGranted. Вы можете передать второй аргумент, который является вашим объектом.
$user = new Core\Model\User(); var_dump($user->getRoles(), $this->get('security.context')->isGranted('ROLE_ADMIN', $user));
Вывод:
array (size=1) 0 => string 'ROLE_USER' (length=9) boolean true
Моя иерархия роли:
role_hierarchy: ROLE_USER: ~ ROLE_VERIFIED_USER: [ROLE_USER] ROLE_ADMIN: [ROLE_VERIFIED_USER] ROLE_SUPERADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] ROLE_ALLOWED_TO_SWITCH: ~
Мой UserInterface->getRoles()
:
public function getRoles() { $roles = [$this->isVerified() ? 'ROLE_VERIFIED_USER' : 'ROLE_USER']; /** * @var UserSecurityRole $userSecurityRole */ foreach ($this->getUserSecurityRoles() as $userSecurityRole) { $roles[] = $userSecurityRole->getRole(); } return $roles; }
ROLE_ADMIN
должен быть явно назначен, но isGranted('ROLE_ADMIN', $user)
возвращает TRUE
даже если пользователь был только что создан, и ему не были назначены роли, отличные от ROLE_USER
по умолчанию, если только для текущего пользователя, ROLE_ADMIN
предоставлен ROLE_ADMIN
предоставляется ROLE_ADMIN
. Это заставляет меня думать, что второй аргумент isGranted()
просто проигнорирован и вместо этого используется isGranted()
предоставленный isGranted()
с AccessDecisionManager->decide()
SecurityContext
.
Если это ошибка, я отправлю отчет, но, возможно, я все еще делаю что-то неправильно?
Для этого вам нужен только AccessDecisionManager
, нет необходимости в контексте безопасности, поскольку вам не нужна аутентификация.
$user = new Core\Model\User(); $token = new UsernamePasswordToken($user, 'none', 'none', $user->getRoles()); $isGranted = $this->get('security.access.decision_manager') ->decide($token, array('ROLE_ADMIN'));
Это правильно учитывает иерархию ролей, поскольку RoleHierarchyVoter
регистрируется по умолчанию
Обновить
Как отметил @redalaanait, security.access.decision_manager является частной службой, поэтому доступ к ней напрямую – это нехорошо. Лучше использовать сервисный алиасинг , который позволяет вам получить доступ к частным услугам.
Возможно, вы можете создать экземпляр нового экземпляра securityContext и использовать его для проверки того, предоставлен ли пользователь:
$securityContext = new \Symfony\Component\Security\Core\SecurityContext($this->get('security.authentication.manager'), $this->get('security.access.decision_manager')); $token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user, null, $this->container->getParameter('fos_user.firewall_name'), $user->getRoles()); $securityContext->setToken($token); if ($securityContext->isGranted('ROLE_ADMIN')) { // some stuff to do }
security.context
Устаревает с 2.6.
Использовать AuthorizationChecker
:
$token = new UsernamePasswordToken( $user, null, 'secured_area', $user->getRoles() ); $tokenStorage = $this->container->get('security.token_storage'); $tokenStorage->setToken($token); $authorizationChecker = new AuthorizationChecker( $tokenStorage, $this->container->get('security.authentication.manager'), $this->container->get('security.access.decision_manager') ); if (!$authorizationChecker->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException(); }
RoleVoter
игнорирует объект $, прошедший через SecurityContext->isGranted()
. Это приводит к тому, что RoleHierarchyVoter
извлекает роли из Token
вместо предоставленного объекта UserInterface
$ (если существует), поэтому мне пришлось найти другой маршрут.
Может быть, есть лучший способ сделать это, и если есть, я бы точно хотел знать, но это решение, которое я придумал:
Сначала я реализовал ContainerAwareInterface
в своем классе User, чтобы я мог получить доступ к компоненту безопасности изнутри:
final class User implements AdvancedUserInterface, ContainerAwareInterface { // ... /** * @var ContainerInterface */ private $container; // ... public function setContainer(ContainerInterface $container = null) { if (null === $container) { throw new \Exception('First argument to User->setContainer() must be an instance of ContainerInterface'); } $this->container = $container; } // ... }
Затем я определил метод hasRole()
:
/** * @param string|\Symfony\Component\Security\Core\Role\RoleInterface $roleToCheck * @return bool * @throws \InvalidArgumentException */ public function hasRole($roleToCheck) { if (!is_string($roleToCheck)) { if (!($roleToCheck instanceof \Symfony\Component\Security\Core\Role\RoleInterface)) { throw new \InvalidArgumentException('First argument expects a string or instance of RoleInterface'); } $roleToCheck = $roleToCheck->getRole(); } /** * @var \Symfony\Component\Security\Core\SecurityContext $thisSecurityContext */ $thisSecurityContext = $this->container->get('security.context'); $clientUser = $thisSecurityContext->getToken()->getUser(); // determine if we're checking a role on the currently authenticated client user if ($this->equals($clientUser)) { // we are, so use the AccessDecisionManager and voter system instead return $thisSecurityContext->isGranted($roleToCheck); } /** * @var \Symfony\Component\Security\Core\Role\RoleHierarchy $thisRoleHierarchy */ $thisRoleHierarchy = $this->container->get('security.role_hierarchy'); $grantedRoles = $thisRoleHierarchy->getReachableRoles($this->getRoles()); foreach ($grantedRoles as $grantedRole) { if ($roleToCheck === $grantedRole->getRole()) { return TRUE; } } return FALSE; }
С контроллера:
$user = new User(); $user->setContainer($this->container); var_dump($user->hasRole('ROLE_ADMIN')); var_dump($this->get('security.context')->isGranted('ROLE_ADMIN')); var_dump($this->get('security.context')->isGranted('ROLE_ADMIN', $user)); $user->addUserSecurityRole('ROLE_ADMIN'); var_dump($user->hasRole('ROLE_ADMIN'));
Вывод:
boolean false boolean true boolean true boolean true
Хотя это не связано с AccessDecisionManager
или зарегистрированными избирателями (если только тестируемый экземпляр не является пользователем, прошедшим проверку подлинности), этого достаточно для моих потребностей, поскольку мне просто нужно выяснить, имеет ли данный пользователь конкретную роль.
Это выглядит как проблема с:
abstract class AbstractToken implements TokenInterface
Посмотрите на конструктор. Похоже, что роли создаются при создании экземпляра и не запрашиваются во время выполнения.
public function __construct(array $roles = array()) { $this->authenticated = false; $this->attributes = array(); $this->roles = array(); foreach ($roles as $role) { if (is_string($role)) { $role = new Role($role); } elseif (!$role instanceof RoleInterface) { throw new \InvalidArgumentException(sprintf('$roles must be an array of strings, or RoleInterface instances, but got %s.', gettype($role))); } $this->roles[] = $role; } }
Следовательно, роли не могут измениться после создания токена. Я думаю, что вариант состоит в том, чтобы написать своего избирателя. Я все еще оглядываюсь.
Создайте службу AccessDecisionMaker
(используйте решение Shady)
<?php namespace Bp\CommonBundle\Service; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Security\Core\Role\RoleInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\SecurityContext; class AccessDecisionMaker { /** @var Container */ private $container; /** @var SecurityContext */ private $securityContext; function __construct($container) { $this->container = $container; if (!$this->securityContext) { // Ensure security context is created only once $this->securityContext = new SecurityContext($this->container->get( 'security.authentication.manager' ), $this->container->get('security.access.decision_manager')); } } public function isGranted($roleToCheck, UserInterface $user) { if (!is_string($roleToCheck)) { if (!($roleToCheck instanceof RoleInterface)) { throw new \InvalidArgumentException('First argument expects a string or instance of RoleInterface'); } $roleToCheck = $roleToCheck->getRole(); } $token = new UsernamePasswordToken($user, null, $this->container->getParameter( 'fos_user.firewall_name' ), $user->getRoles()); $this->securityContext->setToken($token); if ($this->securityContext->isGranted($roleToCheck)) { return true; } return false; } }
Настроить это как услугу
bp.access_decision_maker: class: Bp\CommonBundle\Service\AccessDecisionMaker arguments: [@service_container ]
Используй это
$this->container->get('bp.access_decision_maker')->isGranted("ROLE_ADMIN",$user);
Вы можете использовать службу security.context
с методом isGranted
.
Вы можете передать второй аргумент, который является вашим объектом (см. Здесь ).
В контроллере:
$this->get('security.context')->isGranted('ROLE_FOOBAR', $myUser)