Wednesday, September 12, 2012

Using ACLs and ACEs in Symfony 2

Symfony 2 has a couple of pages on how to use Access Control Lists (ACLs) which only describe how to create an Access Control Entry (ACE), but not how to maintain (update/remove) them.

The updating/removing of ACE's is a bit harder than creating a new one. Therefore I just wanted to share a simple example. This code comes from a UserController where a User object can be assigned to one or multiple companies which (s)he should be able to manage.

public function updateAction($id)
{
 $em = $this->getDoctrine()->getEntityManager();

 $entity = $em->getRepository('MyBundle:User')->find($id);

 if (!$entity) {
  throw $this->createNotFoundException('Unable to find User');
 }

 $prevCompanies = $entity->getCompanies()->toArray();

 $editForm   = $this->createForm(new UserType(), $entity);
 $deleteForm = $this->createDeleteForm($id);

 $request = $this->getRequest();

 $editForm->bindRequest($request);

 if ($editForm->isValid()) {

  $em->persist($entity);
  $em->flush();

  /**
   * ACL MAGIC
   */

  $aclProvider = $this->get('security.acl.provider');
  $securityIdentity = UserSecurityIdentity::fromAccount($entity);

  // Store the currently selected companies to figure out later
  // which entries have been removed
  $newCompanyIds = array();
  $companies = $entity->getCompanies();
  foreach($companies as $company) {
   $newCompanyIds[] = $company->getId();

   $objectIdentity = ObjectIdentity::fromDomainObject($company);
   try { 
    // If there is no ACL for the company yet, an 
    // AclNotFoundException is thrown
    $acl = $aclProvider->findAcl(
     $objectIdentity,
     array($securityIdentity)
    );

    // For some reason we need to use an index to update an 
    // ACE, so we need to use an index, so start looping
    foreach($acl->getObjectAces() as $index => $ace) {
     $aceSecurityId = $ace->getSecurityIdentity();
     if($aceSecurityId ->equals($securityIdentity)) {
      $acl->updateObjectAce(
       $index,
       MaskBuilder::MASK_OPERATOR
      );
     }
    }
    $aclProvider->updateAcl($acl);
   } catch (AclNotFoundException $e) {
    // No existing ACL found so create a new one
    $acl = $aclProvider->createAcl($objectIdentity);
    $acl->insertObjectAce(
     $securityIdentity,
     MaskBuilder::MASK_OPERATOR
    );
    $aclProvider->updateAcl($acl);
   }
  }

  foreach($prevCompanies as $company) {
   if(!in_array($company->getId(),$newCompanyIds)) {
    $objIdentity = ObjectIdentity::fromDomainObject($company);

    try {
     $acl = $aclProvider->findAcl(
      $objIdentity,
      array($securityIdentity)
     );

     // For some reason we need to use an index to delete
     // an ACE, so we need to use an index, so start
     // looping
     foreach($acl->getObjectAces() as $index => $ace) {
      $aceSecurityId = $ace->getSecurityIdentity();
      if($aceSecurityId->equals($securityIdentity)) {
       $acl->deleteObjectAce($index);
      }
     }
     $aclProvider->updateAcl($acl);
    } catch (AclNotFoundException $e) {
     // We should never get here! Added because some 
     // companies already existed before the ACL code was 
     // added
    }
   }
  }
  
  /**
   * End of ACL MAGIC
   */

  return $this->redirect(
   $this->generateUrl('admin_user_edit', array('id' => $id))
  );
 }

 return $this->render('MyBundle:User:edit.html.twig', array(
  'entity'      => $entity,
  'edit_form'   => $editForm->createView(),
  'delete_form' => $deleteForm->createView(),
 ));
}

2 comments:

  1. Thanks for this post! This is a good article for understanding ACL magic.

    ReplyDelete
  2. Yes, my doctor is expert in acls. Thanks for such a good article.

    ReplyDelete