在我的项目中,我需要在数据库中存储角色层次结构并动态创建新角色。
在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()
的构造函数
如我所知,它使用配置中的角色:
$container->setParameter('security.role_hierarchy.roles', $config['role_hierarchy']);
在我看来,最好的方法是从数据库中编译一组角色并将其设置为服务的参数。但我还没有明白该怎么做。
我看到的第二种方法是定义从基础继承的RoleHierarchy
类。但是因为在基类RoleHierarchy
类中,$hierarchy
属性被定义为私有,所以我必须重新定义基类RoleHierarchy
类中的所有函数。但我认为这不是一个好的OOP和Symfony方式......
答案 0 :(得分:38)
解决方案很简单。 首先,我创建了一个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>
这就是全部。 也许,我的代码中有一些不必要的东西。也许有可能写得更好。但我认为,这个主要观点现在很明显。
答案 1 :(得分:14)
我做了同样的事情,比如zIs(将RoleHierarchy存储在数据库中),但我不能像zIs那样在构造函数中加载完整的角色层次结构,因为我必须在{{1}内部加载自定义的doctrine过滤器事件。构造函数将在kernel.request
之前被称为,因此我无法选择。
因此,我检查了安全组件,发现kernel.request
调用自定义Symfony
根据用户角色检查Voter
:
roleHierarchy
getReachableRoles方法返回用户可以使用的所有角色。例如:
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());
}
}
如果用户分配了ROLE_SUPERVISOR角色,则Method返回角色ROLE_SUPERVISOR,ROLE_BRANCH和ROLE_EMP(角色对象或类,实现RoleInterface)
此外,如果 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 ]
security.yaml
为了解决我的问题,我创建了自己的自定义选民并扩展了RoleVoter-Class:
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');
}
一个注意:我的设置类似于zls。我对角色的定义(在我的例子中,我称之为群组):
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;
}
}
用户定义:
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
也许这有助于某人。
答案 2 :(得分:3)
我开发了一个包。您可以在https://github.com/Spomky-Labs/RoleHierarchyBundle
找到它答案 3 :(得分:2)
我的解决方案受到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代码,很明显它会转储任何重复项。这也意味着如果发生圆形连接,它不会卡在无限循环中,但这肯定需要更多的测试。
我希望这证明对某人有帮助。
答案 4 :(得分:0)
由于角色层次结构不经常更改,因此这是一个快速类来缓存到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));
}
}
}
}
答案 5 :(得分:-3)
我希望这会对你有所帮助。
function getRoles()
{
// return array(1=>'ROLE_ADMIN',2=>'ROLE_USER');
return array(new UserRole($this));
}
你可以从中获得一个好主意, Where to define security roles?
http://php-and-symfony.matthiasnoback.nl/(2012年7月28日)