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.