Jak zmienić hierarchię ról w Symfony2?

W moim projekcie muszę przechowywać hierarchię ról w bazie danych i dynamicznie tworzyć nowe role. W Symfony2 hierarchia ról jest domyślnie przechowywana w security.yml. Co znalazłem:

Istnieje usługa security.role_hierarchy (Symfony\Component\Security\Core\Role\RoleHierarchy); Ta usługa otrzymuje tablicę ról w konstruktorze:

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

    $this->buildRoleMap();
}

I $hierarchy własność jest prywatna.

Ten argument pochodzi z konstruktora \Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension::createRoleHierarchy() który używa ról z config, jak rozumiem:

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

Wydaje mi się, że najlepszym sposobem jest skompiluj tablicę ról z bazy danych i ustaw ją jako argument dla usługi. Ale jeszcze nie zrozumiałem, jak to zrobić.

Drugim sposobem, jaki widzę, jest zdefiniowanie własnej RoleHierarchy klasy odziedziczonej po podstawowej. Ale ponieważ w klasie base RoleHierarchy właściwość $hierarchy jest zdefiniowana jako prywatna, to musiałbym przedefiniować wszystkie funkcje z klasy base RoleHierarchy. Ale nie sądzę, że jest to dobry sposób na OOP i Symfony...

 25
Author: martin, 2012-07-22

6 answers

Rozwiązanie było proste. Najpierw stworzyłem byt ról.

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;

    ...
}

Potem powstała usługa Rolehierarchia, rozszerzona z rodzimej Symfony. Odziedziczyłem konstruktor, dodałem tam EntityManager i dostarczyłem oryginalnemu konstruktorowi nową tablicę ról zamiast starej:

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

... i przedefiniował ją jako usługę:

<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>
To wszystko. Może w moim kodzie jest coś niepotrzebnego. Może da się pisać lepiej. Ale myślę, że główna idea jest teraz widoczna.
 37
Author: zIs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-11 19:25:46

Miałem zrobić to samo jak zIs (aby zapisać Rolehierarchię w bazie danych), ale nie mogę załadować pełnej hierarchii ról wewnątrz konstruktora, tak jak zIs, ponieważ musiałem załadować niestandardowy filtr doctrine wewnątrz zdarzenia kernel.request. Konstruktor będzie nazywany przed kernel.request, więc nie było to dla mnie rozwiązaniem.

Dlatego sprawdziłem komponent bezpieczeństwa i dowiedziałem się, że Symfony wywołuje Niestandardowy Voter, aby sprawdzić roleHierarchy Według użytkowników rola:

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 <[email protected]>
 */
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());
    }
}

Metoda getReachableRoles zwraca wszystkie role, którymi może być użytkownik. Na przykład:

           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 ]
Jeśli użytkownik ma przypisaną rolę ROLE_SUPERVISOR, metoda zwraca role ROLE_SUPERVISOR, ROLE_BRANCH i ROLE_EMP (Role-Objects lub Classes, które implementują RoleInterface)

Ponadto ten zwyczaj zostanie wyłączony, jeśli w 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');
    }

Aby rozwiązać mój problem stworzyłem własny, niestandardowy głos i rozszerzyłem RoleVoter-Klasa też:

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

Jedna uwaga: moja konfiguracja jest podobna do zls. Moja definicja roli (w moim przypadku nazwałem ją grupą):

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

I 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
Może to komuś pomoże.
 13
Author: manixx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-27 08:37:25

Opracowałem pakiet.

Znajdziesz go na https://github.com/Spomky-Labs/RoleHierarchyBundle

 3
Author: Florent Morselli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-15 13:19:19

Moje rozwiązanie zostało zainspirowane rozwiązaniem dostarczonym przez zls. Jego rozwiązanie działało dla mnie doskonale, ale relacja jeden do wielu między rolami oznaczała posiadanie jednego ogromnego drzewa ról, które stało się trudne do utrzymania. Ponadto, problem może wystąpić, jeśli dwie różne role chcą dziedziczyć jedną tę samą rolę (ponieważ może być tylko jeden rodzic). Dlatego postanowiłem stworzyć rozwiązanie wiele do wielu. Zamiast mieć tylko rodzica w klasie ról, najpierw umieściłem to w roli klasa:

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

Potem przepisałem funkcję buildRolesTree TAK:

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

Rezultatem jest możliwość stworzenia kilku łatwych w utrzymaniu drzew. Na przykład możesz mieć drzewo ról definiujące rolę ROLE_SUPERADMIN i całkowicie oddzielne drzewo definiujące rolę ROLE_ADMIN z kilkoma rolami współdzielonymi między nimi. Chociaż należy unikać połączeń kołowych (role powinny być ułożone jak drzewa, bez żadnych połączeń kołowych między nimi), nie powinno być problemy, jeśli tak się stanie. Nie testowałem tego, ale przechodząc przez kod buildRoleMap, jest oczywiste, że wyrzuca wszystkie duplikaty. Powinno to również oznaczać, że nie utknie w niekończących się pętlach, jeśli wystąpi połączenie kołowe, ale to zdecydowanie wymaga więcej testów.

Mam nadzieję, że to komuś pomoże.
 2
Author: Andrej Mohar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-26 13:24:45

Ponieważ hierarchia ról nie zmienia się często, jest to szybka Klasa do buforowania do 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
Author: elachance,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-15 23:36:47

Mam nadzieję, że to ci pomoże.

function getRoles()
{

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

Możesz uzyskać dobry pomysł z, Gdzie zdefiniować role bezpieczeństwa?

Http://php-and-symfony.matthiasnoback.nl / (2012 Lipiec 28)

 -3
Author: hello,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:53:59