0

次のシナリオでアクセス制御を処理する方法についてアドバイスが必要です。

  • 株式会社
    • 1 つまたは複数の会社を持っている
    • 1 つまたは複数の ROLE_CORP_ADMIN を持つ
  • 会社
    • 1 つまたは複数のリージョンがあります。
    • 1 つまたは複数の ROLE_COMPANY_ADMIN があります。
  • 領域:
    • ゼロまたは多数の店舗があります。
    • 1 つまたは複数の ROLE_REGION_ADMIN があります。
  • 店:
    • ゼロまたは多くの資産を持っています。
    • 1 つまたは複数の ROLE_STORE_ADMIN があります。
    • ゼロまたは多数の ROLE_STORE_EMPLOYEE があります。
    • 0 個または多数の ROLE_STORE_CUSTOMER を持っている (多いほどよい)。

アプリケーションは、多くの企業をサポートする必要があります。

私の本能は、管理者のためにエンティティごとに多対多の関係を作成することです (例: region_iduser_id)。パフォーマンスに応じてuser_idcorporation_idcompany_idregion_id、およびを使用して、より非正規化されたテーブルを使用できstore_idます。次に、有権者クラス (全会一致の戦略) を作成します。

public function vote(TokenInterface $token, $object, array $attributes)
{
    // If SUPER_ADMIN, return ACCESS_GRANTED
    // If User in $object->getAdmins(), return ACCESS_GRANTED
    // Else, return ACCESS_DENIED
}

権限は階層的であるため、getAdmins()関数は管理者のすべての所有者もチェックします。例: $region->getAdmins()所有会社と企業の管理者も返します。

明らかな何かが欠けているように感じます。関数の実装方法によってはgetAdmins()、このアプローチでは、投票ごとに少なくとも 1 つのデータベースへのヒットが必要になります。これについて「より良い」方法はありますか?

よろしくお願いします。

4

1 に答える 1

2

私は上で提起したことを行いましたが、うまく機能しています。Symfony cookbookに従って、voter は簡単に実装できました。多対多の<entity>_ownersテーブルは正常に機能します。

階層的なアクセス許可を処理するために、エンティティでカスケード呼び出しを使用しました。エレガントでも効率的でもありませんが、速度の点では悪くありません。すぐに単一の DQL クエリを使用するようにこれをリファクタリングすると確信していますが、カスケード呼び出しは今のところ機能します。

class Store implements OwnableInterface
{
    ....

    /**
     * @ORM\ManyToMany(targetEntity="Person")
     * @ORM\JoinTable(name="stores_owners",
     *      joinColumns={@ORM\JoinColumn(name="store_id", referencedColumnName="id", nullable=true)},
     *      inverseJoinColumns={@ORM\JoinColumn(name="person_id", referencedColumnName="id")}
     *      )
     *
     * @var ArrayCollection|Person[]
     */
    protected $owners;

    ...

    public function __construct()
    {
        $this->owners = new ArrayCollection();
    }

    ...

    /**
     * Returns all people who are owners of the object
     * @return ArrayCollection|Person[]
     */
    function getOwners()
    {
        $effectiveOwners = new ArrayCollection();

        foreach($this->owners as $owner){
            $effectiveOwners->add($owner);
        }

        foreach($this->getRegion()->getOwners() as $owner){
            $effectiveOwners->add($owner);
        }

        return $effectiveOwners;
    }

    /**
     * Returns true if the person is an owner.
     * @param Person $person
     * @return boolean
     */
    function isOwner(Person $person)
    {
        return ($this->getOwners()->contains($person));
    }

    ...

}

Regionエンティティも実装OwnableInterfaceし、それから などgetOwners()を呼び出します。getCompany()->getOwners()

array_merge所有者がいない場合(null)に問題があったため、新しい$effectiveOwners ArrayCollectionものはうまく機能しているようです。

これが投票者です。私はほとんどの有権者コードを盗みOwnableInterfaceKnpRadBundleOwnerInterfaceから:

use Acme\AcmeBundle\Security\OwnableInterface;
use Acme\AcmeBundle\Security\OwnerInterface;
use Acme\AcmeUserBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;

class IsOwnerVoter implements VoterInterface
{

    const IS_OWNER = 'IS_OWNER';

    private $container;

    public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container) {
        $this->container = $container;
    }

    public function supportsAttribute($attribute)
    {
        return self::IS_OWNER === $attribute;
    }

    public function supportsClass($class)
    {
        if (is_object($class)) {
            $ref = new \ReflectionObject($class);

            return $ref->implementsInterface('Acme\AcmeBundle\Security\OwnableInterface');
        }

        return false;
    }

    public function vote(TokenInterface $token, $object, array $attributes)
    {
        foreach ($attributes as $attribute) {

            if (!$this->supportsAttribute($attribute)) {
                continue;
            }

            if (!$this->supportsClass($object)) {
                return self::ACCESS_ABSTAIN;
            }

            // Is the token a super user? This will check roles, not user.
            if ( $this->container->get('security.context')->isGranted('ROLE_SUPER_ADMIN') ) {
                return VoterInterface::ACCESS_GRANTED;
            }

            if (!$token->getUser() instanceof User) {
                return self::ACCESS_ABSTAIN;
            }

            // check to see if this token is a user.
            if (!$token->getUser()->getPerson() instanceof OwnerInterface) {
                return self::ACCESS_ABSTAIN;
            }

            // Is this person an owner?
            if ($this->isOwner($token->getUser()->getPerson(), $object)) {
                return self::ACCESS_GRANTED;
            }

            return self::ACCESS_DENIED;
        }

        return self::ACCESS_ABSTAIN;
    }

    private function isOwner(OwnerInterface $owner, OwnableInterface $ownable)
    {
        return $ownable->isOwner($owner);
    }
}
于 2013-08-29T17:41:15.007 に答える