Как изменить хранилище иерархии ролей в Symfony2?

В моем проекте мне нужно хранить иерархию роли в базе данных и динамически создавать новые роли. В Symfony2 иерархия роли по умолчанию хранится в security.yml . Что я нашел:

Существует служба security.role_hierarchy ( Symfony\Component\Security\Core\Role\RoleHierarchy ); Эта служба получает массив ролей в конструкторе:

 public function __construct(array $hierarchy) { $this->hierarchy = $hierarchy; $this->buildRoleMap(); } 

и свойство $hierarchy является приватным.

Этот аргумент приходит в конструкторе из \Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension::createRoleHierarchy() который использует роли из config, как я понял:

  $ container-> setParameter ('security.role_hierarchy.roles', $ config ['role_hierarchy']); 

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

Второй способ, который я вижу, – определить собственный класс RoleHierarchy унаследованный от базового. Но так как в базовом классе RoleHierarchy свойство $hierarchy определяется как private, то мне придется переопределить все функции из базового класса RoleHierarchy . Но я не думаю, что это хороший ООП и способ Symfony …

Solutions Collecting From Web of "Как изменить хранилище иерархии ролей в Symfony2?"

Решение было простым. Сначала я создал объект Role.

 class Role { /** * @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string $name * * @ORM\Column(name="name", type="string", length=255) */ private $name; /** * @ORM\ManyToOne(targetEntity="Role") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") **/ private $parent; ... } 

после этого создал службу RoleHierarchy, расширенную от родной Symfony. Я унаследовал конструктор, добавил туда EntityManager и предоставил исходный конструктор с новым массивом ролей вместо старого:

 class RoleHierarchy extends Symfony\Component\Security\Core\Role\RoleHierarchy { private $em; /** * @param array $hierarchy */ public function __construct(array $hierarchy, EntityManager $em) { $this->em = $em; parent::__construct($this->buildRolesTree()); } /** * Here we build an array with roles. It looks like a two-levelled tree - just * like original Symfony roles are stored in security.yml * @return array */ private function buildRolesTree() { $hierarchy = array(); $roles = $this->em->createQuery('select r from UserBundle:Role r')->execute(); foreach ($roles as $role) { /** @var $role Role */ if ($role->getParent()) { if (!isset($hierarchy[$role->getParent()->getName()])) { $hierarchy[$role->getParent()->getName()] = array(); } $hierarchy[$role->getParent()->getName()][] = $role->getName(); } else { if (!isset($hierarchy[$role->getName()])) { $hierarchy[$role->getName()] = array(); } } } return $hierarchy; } } 

… и переопределил его как услугу:

 <services> <service id="security.role_hierarchy" class="Acme\UserBundle\Security\Role\RoleHierarchy" public="false"> <argument>%security.role_hierarchy.roles%</argument> <argument type="service" id="doctrine.orm.default_entity_manager"/> </service> </services> 

Это все. Может быть, в моем коде есть что-то ненужное. Может быть, лучше писать лучше. Но я думаю, что основная идея сейчас очевидна.

Я сделал то же самое, что и zIs (для хранения RoleHierarchy в базе данных), но я не могу загрузить полную иерархию роли внутри конструктора, как это сделал zI, потому что мне пришлось загрузить пользовательский фильтр доктрины внутри события kernel.request . Конструктор будет вызван до kernel.request поэтому для меня это не было вариантом.

Поэтому я проверил компонент безопасности и выяснил, что Symfony называет пользовательского Voter для проверки roleHierarchy соответствии с ролью пользователей:

 namespace Symfony\Component\Security\Core\Authorization\Voter; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; /** * RoleHierarchyVoter uses a RoleHierarchy to determine the roles granted to * the user before voting. * * @author Fabien Potencier <fabien@symfony.com> */ class RoleHierarchyVoter extends RoleVoter { private $roleHierarchy; public function __construct(RoleHierarchyInterface $roleHierarchy, $prefix = 'ROLE_') { $this->roleHierarchy = $roleHierarchy; parent::__construct($prefix); } /** * {@inheritdoc} */ protected function extractRoles(TokenInterface $token) { return $this->roleHierarchy->getReachableRoles($token->getRoles()); } } 

Метод getReachableRoles возвращает все роли, которыми может быть пользователь. Например:

  ROLE_ADMIN / \ ROLE_SUPERVISIOR ROLE_BLA | | ROLE_BRANCH ROLE_BLA2 | ROLE_EMP or in Yaml: ROLE_ADMIN: [ ROLE_SUPERVISIOR, ROLE_BLA ] ROLE_SUPERVISIOR: [ ROLE_BRANCH ] ROLE_BLA: [ ROLE_BLA2 ] 

Если пользователю назначена роль ROLE_SUPERVISOR, метод возвращает роли ROLE_SUPERVISOR, ROLE_BRANCH и ROLE_EMP (Ролевые объекты или классы, которые реализуют RoleInterface)

Кроме того, этот пользовательский избиратель будет отключен, если нет иерархии RoleHierarchy, определенной в security.yaml

 private function createRoleHierarchy($config, ContainerBuilder $container) { if (!isset($config['role_hierarchy'])) { $container->removeDefinition('security.access.role_hierarchy_voter'); return; } $container->setParameter('security.role_hierarchy.roles', $config['role_hierarchy']); $container->removeDefinition('security.access.simple_role_voter'); } 

