How to use class-scope aces in Symfony2?
Asked Answered
B

4

11

I've got a problem with class-scope aces. I've created an ace for a class like this :

$userIdentity = UserSecurityIdentity::fromAccount($user);
$classIdentity = new ObjectIdentity('some_identifier', 'Class\FQCN');
$acl = $aclProvider->createAcl($classIdentity);
$acl->insertClassAce($userIdentity, MaskBuilder::MASK_CREATE);
$aclProvider->updateAcl($acl);

Now, I'm trying to check the user's permissions. I've found this way of doing things, which is not documented, but gives the expected results on a class basis :

$securityContext->isGranted('CREATE', $classIdentity);  // returns true
$securityContext->isGranted('VIEW', $classIdentity);    // returns true
$securityContext->isGranted('DELETE', $classIdentity);  // returns false

This method is well adapated to the "CREATE" permission check, where there's no available object instance to pass to the method. However, it should be possible to check if another permission is granted on a particular instance basis :

$entity = new Class\FQCN();
$em->persist($entity);
$em->flush();
$securityContext->isGranted('VIEW', $entity);  // returns false

This is where the test fails. I expected that an user who has a given permission mask on a class would have the same permissions on every instance of that class, as stated in the documentation ("The PermissionGrantingStrategy first checks all your object-scope ACEs if none is applicable, the class-scope ACEs will be checked"), but it seems not to be the case here.

Borosilicate answered 7/11, 2011 at 14:10 Comment(1)
this link clarifies some details about this matter:groups.google.com/forum/#!topic/symfony-devs/UR8PmwCAr40Strath
T
6

you are doing it right. and according to the bottom of this page, it should work, but it does not.

the easiest way to make it work is creating an AclVoter class:

namespace Core\Security\Acl\Voter;

use JMS\SecurityExtraBundle\Security\Acl\Voter\AclVoter as BaseAclVoter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Doctrine\Common\Util\ClassUtils;

class AclVoter extends BaseAclVoter
{
    public function vote( TokenInterface $token , $object , array $attributes )
    {   
    //vote for object first
    $objectVote =   parent::vote( $token , $object , $attributes ); 

    if( self::ACCESS_GRANTED === $objectVote )
    {
        return self::ACCESS_GRANTED;
    }
    else
    {
        //then for object's class
        $oid    =   new ObjectIdentity( 'class' , ClassUtils::getRealClass( get_class( $object ) ) );
        $classVote  =   parent::vote( $token , $oid , $attributes );                

        if( self::ACCESS_ABSTAIN === $objectVote )
        {       
        if( self::ACCESS_ABSTAIN === $classVote )
        {          
            return self::ACCESS_ABSTAIN;
        }
        else
        {
            return $classVote;
        }
        }
        else if( self::ACCESS_DENIED === $objectVote )
        {
        if( self::ACCESS_ABSTAIN === $classVote )
        {
            return self::ACCESS_DENIED;
        }
        else
        {
            return $classVote;
        }
        }       
    }

    return self::ACCESS_ABSTAIN;
    }
}

then in security.yml set this:

jms_security_extra:
    voters:
        disable_acl: true  

and finally set up the voter as a service:

core.security.acl.voter.basic_permissions:
    class: Core\Security\Acl\Voter\AclVoter
    public: false
    arguments: 
      - '@security.acl.provider'
      - '@security.acl.object_identity_retrieval_strategy'
      - '@security.acl.security_identity_retrieval_strategy'
      - '@security.acl.permission.map' 
      - '@?logger'   
    tags:
        - { name: security.voter , priority: 255 }           
        - { name: monolog.logger , channel: security }    
Tophet answered 23/8, 2012 at 18:52 Comment(2)
+1 Thank you for this. I was on a similar track and couldn't quite get my 'class' based ACL's to work. With the OP's question and your answer I was able to get my ACL's working perfectly.Reims
I find that when I try to inject the service @security.acl.object_identity_retrieval_strategy into my custom voter I get an exception saying the service is not available. Is there a point at which this service is not registered in the container?Cao
N
5

You need to ensure each object has its own ACL (use $aclProvider->createAcl($entity)) for class-scope permissions to work correctly.

See this discussion: https://groups.google.com/forum/?fromgroups=#!topic/symfony2/pGIs0UuYKX4

Neruda answered 1/11, 2012 at 16:57 Comment(1)
i have to create object scope and a class scope acls to make the class scope work ? can you make a litle example to ilustrate your idea ?Mcnally
A
2

If you don't have an existing entity, you can check against the objectIdentity you created. Be careful to use "double-backslashes", because of the escaping of the backslash.

$post = $postRepository->findOneBy(array('id' => 1));

$securityContext = $this->get('security.context');
$objectIdentity = new ObjectIdentity('class', 'Liip\\TestBundle\\Entity\\Post');

// check for edit access
if (true === $securityContext->isGranted('EDIT', $objectIdentity)) {
    echo "Edit Access granted to: <br/><br/> ";
    print_r("<pre>");
    print_r($post);
    print_r("</pre>");
} else {
    throw new AccessDeniedException();
}

That should work!

If you would check for "object-scope" you could just use $post instead of $objectIdentity in the isGranted function call.

Amontillado answered 19/2, 2012 at 13:14 Comment(0)
I
0

I have tried to find the best solution for this problem and I think the best answer is the one of rryter / edited by Bart. I just want to extend the solution.

Let's say you want to give access to a specific object type for a specific user, but not for a concrete object instance (for example id=1).

Then you can do the following:

    $aclProvider = $this->get('security.acl.provider');
    $objectIdentity = new ObjectIdentity('class',    'someNamspace\\SeperatedByDoubleSlashes');
    $acl = $aclProvider->createAcl($objectIdentity);

    // retrieving the security identity of the currently logged-in user
    $securityContext = $this->get('security.context');
    $user = $securityContext->getToken()->getUser();
    $securityIdentity = UserSecurityIdentity::fromAccount($user);

    // grant owner access
    $acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
    $aclProvider->updateAcl($acl);

    $securityContext = $this->get('security.context');
    // check for edit access
    if (false === $securityContext->isGranted('EDIT', $objectIdentity)) {
        throw new AccessDeniedException();
    }

The difference to the example given by the symfony cookbook is that you are using the class scope and not the object scope. There's only 1 line that makes the difference:

$objectIdentity = new ObjectIdentity('class',    'someNamspace\\SeperatedByDoubleSlashes');

instead of:

$objectIdentity = ObjectIdentity::fromDomainObject($object);

You can still add specific permissions for specific object instances if you have at least one class scope permission in your classes acl.

Insipience answered 27/1, 2014 at 11:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.