3

一般来说,我有以下商业模式:

有用户和组。每个用户只属于一个组,组的数量不是预先确定的(以及大多数站点的用户数量)。还有几个不同的忙碌对象,可能属于用户。

组不是单独的对象,应该由 ACL 本身控制,但它们应该影响其他实体的控制方式,就像 unix 组一样。

有 3 个基本角色:SUPERADMIN、ADMIN 和 USER。

  • SUPERADMIN 可以对任何实体做任何事情。
  • 用户通常能够读/写自己的实体(包括他/她自己)并从他/她的组中读取实体。
  • ADMIN 应该完全控制其组内的实体,但不能控制其他组的实体。我不明白如何在这里应用 ACL 继承(以及这是否可以应用)。

我也很感兴趣,如何在 ACL 中应用拒绝访问。像用户一样,除了登录之外,他的所有字段都具有读/写访问权限。用户应该只阅读他的登录信息。即,提供对他自己的配置文件的读/写访问权限是合乎逻辑的,但拒绝对登录名进行写入,而不是直接定义对他的所有字段(登录名除外)的读/写访问权限。

4

2 回答 2

2

好的,就是这样。代码一点也不完美,但总比没有好。

选民服务。

<?php
namespace Acme\AcmeBundle\Services\Security;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;

class GroupedConcernVoter implements VoterInterface {

    public function __construct(ContainerInterface $container)
    {   
        $this->container = $container;
        $rc = $this->container->getParameter('grouped_concern_voter.config');
        // some config normalization performed
        $this->rightsConfig = $rc;
    }   

    // even though supportsAttribute and supportsClass methods are required by interface,
    // services that I saw, leaves them empty and do not use them

    public function supportsAttribute($attribute)
    {   
        return in_array($attribute, array('OWNER', 'MASTER', 'OPERATOR', 'VIEW', 'EDIT', 'CREATE', 'DELETE', 'UNDELETE', 'DEPLOY'))
            // hacky way to support per-attribute edit and even view rights.
            or preg_match("/^(EDIT|VIEW)(_[A-Z]+)+$/", $attribute);
    }           

    public function supportsClass($object)
    {   
        $object = $object instanceof ObjectIdentity ? $object->getType() : $object;
        // all our business object, which should be manageable by that code have common basic class.
        // Actually it is a decorator over Propel objects with some php magic... nevermind.
        // If one wants similar solution, interface like IOwnableByUserAndGroup with
        // getUserId and getGroupId methods may be defined and used
        return is_subclass_of($object, "Acme\\AcmeBundle\\CommonBusinessObject");
    }       

    function vote(TokenInterface $token, $object, array $attributes)
    {   

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

        if (is_string($object)) {
            $scope = 'own';
            $entity = $object;
        } else {
            if ($object->getUserId() == $this->getUser()->getId()) {
                $scope = 'own';
            } else if ($object->getGroupId() == $this->getUser()->getGroupId()) {
                $scope = 'group';
            } else {
                $scope = 'others';
            }
            $entity = get_class($object);
        }

        $user = $token->getUser();
        $roles = $user->getRoles();
        $role = empty($roles) ? 'ROLE_USER' : $roles[0];

        $rights = $this->getRightsFor($role, $scope, $entity);
        if ($rights === null) return self::ACCESS_ABSTAIN;

        // some complicated logic for checking rights...
        foreach ($attributes as $attr) {
            $a = $attr;
            $field = '';
            if (preg_match("/^(EDIT|VIEW)((?:_[A-Z]+)+)$/", $attr, $m)) list(, $a, $field) = $m;
            if (!array_key_exists($a, $rights)) return self::ACCESS_DENIED;
            if ($rights[$a]) {
                if ($rights[$a] === true
                or  $field === '')
                    return self::ACCESS_GRANTED;
            }
            if (is_array($rights[$a])) {
                if ($field == '') return self::ACCESS_GRANTED;
                $rfield = ltrim(strtolower($field), '_');
                if (in_array($rfield, $rights[$a])) return self::ACCESS_GRANTED;
            }

            return self::ACCESS_DENIED;
        }
    }

    private function getRightsFor($role, $scope, $entity)
    {
        if (array_key_exists($entity, $this->rightsConfig)) {
            $rc = $this->rightsConfig[$entity];
        } else {
            $rc = $this->rightsConfig['global'];
        }
        $rc = $rc[$role][$scope];
        $ret = array();
        foreach($rc as $k => $v) {
            if (is_numeric($k)) $ret[$v] = true;
            else $ret[$k] = $v;
        }
        // hacky way to emulate cumulative rights like in ACL
        if (isset($ret['OWNER'])) $ret['MASTER'] = true;
        if (isset($ret['MASTER'])) $ret['OPERATOR'] = true;
        if (isset($ret['OPERATOR']))
            foreach(array('VIEW', 'EDIT', 'CREATE', 'DELETE', 'UNDELETE') as $r) $ret[$r] = true;
        return $ret;
    }

    private function getUser() {
        if (empty($this->user)) {
            // Not sure, how this shortcut works. This is a service (?) returning current authorized user.
            $this->user = $this->container->get('acme.user.shortcut');
        }
        return $this->user;
    }

}

而配置...实际上,它是特定于实现的,其结构是完全任意的。

grouped_concern_voter.config:
    global:
        ROLE_SUPERADMIN:
            own: [MASTER]
            group: [MASTER]
            others: [MASTER]
        ROLE_ADMIN:
            own: [MASTER]
            group: [MASTER]
            others: []
        ROLE_USER:
            own: [VIEW, EDIT, CREATE]
            group: [VIEW]
            others: []
    "Acme\\AcmeBundle\\User":
        # rights for ROLE_SUPERADMIN are derived from 'global'
        ROLE_ADMIN:
            own:
                VIEW: [login, email, real_name, properties, group_id]
                EDIT: [login, password, email, real_name, properties]
                CREATE: true
            group:
                VIEW: [login, email, real_name, properties]
                EDIT: [login, password, email, real_name, properties]
            # rights for ROLE_ADMIN/others are derived from 'global'
        ROLE_USER:
            own:
                VIEW: [login, password, email, real_name, properties]
                EDIT: [password, email, real_name, properties]
            group: []
            # rights for ROLE_USER/others are derived from 'global'
    "Acme\\AcmeBundle\\Cake":
        # most rights are derived from global here.
        ROLE_ADMIN:
            others: [VIEW]
        ROLE_USER:
            own: [VIEW]
            others: [VIEW]

最后是用法示例。控制器中的某处:

$cake = Acme\AcmeBundle\CakeFactory->produce('strawberry', '1.3kg');
$securityContext = $this->get('security.context');
if ($securityContext->isGranted('EAT', $cake)) {
    die ("The cake is a lie");
}
于 2012-08-21T10:27:38.740 回答
0

创建组时,创建角色 ROLE_GROUP_(group id),使用该角色提升组,并授予使用 rolesecurityidentity 的权限

于 2012-09-21T05:03:39.570 回答