EDIT: I have updated the post to reflect my latest findings.
First of all I suggest you read the Symfony2 documentation about ACLs in Symfony2,
check it out here:
ACL
Advanced ACL
ClassScope
I was asking about
ACL stuff on the Symfony2 Google Group, and I have received a short description from a user named "rmsint". His name is Roel, credit where credit is due :-)
The class-scope contains access rules (ACEs) that tell what a user can do with the class. The object-scope contains access rules that tell what a user can do with a specific object. Object-scope rules and class-scope rules can both be applied to the object when it is created.It's better to attach class-scope and object-scope rules to the ACL when the object is created. This way access rules can be inherited from a parent ACL. An example is that a Comment object should grant access if the user is allowed to edit the parent Post object.
This is how I setup the ACL respectively the (Class) ACE, I did this via an app/console command. This is, as explained by Roel, by no means the "state of the art" how to do it. I just felt it was the easiest way for me, so I could easily create different ACL setups after dropping the database, since I am just doing this to figure out how all this stuff works.
ClassScopeCommand.php
namespace Liip\TestBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
class ClassScopeCommand extends ContainerAwareCommand
{
protected function configure()
{
parent::configure();
$this
->setName('acl:setup:class-scope')
->setDescription('Setup ACLs and ACEs for Class-Scope access demo');
}
/**
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
/**
* @var $aclProvider \Symfony\Component\Security\Acl\Dbal\MutableAclProvider
* @var $acl \Symfony\Component\Security\Acl\Domain\Acl
* @var $securityContext \Symfony\Component\Security\Core\SecurityContext
* @var $user \Liip\TestBundle\Entity\User
* @var $em \Doctrine\ORM\EntityManager
*/
$aclProvider = $this->getContainer()->get('security.acl.provider');
$oid = new ObjectIdentity('class', 'Liip\\TestBundle\\Entity\\Post');
$acl = $aclProvider->createAcl($oid);
$securityIdentity = new RoleSecurityIdentity("ROLE_POST_OWNER");
// grant owner access
$acl->insertClassAce($securityIdentity, MaskBuilder::MASK_OWNER);
$aclProvider->updateAcl($acl);
return 0;
}
}
And this is how you can check the permissions in the controller:
ClassScopeController.php
namespace Liip\TestBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Liip\TestBundle\Entity\Post;
class ClassScopeController extends Controller
{
/**
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
*/
public function indexAction()
{
/**
* @var $postRepository \Liip\TestBundle\Entity\PostRepository
* @var $em \Doctrine\ORM\EntityManager
* @var $securityContext \Symfony\Component\Security\Core\SecurityContext;
*/
$postRepository = $this->getDoctrine()->getRepository('Liip\TestBundle\Entity\Post');
$postExists = count($postRepository->findOneBy(array('id' => 1))) === 1;
// if there is no Post in the database yet, create one
if (!$postExists) {
$post = new Post();
$post->setPost('This post is protected by an ACL and should only be visible to users with the role "ROLE_POST_OWNER"');
$em = $this->getDoctrine()->getEntityManager();
$em->persist($post);
$em->flush();
unset($post);
}
$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:
";
print_r("");
print_r($post);
print_r("
");
} else {
throw new AccessDeniedException();
}
return $this->render('LiipTestBundle:Default:index.html.twig');
}
}
Class-Field-Scope
The ACL/ACE setup is a bit different, that's what I came up with:
ClassFieldScopeCommand.php
namespace Liip\TestBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
class ClassFieldScopeCommand extends ContainerAwareCommand
{
protected function configure()
{
parent::configure();
$this
->setName('acl:setup:class-field-scope')
->setDescription('Setup ACLs and ACEs for Class-Field-Scope access demo');
}
/**
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
/**
* @var $aclProvider \Symfony\Component\Security\Acl\Dbal\MutableAclProvider
* @var $acl \Symfony\Component\Security\Acl\Domain\Acl
* @var $securityContext \Symfony\Component\Security\Core\SecurityContext
* @var $user \Liip\TestBundle\Entity\User
* @var $em \Doctrine\ORM\EntityManager
*/
$aclProvider = $this->getContainer()->get('security.acl.provider');
// Object Identity for a whole class respectively entity
$oid = new ObjectIdentity('class', 'Liip\\TestBundle\\Entity\\Post');
// Object Identity for specific domainObject
// $oid = new ObjectIdentity::fromDomainObject($post);
$acl = $aclProvider->createAcl($oid);
// Role based securityIdentity
$securityIdentity = new RoleSecurityIdentity("ROLE_POST_OWNER");
// User based securityIdentity
// $securityContext =$this->getContainer()->get('security.context');
// $user = $securityContext->getToken()->getUser();
// $securityIdentity = UserSecurityIdentity::fromAccount($user);
// grant access
//$acl->insertClassAce($securityIdentity, MaskBuilder::MASK_OWNER);
$acl->insertClassFieldAce('post', $securityIdentity, MaskBuilder::MASK_EDIT);
$acl->insertClassFieldAce('id', $securityIdentity, MaskBuilder::MASK_EDIT);
$aclProvider->updateAcl($acl);
return 0;
}
}
And this is how you can check the permissions in the controller:
ClassFieldScopeController.php
namespace Liip\TestBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Acl\Voter\FieldVote;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Liip\TestBundle\Entity\Post;
class ClassFieldScopeController extends Controller
{
public function indexAction()
{
/**
* @var $postRepo \Liip\TestBundle\Entity\PostRepository
* @var $em \Doctrine\ORM\EntityManager
* @var $securityContext SecurityContext;
*/
$postRepo = $this->getDoctrine()->getRepository('Liip\TestBundle\Entity\Post');
$postExists = count ($postRepo->findOneById(1)) === 1;
if($postExists){
$post = $postRepo->findOneById(1);
$securityContext = $this->get('security.context');
$oid = new ObjectIdentity('class', 'Liip\\TestBundle\\Entity\\Post');
$object = new FieldVote($oid, 'id');
if (true === $securityContext->isGranted('EDIT', $object)){
echo "Access to 'id' field granted";
}else{
echo "Access denied";
}
$object = new FieldVote($oid, 'post');
if (true === $securityContext->isGranted('EDIT', $object))
{
echo "Access to 'post' field granted";
}else{
echo "Access denied";
}
}else{
$post = new Post();
$post->setPost('This post is protected by an ACL and should only be visible to users with the role "ROLE_POST_OWNER"');
$em = $this->getDoctrine()->getEntityManager();
$em->persist($post);
$em->flush();
}
return $this->render('LiipTestBundle:Default:index.html.twig');
}
}
Based on this it's also possible to do this on an "Object-Scope" or "Object-Field-Scope".
You simply need to adjust the objectIdentity accordingly with ObjectIdentity::fromDomainObject() when setting up the ACL.