5

I used SonataUser with FOSUser to manage my users and created a custom field company to attach each one to a given company.

Now I'd simply need to give users the ability to manage only users attached to the same company:

user1 company1
user2 company1
user3 company2
user4 company2

Example: user1 should be able to list/edit only user1 & user2

Should I use ACLs ?

Can you point me to the right direction or tutorial to customize SonataUser for this purpose ?

Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307

2 Answers2

10

Yes ACL is the way to go. create a CompanyVoter implementing VoterInterface and check if the user is on the same company inside it's vote() method.

the cookbook entry "How to implement your own Voter to blacklist IP Addresses" gives a good introduction.

change your access-decision-manager's strategy to 'unanimous'. This means if only one voter denies access (e.g. the CompanyVoter), access is not granted to the end user.

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

Now create your Voter

// 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;
   }

}

Finally register the voter as as a service

# 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 }

... now use it in your twig template

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

or in your controller ...

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

or using JMSSecurityExtraBundle ...

/**
 * @SecureParam(name="user", permissions="EDIT")
 */
public function editUser(UserInterface $user) 
{  
    // ...
}
Nicolai Fröhlich
  • 51,330
  • 11
  • 126
  • 130
  • I tried this way, it works for edition (prevents edition for other companies) but the list still displays all the users. Seems like the voter is not called for displaying the line. – Pierre de LESPINAY Jun 11 '13 at 14:43
  • what do you mean by displaying the line? – Nicolai Fröhlich Jun 12 '13 at 08:26
  • As I need to show only certain users in the list grid, there should be no line displayed at all for them (instead of appearing readonly) Note I'm talking about SonataAdmin grid – Pierre de LESPINAY Jun 12 '13 at 09:09
  • then adjust your database query for the list view ? you shouldnt fetch all users if you don't need them right? What does this have to do with the acl? you can even fetch all and hide them with the twig function is_granted ... or just add an attribute 'LIST' or 'SHOW' to your security voter's attributes ... or extend it – Nicolai Fröhlich Jun 12 '13 at 09:12
  • `LIST` should be the one required, it's already there, but Sonata doesn't take it into account. That's why I have the impression it's not interrogated at row rendering time. – Pierre de LESPINAY Jun 12 '13 at 09:21
  • just use a custom database query in your entity repository filtering users by company. there is no LIST acl attribute as long as you don't provide it in a voter and check for it in your controller/template. – Nicolai Fröhlich Jun 12 '13 at 09:41
  • Ok finally I think I'm going to add this 'LIST' at `` rendering time. Thanks – Pierre de LESPINAY Jun 12 '13 at 10:13
1

As I didn't need ACLs here, (only voters) I used the role security handler of sonata.

But I had issues using it because its default implementation of isGranted() doesn't pass the current object to the voter.

So I had to extend it, check my monologue in this github issue for more detail.


By the way, my PR was accepted about this issue

Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307