【问题标题】:symfony2 acl groupssymfony2 acl 组
【发布时间】:2012-04-27 08:56:43
【问题描述】:

一般我有以下商业模式:

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

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

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

  • SUPERADMIN 可以对任何实体执行任何操作。
  • 用户通常能够读/写自己的实体(包括他/她自己)并读取 他/她的团队中的实体。
  • ADMIN 应该完全控制 他的组内的实体,但不是来自其他组的实体。我不 了解如何在这里应用 ACL 继承(以及这是否可以 完全适用)。

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

【问题讨论】:

  • 好的,我已经在不使用 ACL 的情况下解决了这个问题,但可以使用 ACL 集成:我注册了自己的选民服务。
  • @krilloid - 我和你有同样的问题。你能分享你的选民服务代码吗?将不胜感激。谢谢
  • kirilloid 如果您找到合适的设计,最好将其发布在这里,回答您自己的问题。像@Flukey 一样,我将不胜感激,因为我正在处理类似的任务。谢谢。
  • @Flukey 我已经在答案中发布了我的代码。

标签: symfony acl usergroups


【解决方案1】:

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

选民服务。

<?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");
}

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 我考虑过,但组内有不同的角色。如果用户更改组,我也应该更新所有角色。
    • @kirilloid 你总是可以创建 ROLE_GROUP_A_(group id)、ROLE_GROUP_B_(group id) 等等,我不太明白,“如果用户更改组,则更新所有角色”是什么意思(?) 当用户更改组时,它会自动拥有来自这个新组的角色,并且没有前一个组的任何角色
    • 更改“游戏规则”需要更新一堆 ACL 记录。前几天我决定,有一种新的正确口味,任何人都可以品尝任何蛋糕。如果将组作为角色,我应该编写一些新代码并进行数据库迁移以更新当前 ACL。在我的配置中,我应该只更新配置中的一行:ROLE_USER: others: [TASTE]
    猜你喜欢
    • 2012-08-12
    • 1970-01-01
    • 2016-10-11
    • 1970-01-01
    • 2016-03-03
    • 2011-12-14
    • 2012-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多