27

私のプロジェクトでは、ロール階層をデータベースに保存し、新しいロールを動的に作成する必要があります。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']);

データベースからロールの配列をコンパイルし、それをサービスの引数として設定するのが最善の方法のようです。しかし、私はまだそれを行う方法を理解していません。

RoleHierarchy2 番目の方法は、基本クラスから継承した独自のクラスを定義することです。ただし、基本RoleHierarchyクラスでは$hierarchyプロパティがプライベートとして定義されているため、基本RoleHierarchyクラスからすべての関数を再定義する必要があります。しかし、それは良い OOP と Symfony の方法だとは思いません...

4

6 に答える 6

40

解決策は簡単でした。まず、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;

    ...
}

その後、Symfonyのネイティブサービスから拡張されたRoleHierarchyサービスを作成しました。コンストラクターを継承し、そこに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>

それで全部です。たぶん、私のコードには不要なものがあります。多分それはよりよく書くことが可能です。しかし、私は、その主なアイデアは今明らかだと思います。

于 2012-08-11T18:19:30.270 に答える
14

zIs と同じこと (RoleHierarchy をデータベースに格納するため) を行いましたが、zIs のようにコンストラクター内に完全なロール階層をロードすることはできません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;
    }
}

1 つの注意: 私のセットアップは zls のものに似ています。役割の私の定義 (私の場合はグループと呼びます):

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

多分これは誰かを助けるでしょう。

于 2013-02-07T14:01:57.910 に答える
3

バンドルを開発しました。

https://github.com/Spomky-Labs/RoleHierarchyBundleで見つけることができます

于 2013-11-16T08:25:39.403 に答える
2

私のソリューションは、zls が提供するソリューションに触発されました。彼のソリューションは私にとっては完璧に機能しましたが、ロール間の 1 対多の関係は、1 つの巨大なロール ツリーを持つことを意味し、維持するのが難しくなりました。また、2 つの異なるロールが 1 つの同じロールを継承する場合、問題が発生する可能性があります (親は 1 つしか存在できないため)。そのため、多対多のソリューションを作成することにしました。役割クラスに親だけを含める代わりに、最初にこれを役割クラスに入れました。

/**
 * @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 コードを調べてみると、重複があれば明らかにダンプされます。これは、循環接続が発生した場合に無限ループに陥らないことも意味しますが、これにはさらにテストが必要です.

これが誰かの役に立てば幸いです。

于 2013-09-26T13:24:45.697 に答える
-3

これがお役に立てば幸いです。

function getRoles()
{

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

「セキュリティ ロールを定義する場所」から良いアイデアを得ることができ ます。

http://php-and-symfony.matthiasnoback.nl/ (2012 年 7 月 28 日)

于 2012-07-28T05:46:46.450 に答える