Comment changer le stockage de la hiérarchie des rôles dans Symfony2?

dans mon projet, je dois stocker la hiérarchie des rôles dans la base de données et créer de nouveaux rôles de manière dynamique. Dans Symfony2, la hiérarchie des rôles est stockée dans security.yml par défaut. Qu'ai-je trouvé:

Il y a un service security.role_hierarchy ( SymfonyComponentSecurityCoreRoleRoleHierarchy ); Ce service reçoit un tableau de rôles dans le constructeur:

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

    $this->buildRoleMap();
}

et la propriété $hierarchy est privée.

cet argument vient dans le constructeur de SymfonyBundleSecurityBundleDependencyInjectionSecurityExtension::createRoleHierarchy() qui utilise les rôles de config, comme je l'ai compris:

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

il me semble que la meilleure façon est de compiler un tableau de rôles à partir de la base de données et de le Définir comme un argument pour le service. Mais je n'ai pas encore compris comment le faire.

la deuxième façon que je vois est de définir ma propre classe RoleHierarchy hérité de la base. Mais puisque dans la classe de base RoleHierarchy la propriété $hierarchy est définie comme privée, que je devrais redéfinir tous les fonctions à partir de la base RoleHierarchy classe. Mais je ne pense pas que c'est une bonne programmation orientée objet et Symfony...

25
demandé sur martin 2012-07-22 21:15:28

6 réponses

la solution était simple. J'ai d'abord créé une entité de rôle.

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;

    ...
}

après cela a créé un service RoleHierarchy, étendu de la Symfony native un. J'ai hérité du constructeur, y ai ajouté un EntityManager et fourni un constructeur original avec un nouveau tableau de rôles au lieu de l'ancien:

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

... et l'a redéfini comme un 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>

C'est tout. Peut-être, il y a quelque chose inutile dans mon code. Peut-être est-il possible d'écrire mieux. Mais je pense que cette idée principale est évidente maintenant.

37
répondu zIs 2012-08-11 19:25:46

j'ai fait la même chose que zIs (pour stocker la RoleHierarchy dans la base de données) mais je ne peux pas charger la hiérarchie complète des rôles à l'intérieur du constructeur comme zIs l'a fait, parce que j'ai dû charger un filtre de doctrine personnalisé à l'intérieur de l'événement kernel.request . Le constructeur sera appelé avant le kernel.request donc ce n'était pas une option pour moi.

donc j'ai vérifié le composant de sécurité et j'ai découvert que Symfony appelle une Voter pour vérifier le roleHierarchy selon le rôle de l'utilisateur:

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

la méthode getReachableRoles renvoie tous les rôles que l'utilisateur peut jouer. Par exemple:

           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 ]

si l'Utilisateur a le rôle de RÔLE_SUPERVISEUR assigné la méthode renvoie les rôles ROLE_SUPERVISEUR, ROLE_BRANCH et ROLE_EMP (rôle-objets ou Classes, qui implémentent RoleInterface)

en outre, cet électeur de coutume sera désactivé si il n'y a pas de RoleHierarchy défini dans le 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');
    }

pour résoudre mon problème, j'ai créé mon propre électeur sur mesure et étendu la classe RoleVoter, aussi:

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: ma configuration est similaire à celles de zls. Ma définition du rôle (dans mon cas je l'ai appelé Groupe it):

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

et la définition userd:

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

peut-être que ça aide quelqu'un.

13
répondu manixx 2013-02-27 08:37:25

j'ai développé un paquet.

vous pouvez le trouver à https://github.com/Spomky-Labs/RoleHierarchyBundle

3
répondu Florent Morselli 2015-02-15 13:19:19

ma solution a été inspirée par la solution fournie par zls. Sa solution fonctionnait parfaitement pour moi, mais la relation un-à-plusieurs entre les rôles signifiait avoir un arbre de rôle énorme, qui deviendrait difficile à maintenir. En outre, un problème pourrait se poser si deux rôles différents voulaient hériter d'un même rôle (puisqu'il ne pouvait y avoir qu'un seul parent). C'est pourquoi j'ai décidé de créer une solution de plusieurs à plusieurs. Au lieu d'avoir seulement le parent dans la classe de rôle, j'ai d'abord mis ceci dans la classe de rôle:

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

après cela j'ai réécrit la fonction buildRolesTree comme ceci:

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

le résultat est la capacité de créer plusieurs arbres facilement entretenus. Par exemple, vous pouvez avoir un arbre de rôles définissant le rôle de ROLE_SUPERADMIN et un arbre entièrement séparé définissant un rôle de ROLE_ADMIN avec plusieurs rôles partagés entre eux. Bien que les connexions circulaires doivent être évitées (les rôles doivent être présentés comme des arbres, sans aucune connexion circulaire) entre eux), il ne devrait pas y avoir de problèmes si cela se produit réellement. Je ne l'ai pas testé, mais en parcourant le code buildRoleMap, il est évident qu'il supprime tous les doublons. Cela devrait également signifier qu'il ne sera pas coincé dans des boucles sans fin si la connexion circulaire se produit, mais cela a certainement besoin de plus de tests.

j'espère que cela se révélera utile à quelqu'un.

2
répondu Andrej Mohar 2013-09-26 13:24:45

puisque la hiérarchie des rôles ne change pas souvent, c'est une classe rapide à mettre en cache pour 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));
            }
        }
    }
}
0
répondu elachance 2015-01-15 23:36:47

j'espère que cela vous aidera.

function getRoles()
{

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

Vous pouvez obtenir une bonne idée de, où définir les rôles en matière de sécurité?

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

-3
répondu hello 2017-05-23 11:53:59