Чтобы решить мою проблему, я создал свой собственный Voter и расширил RoleVoter-Class тоже:

 use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Acme\Foundation\UserBundle\Entity\Group; use Doctrine\ORM\EntityManager; class RoleHierarchyVoter extends RoleVoter { private $em; public function __construct(EntityManager $em, $prefix = 'ROLE_') { $this->em = $em; parent::__construct($prefix); } /** * {@inheritdoc} */ protected function extractRoles(TokenInterface $token) { $group = $token->getUser()->getGroup(); return $this->getReachableRoles($group); } public function getReachableRoles(Group $group, &$groups = array()) { $groups[] = $group; $children = $this->em->getRepository('AcmeFoundationUserBundle:Group')->createQueryBuilder('g') ->where('g.parent = :group') ->setParameter('group', $group->getId()) ->getQuery() ->getResult(); foreach($children as $child) { $this->getReachableRoles($child, $groups); } return $groups; } } 

One Note: Моя установка похожа на zls. Мое определение роли (в моем случае я назвал ее Group):

 Acme\Foundation\UserBundle\Entity\Group: type: entity table: sec_groups id: id: type: integer generator: { strategy: AUTO } fields: name: type: string length: 50 role: type: string length: 20 manyToOne: parent: targetEntity: Group 

И пользовательское определение:

 Acme\Foundation\UserBundle\Entity\User: type: entity table: sec_users repositoryClass: Acme\Foundation\UserBundle\Entity\UserRepository id: id: type: integer generator: { strategy: AUTO } fields: username: type: string length: 30 salt: type: string length: 32 password: type: string length: 100 isActive: type: boolean column: is_active manyToOne: group: targetEntity: Group joinColumn: name: group_id referencedColumnName: id nullable: false 

Может быть, это помогает кому-то.

Я разработал пакет.

Вы можете найти его на странице https://github.com/Spomky-Labs/RoleHierarchyBundle

Мое решение было вдохновлено решением zls. Его решение отлично работало для меня, но отношение «один ко многим» между ролями означало наличие одного огромного ролевого дерева, которое было бы трудно поддерживать. Кроме того, может возникнуть проблема, если две разные роли хотели наследовать одну и ту же роль (поскольку может быть только один родитель). Вот почему я решил создать решение «многие-ко-многим». Вместо того, чтобы иметь только родительский класс в ролевом классе, я сначала поместил это в класс ролей:

 /** * @ORM\ManyToMany(targetEntity="Role") * @ORM\JoinTable(name="role_permission", * joinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}, * inverseJoinColumns={@ORM\JoinColumn(name="permission_id", referencedColumnName="id")} * ) */ protected $children; 

После этого я переписал функцию buildRolesTree следующим образом:

 private function buildRolesTree() { $hierarchy = array(); $roles = $this->em->createQuery('select r, p from AltGrBaseBundle:Role r JOIN r.children p')->execute(); foreach ($roles as $role) { /* @var $role Role */ if (count($role->getChildren()) > 0) { $roleChildren = array(); foreach ($role->getChildren() as $child) { /* @var $child Role */ $roleChildren[] = $child->getRole(); } $hierarchy[$role->getRole()] = $roleChildren; } } return $hierarchy; } 

Результатом является возможность создания нескольких легко поддерживаемых деревьев. Например, у вас может быть дерево ролей, определяющее роль ROLE_SUPERADMIN и полностью отдельное дерево, определяющее роль ROLE_ADMIN, с несколькими ролями, совместно используемыми между ними. Хотя следует избегать круговых соединений (роли должны быть выложены как деревья, без каких-либо круговых связей между ними), не должно быть никаких проблем, если это происходит на самом деле. Я не тестировал это, но просматривая код buildRoleMap, очевидно, что он сбрасывает любые дубликаты. Это также должно означать, что он не застрянет в бесконечных циклах, если произойдет круговое соединение, но это определенно требует большего тестирования.

Надеюсь, это окажется полезным для кого-то.

Поскольку иерархия роли не меняется часто, это быстрый класс для кэширования memcached.

 <?php namespace .....; use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Lsw\MemcacheBundle\Cache\MemcacheInterface; /** * RoleHierarchy defines a role hierarchy. */ class RoleHierarchy implements RoleHierarchyInterface { /** * * @var MemcacheInterface */ private $memcache; /** * * @var array */ private $hierarchy; /** * * @var array */ protected $map; /** * Constructor. * * @param array $hierarchy An array defining the hierarchy */ public function __construct(array $hierarchy, MemcacheInterface $memcache) { $this->hierarchy = $hierarchy; $roleMap = $memcache->get('roleMap'); if ($roleMap) { $this->map = unserialize($roleMap); } else { $this->buildRoleMap(); // cache to memcache $memcache->set('roleMap', serialize($this->map)); } } /** * {@inheritdoc} */ public function getReachableRoles(array $roles) { $reachableRoles = $roles; foreach ($roles as $role) { if (!isset($this->map[$role->getRole()])) { continue; } foreach ($this->map[$role->getRole()] as $r) { $reachableRoles[] = new Role($r); } } return $reachableRoles; } protected function buildRoleMap() { $this->map = array(); foreach ($this->hierarchy as $main => $roles) { $this->map[$main] = $roles; $visited = array(); $additionalRoles = $roles; while ($role = array_shift($additionalRoles)) { if (!isset($this->hierarchy[$role])) { continue; } $visited[] = $role; $this->map[$main] = array_unique(array_merge($this->map[$main], $this->hierarchy[$role])); $additionalRoles = array_merge($additionalRoles, array_diff($this->hierarchy[$role], $visited)); } } } } 

Я надеюсь, что это поможет вам.

 function getRoles() { // return array(1=>'ROLE_ADMIN',2=>'ROLE_USER'); return array(new UserRole($this)); } 

Вы можете получить хорошую идею, где определить роли безопасности?

http://php-and-symfony.matthiasnoback.nl/ (2012 28 июля)