Sonata用户 - 自定义字段的安全性

时间:2013-06-10 09:32:15

标签: symfony fosuserbundle sonata-admin

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

现在我只需要让用户只管理连接到同一家公司的用户:

user1 company1
user2 company1
user3 company2
user4 company2

示例:user1应该只能列出/编辑user1&用户2

我应该使用ACL吗?

您能指出正确的方向或教程来为此目的自定义SonataUser吗?

2 个答案:

答案 0 :(得分:10)

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

食谱条目“How to implement your own Voter to blacklist IP Addresses”给出了很好的介绍。

将您的访问决策经理的策略更改为“一致”。这意味着如果只有一个选民拒绝访问(例如,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) 
{  
    // ...
}

答案 1 :(得分:1)

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

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

所以我不得不扩展它,在github issue查看我的独白,了解更多细节。


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