2012-04-27 68 views
3

一般我有以下商业模式:symfony2 acl群体

有用户和群组。每个用户只属于一个组,并且前面没有确定组的数量(以及大多数站点的用户数量)。 也有几个不同的繁忙度对象,可能属于用户。

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

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

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

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

+0

好吧,我已经解决了它的W/O使用ACL ,但有可能与ACL整合:我注册了我自己的选民服务。 – kirilloid 2012-04-28 06:30:41

+0

@krilloid - 我和你有同样的问题。你能分享你的选民服务代码吗?这将非常感激。谢谢 – Flukey 2012-05-22 07:42:14

+0

kirilloid如果你找到了合适的设计,最好在这里发布它,回答你自己的问题。像@Flukey一样,当我接近类似的任务时,我将不胜感激。谢谢。 – mokagio 2012-08-08 07:54:55

回答

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

创建一个组时,创建角色ROLE_GROUP_(组ID),促进组这个角色,并授予权限与rolesecurityidentity

+0

我已经想过了,但组内有不同的角色。此外,如果用户更改组,我应该更新所有角色。 – kirilloid 2012-09-21 09:04:49

+0

@kirilloid 你总是可以创建ROLE_GROUP_A_(组ID),ROLE_GROUP_B_(组ID),等等,我真的不明白,当用户改变时“更新所有角色,如果用户改变组”(?)是什么意思它有自动从这个新组中的角色,并没有任何前一个角色 – 2012-10-01 07:15:13

+0

更改“游戏规则”需要更新一堆ACL记录。有一天我决定,有一个新的权利口味,任何人都可以品尝任何蛋糕。在组作为角色的情况下,我应该编写一些新代码并执行数据库迁移,更新当前ACL。 Inb大小写配置我应该只更新配置中的一行: 'ROLE_USER:others:[TASTE]' – kirilloid 2012-10-01 07:33:59