简体   繁体   English

如何在Symfony2中更改角色层次结构存储?

[英]How to change role hierarchy storage in Symfony2?

In my project I need to store role hierarchy in database and create new roles dynamically. 在我的项目中,我需要将角色层次结构存储在数据库中并动态创建新角色。 In Symfony2 role hierarchy is stored in security.yml by default. 在Symfony2中,默认情况下,角色层次结构存储在security.yml What have I found: 我发现了什么:

There is a service security.role_hierarchy ( Symfony\\Component\\Security\\Core\\Role\\RoleHierarchy ); 有一个服务security.role_hierarchySymfony\\Component\\Security\\Core\\Role\\RoleHierarchy ); This service receives a roles array in constructor: 此服务在构造函数中接收角色数组:

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

    $this->buildRoleMap();
}

and the $hierarchy property is private. $hierarchy属性是私有的。

This argument comes in constructor from \\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\SecurityExtension::createRoleHierarchy() which uses roles from config, as I understood: 这个参数来自\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\SecurityExtension::createRoleHierarchy()构造函数,它使用了config中的角色,正如我所理解的:

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

It seems me that the best way is to compile an array of roles from database and set it as an argument for the service. 在我看来,最好的方法是从数据库中编译一组角色并将其设置为服务的参数。 But I haven't yet understood how to do it. 但我还没有明白该怎么做。

The second way I see is to define my own RoleHierarchy class inherited from the base one. 我看到的第二种方法是定义从基础继承的我自己的RoleHierarchy类。 But since in the base RoleHierarchy class the $hierarchy property is defined as private, than I would have to redefine all the functions from the base RoleHierarchy class. 但是因为在基本RoleHierarchy类中, $hierarchy属性被定义为private,所以我必须重新定义基本RoleHierarchy类中的所有函数。 But I don't think it is a good OOP and Symfony way... 但我认为这不是一个好的OOP和Symfony方式......

The solution was simple. 解决方案很简单。 First I created a Role entity. 首先,我创建了一个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;

    ...
}

after that created a RoleHierarchy service, extended from the Symfony native one. 之后创建了一个RoleHierarchy服务,从Symfony本地服务扩展而来。 I inherited the constructor, added an EntityManager there and provided an original constructor with a new roles array instead of the old one: 我继承了构造函数,在那里添加了一个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;
    }
}

... and redefined it as a service: ......并将其重新定义为服务:

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

That's all. 就这样。 Maybe, there is something unnecessary in my code. 也许,我的代码中有一些不必要的东西。 Maybe it is possible to write better. 也许有可能写得更好。 But I think, that main idea is evident now. 但我认为,这个主要观点现在很明显。

I had do the same thing like zIs (to store the RoleHierarchy in the database) but i cannot load the complete role hierarchy inside the Constructor like zIs did, because i had to load a custom doctrine filter inside the kernel.request event. 我做了同样的事情,比如zIs(将RoleHierarchy存储在数据库中),但我不能像zIs那样在构造函数中加载完整的角色层次结构,因为我必须在kernel.request事件中加载自定义doctrine过滤器。 The Constructor will be called before the kernel.request so it was no option for me. 构造函数将在kernel.request 之前 kernel.request因此对我来说无处可选。

Therefore I checked the security component and found out that Symfony calls a custom Voter to check the roleHierarchy according to the users role: 因此,我检查了安全组件,发现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());
    }
}

The getReachableRoles Method returns all roles the user can be. getReachableRoles方法返回用户可以使用的所有角色。 For example: 例如:

           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 ]

If the user has the ROLE_SUPERVISOR role assigned the Method returns the roles ROLE_SUPERVISOR, ROLE_BRANCH and ROLE_EMP (Role-Objects or Classes, which implementing RoleInterface) 如果用户分配了ROLE_SUPERVISOR角色,则Method返回角色ROLE_SUPERVISOR,ROLE_BRANCH和ROLE_EMP(角色对象或类,实现RoleInterface)

Furthermore this custom voter will be disabled if there is no RoleHierarchy defined in the security.yaml 此外,如果security.yaml没有定义RoleHierarchy,则将禁用此自定义选民

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

To solve my issue I created my own custom Voter and extended the RoleVoter-Class, too: 为了解决我的问题,我创建了自己的自定义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: My Setup is similar to zls ones. 注意:我的设置类似于zls。 My Definition for the role (in my case I called it 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

And the userdefinition: 而userdefinition:

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

Maybe this helps someone. 也许这有助于某人。

I developped a bundle. 我开发了一个包。

You can find it at https://github.com/Spomky-Labs/RoleHierarchyBundle 您可以在https://github.com/Spomky-Labs/RoleHierarchyBundle找到它

My solution was inspired by the solution provided by zls. 我的解决方案受到zls提供的解决方案的启发。 His solution worked perfectly for me, but the one-to-many relation between the roles meant having one huge role tree, which would become hard to maintain. 他的解决方案对我来说非常有效,但角色之间的一对多关系意味着拥有一个巨大的角色树,这将很难维护。 Also, a problem might occur if two different roles wanted to inherit one same role (as there could only be one parent). 此外,如果两个不同的角色想要继承一个相同的角色(因为只能有一个父角色),则可能会出现问题。 That's why I decided to create a many-to-many solution. 这就是我决定创建多对多解决方案的原因。 Instead of having only the parent in the role class, I have first put this in the role class: 我首先将它放在角色类中,而不是只在角色类中使用父级:

/**
 * @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;

After that I rewrote the buildRolesTree function like so: 之后我重写了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;
}

The result is the ability to create several easily maintained trees. 结果是能够创建几个易于维护的树。 For instance, you can have a tree of roles defining the ROLE_SUPERADMIN role and entirely separate tree defining a ROLE_ADMIN role with several roles shared between them. 例如,您可以拥有一个定义ROLE_SUPERADMIN角色的角色树,并完全分离定义ROLE_ADMIN角色的树,并在它们之间共享多个角色。 Although circular connections should be avoided (roles should be laid out as trees, without any circular connections between them), there should be no problems if it actually happens. 虽然应该避免循环连接(角色应该布置成树,但它们之间没有任何圆形连接),如果实际发生则应该没有问题。 I haven't tested this, but going through the buildRoleMap code, it is obvious it dumps any duplicates. 我没有测试过这个,但是通过buildRoleMap代码,很明显它会转储任何重复项。 This should also mean it won't get stuck in endless loops if the circular connection occurs, but this definitely needs more testing. 这也意味着如果发生圆形连接,它不会卡在无限循环中,但这肯定需要更多的测试。

I hope this proves helpful to someone. 我希望这证明对某人有帮助。

Since role hierarchy don't change often, this a quick class to cache to memcached. 由于角色层次结构不经常更改,因此这是一个缓存到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));
            }
        }
    }
}

I hope this will help you. 我希望这能帮到您。

function getRoles()
{

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

You can get a good idea from, Where to define security roles? 您可以从哪里定义安全角色获得一个好主意

http://php-and-symfony.matthiasnoback.nl/ ( 2012 July 28 ) http://php-and-symfony.matthiasnoback.nl/(2012 July 28)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM