[英]Symfony - Efficient access control for (dynamic) hierarchical roles
I need some advice on how to handle access control for the following scenario: 我需要一些关于如何处理以下场景的访问控制的建议:
The application should support many corporations. 该应用程序应该支持许多公司。
My instinct is to create either a many-to-many relationship per entity for their admins (eg region_id
, user_id
). 我的直觉是为他们的管理员创建每个实体的多对多关系(例如
region_id
, user_id
)。 Depending on performance, I could go with a more denormalized table with user_id
, corporation_id
, company_id
, region_id
, and store_id
. 根据性能,我可以使用带有
user_id
, corporation_id
, company_id
, region_id
和store_id
的更加非规范化的表。 Then I'd create a voter class (unanimous strategy): 然后我会创建一个选民类(一致的策略):
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
}
Since the permissions are hierarchical, the getAdmins()
function will check all owners for admins as well. 由于权限是分层的,因此
getAdmins()
函数也会检查所有所有者的管理员。 For instance: $region->getAdmins()
will also return admins for the owning company, and corporation. 例如:
$region->getAdmins()
也将返回拥有公司和公司的管理员。
I feel like I'm missing something obvious. 我觉得我错过了一些明显的东西。 Depending on how I implement the
getAdmins()
function, this approach will require at least one hit to the db every vote. 根据我实现
getAdmins()
函数的方式,这种方法每次投票都需要至少一次命中数据库。 Is there a "better" way to go about this? 有没有“更好”的方式来解决这个问题?
Thanks in advance for your help. 在此先感谢您的帮助。
I did just what I posed above, and it is working well. 我做了我上面提到的,它运作良好。 The voter was easy to implement per the Symfony cookbook .
根据Symfony食谱 ,选民很容易实施。 The many-to-many
<entity>_owners
tables work fine. 多对多
<entity>_owners
表工作正常。
To handle the hierarchical permissions, I used cascading calls in the entities. 为了处理分层权限,我在实体中使用了级联调用。 Not elegant, not efficient, but not to bad in terms of speed.
不优雅,不高效,但速度方面也不差。 I'm sure refactor this to use a single DQL query soon, but cascading calls work for now:
我确定很快就会重构这个,以便使用单个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));
}
...
}
The Region
entity would also implement OwnableInterface
and its getOwners()
would then call getCompany()->getOwners()
, etc. Region
实体也将实现OwnableInterface
然后它的getOwners()
将调用getCompany()->getOwners()
等。
There were problems with array_merge
if there were no owners (null), so the new $effectiveOwners ArrayCollection
seems to work well. 如果没有所有者(null),则
array_merge
存在问题,因此新的$effectiveOwners ArrayCollection
似乎运行良好。
Here is the voter. 这是选民。 I stole most of the voter code and
OwnableInterface
and OwnerInterface
from KnpRadBundle : 我偷了大多数选民的代码和
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);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.