5

我使用 SonataUser 和 FOSUser 来管理我的用户,并创建了一个自定义字段company来将每个用户附加到给定的公司。

现在我只需要让用户能够只管理附属于同一公司的用户:

user1 company1
user2 company1
user3 company2
user4 company2

示例:user1 应该能够仅列出/编辑 user1 和 user2

我应该使用 ACL 吗?

你能给我指出正确的方向或教程来为此目的定制 SonataUser 吗?

4

2 回答 2

10

是的,ACL 是要走的路。创建一个实现 VoterInterface 的 CompanyVoter 并在其 vote() 方法中检查用户是否在同一家公司。

食谱条目“如何实现您自己的选民将 IP 地址列入黑名单”给出了很好的介绍。

将您的访问决策管理器的策略更改为“一致”。这意味着如果只有一个投票者拒绝访问(例如 CompanyVoter),则不会授予最终用户访问权限。

# app/config/security.yml
security:
    access_decision_manager:
        strategy: unanimous

现在创建你的选民

// src/Acme/AcmeBundle/YourBundle/Security/Authorization/Voter/CompanyVoter.php
namespace Acme\YourBundle\Security\Authorization\Voter;

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

use Acme\YourUserBundleBundle\Entity\User;
use Symfony\Component\Security\Core\User\UserInterface;

class CompanyVoter implements VoterInterface 
{

    private $container;

    public function __construct($container) 
    {
        $this->container = $container;
    }

    public function supportsAttribute($attribute) 
    {
       return in_array($attribute, array(
          'EDIT',
          'ACTIVATE',
          // ...
       ));
    }

   public function supportsClass($class)
   {
        return in_array("FOS\UserBundle\Model\UserInterface", class_implements($class));
   }

   public function vote(TokenInterface $token, $object, array $attributes) 
   {
       if ( !($this->supportsClass(get_class($object))) ) {
           return VoterInterface::ACCESS_ABSTAIN;
       }

       foreach ($attributes as $attribute) {
           if ( !$this->supportsAttribute($attribute) ) {
               return VoterInterface::ACCESS_ABSTAIN;
           }
       }

       $user = $token->getUser();
       if ( !($user instanceof UserInterface) ) {
           return VoterInterface::ACCESS_DENIED;
       }

       // check if the user has the same company
       if ( $user->getCompany() == $object->getCompany() ) {
           return VoterInterface::ACCESS_GRANTED;
       }

       return VoterInterface::ACCESS_DENIED;
   }

}

最后将选民注册为服务

# src/Acme/AcmeBundle/Resources/config/services.yml
services:
    security.access.company_voter:
        class:      Acme\YourBundle\Security\Authorization\Voter\CompanyVoter
        public:     false
        tags:
           - { name: security.voter }

...现在在你的树枝模板中使用它

{% if is_granted('EDIT', user) %}<a href="#">Edit</a>{% endif %}
{% if is_granted('ACTIVATE', user) %}<a href="#">activate</a>{% endif %}

或在您的控制器中...

public function editAction(UserInterface $user)
{
    if ( $this->get('security.context')->isGranted('EDIT',$user) ) {
        throw new \Symfony\ComponentSecurity\Core\Exception\AccessDeniedException();
    }
}

或使用JMSSecurityExtraBundle ...

/**
 * @SecureParam(name="user", permissions="EDIT")
 */
public function editUser(UserInterface $user) 
{  
    // ...
}
于 2013-06-11T01:55:32.180 回答
1

由于我在这里不需要 ACL,(仅限选民)我使用了奏鸣曲的角色安全处理程序。

但是我在使用它时遇到了问题,因为它的默认实现isGranted()不会将当前对象传递给选民。

所以我不得不扩展它,在这个github 问题中查看我的独白以获取更多详细信息。


顺便说一句,我的PR被接受了关于这个问题

于 2013-08-05T15:16:04.003 回答