2

我在 Symfony 2.1 中使用 ACL,我需要创建一个新的 SecurityIdentity,以便我的 ACL 可以设置为某种组的功能。

想象以下情况:有用户(具有不同角色)的组,每个组都有用户信息。在组 1 中,具有 ROLE_ADMIN 的用户不能编辑同一组中的其他用户的信息,但在组 2 中,具有 ROLE_ADMIN 的用户可以编辑其他信息。

所以基本上我的 ACL 的功能会根据用户所在的组而有所不同。

我想我会通过创建一个新的“GroupSecurityIdentity”来解决这个问题。然而,类本身是不够的,因为我在使用它时遇到了这个异常:

$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.

我的问题是:如何“注册”我的新 SecurityIdentity,以便可以将其用作 RoleSecurityIdentity 和 UserSecurityIdentity?

有什么更好的方法来实现与我想做的类似的系统?

4

2 回答 2

2

2年前我走上了这条路,结果证明这是一个糟糕的决定。修改 ACL 系统很困难,并且在更新 Symfony 时可能会导致问题。至少有 2 个更好的解决方案。我会将它们全部列出,以便您决定哪个最适合您的需求。

新的安全身份

我正在使用GroupInterfaceFOSUserBundle 中的,但我想您也可以使用自己的。需要添加以下文件:

接下来:通过提供以下参数重新连接依赖注入容器:

<parameter key="security.acl.dbal.provider.class">
    Acme\Bundle\DemoBundle\Security\Acl\Dbal\MutableAclProvider
</parameter>
<parameter key="security.acl.security_identity_retrieval_strategy.class">
    Acme\Bundle\DemoBundle\Security\Acl\Domain\SecurityIdentityRetrievalStrategy
</parameter>

是时候交叉手指,看看它是否有效。由于这是旧代码,我可能忘记了一些东西。

为组使用角色

这个想法是让组名对应于角色。

一种简单的方法是让您的User实体重新实现UserInterface::getRoles

public function getRoles()
{
    $roles = parent::getRoles();

    // This can be cached should there be any performance issues
    // which I highly doubt there would be.
    foreach ($this->getGroups() as $group) {
        // GroupInterface::getRole() would probably have to use its
        // canonical name to get something like `ROLE_GROUP_NAME_OF_GROUP`
        $roles[] = $group->getRole();
    }

    return $roles;
}

一个可能的实现GroupInterface::getRole()

public function getRole()
{
    $name = $this->getNameCanonical();

    return 'ROLE_GROUP_'.mb_convert_case($name, MB_CASE_UPPER, 'UTF-8');
}

现在只需按照食谱文章中的说明创建所需的 ACE 。

创建选民

最后,您可以使用自定义投票器来检查特定组的存在以及用户是否有权访问所述对象。一个可能的实现:

<?php

namespace Acme\Bundle\DemoBundle\Authorization\Voter;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;

class MySecureObjectVoter implements VoterInterface
{
    /**
     * {@inheritDoc}
     */
    public function supportsAttribute($attribute)
    {
        $supported = array('VIEW');

        return in_array($attribute, $supported);
    }

    /**
     * {@inheritDoc}
     */
    public function supportsClass($class)
    {
        return $class instanceof GroupableInterface;
    }

    /**
     * {@inheritDoc}
     */
    public function vote(TokenInterface $token, $object, array $attributes)
    {
        $result = VoterInterface::ACCESS_ABSTAIN;

        if (!$object instanceof MySecureObject) {
            return VoterInterface::ACCESS_ABSTAIN;
        }

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

            // Access is granted, if the user and object have at least 1
            // group in common.
            if ('VIEW' === $attribute) {
                $objGroups = $object->getGroups();
                $userGroups = $token->getUser()->getGroups();

                foreach ($userGroups as $userGroup) {
                    foreach ($objGroups as $objGroup) {
                        if ($userGroup->equals($objGroup)) {
                            return VoterInterface::ACCESS_GRANTED;
                        }
                    }
                }

                return voterInterface::ACCESS_DENIED;
            }
        }
    }
}

有关选民的更多详细信息,请参阅食谱示例


我会避免创建自定义安全身份。使用提供的其他两种方法。如果您将拥有大量记录并且每个记录都必须具有不同的访问设置,则第二种解决方案效果最好。投票者可用于设置简单的访问授权逻辑(大多数较小的系统似乎都属于这种情况)或需要灵活性时。

于 2013-04-18T20:16:58.133 回答
0

我在这里写下我的答案以跟踪此错误消息。

我用 ACL 实现了组支持,我不得不破解一点 symfony 核心“MutableAclProvider.php”

protected function getSelectSecurityIdentityIdSql(SecurityIdentityInterface $sid)
{
    if ($sid instanceof UserSecurityIdentity) {
        $identifier = $sid->getClass().'-'.$sid->getUsername();
        $username = true;
    } elseif ($sid instanceof RoleSecurityIdentity) {
        $identifier = $sid->getRole();
        $username = false;
    }else {
        //throw new \InvalidArgumentException('$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.');
        $identifier = $sid->getClass().'-'.$sid->getGroupname();
        $username = true;
    }

    return sprintf(
        'SELECT id FROM %s WHERE identifier = %s AND username = %s',
        $this->options['sid_table_name'],
        $this->connection->quote($identifier),
        $this->connection->getDatabasePlatform()->convertBooleans($username)
    );
}

即使提供的对象不是 UserSecurityIdentity 或 RoleSecurityIdentity 的实例,它也会返回一个值。所以现在我可以使用自定义的“GroupSecurityIdentity”

这并不容易实施,但非常适合我的系统。

于 2014-09-12T17:11:41.183 回答