2013-08-23 2 views
0

мне нужно несколько советов о том, как обращаться контроль доступа к следующему сценарию:Symfony - эффективный контроль доступа (динамические) иерархические роли

  • Корпорация
    • Имеет один или много компаний
    • имеет один или многие ROLE_CORP_ADMIN
  • Компания
    • Имеет на е или во многих регионах.
    • Имеет один или несколько ROLE_COMPANY_ADMIN.
  • Регион:
    • имеет ноль или много магазинов.
    • Имеет один или несколько ROLE_REGION_ADMIN.
  • Магазин:
    • имеет ноль или много активов.
    • Имеет один или несколько ROLE_STORE_ADMIN.
    • Имеет ноль или много ROLE_STORE_EMPLOYEE.
    • Имеет нулевой или много ROLE_STORE_CUSTOMER (многие лучше).

Приложение должно поддерживать многие корпорации.

Моим инстинктом является создание отношений между многими для многих для их администраторов (например, region_id, user_id). В зависимости от производительности я мог бы пойти с более денормализованным столом с user_id, corporation_id, company_id, region_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(), для этого подхода потребуется по крайней мере один удар по db на каждый голос. Есть ли «лучший» способ сделать это?

Заранее за вашу помощь.

ответ

2

Я сделал то, что я поставил выше, и он работает хорошо. Избирателю было легко реализовать за Symfony cookbook. Таблицы «многие-ко-многим» <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, похоже, работает хорошо.

Это избиратель. Я украл большую часть кода избирателей и OwnableInterface и OwnerInterface от KnpRadBundle:

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); 
    } 
} 
+0

спасибо за this.one вопрос: вы использовали 'ArrayCollection :: contains' для' порядка method.in isOwner' для 'ArrayCollection :: содержит 'для возврата true, он должен содержать ** тот же экземпляр ** объекта, который он ищет. Как это возможно, если вы получаете объект Person из объекта User и сравниваете его с' Человек в другом объекте, например: 'Store', чьи поколения не связаны. – user2268997

Смежные вопросы