Skip to content

Commit

Permalink
Fixes #12 Inherited authorizations are left behind when a resource is…
Browse files Browse the repository at this point in the history
… deleted
  • Loading branch information
mnapoli committed Jun 19, 2014
1 parent 5c89494 commit 0b5044a
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 19 deletions.
41 changes: 23 additions & 18 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/ACL.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,21 @@ public function processNewResource(EntityResource $resource)
$repository->insertBulk($cascadedAuthorizations);
}

/**
* Process a resource that has been deleted.
*
* Called by the EntityResourcesListener.
*
* @param EntityResource $resource
*/
public function processDeletedResource(EntityResource $resource)
{
/** @var AuthorizationRepository $repository */
$repository = $this->entityManager->getRepository('MyCLabs\ACL\Model\Authorization');

$repository->removeAuthorizationsForResource($resource);
}

/**
* Clears and rebuilds all the authorizations from the roles.
*/
Expand Down
10 changes: 9 additions & 1 deletion src/Doctrine/EntityResourcesListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,23 @@ public function getSubscribedEvents()

public function onFlush(OnFlushEventArgs $args)
{
$acl = $this->getACL();
$uow = $args->getEntityManager()->getUnitOfWork();

// Remember new resources
// Remember new resources for after flush (we need them to have an ID)
$this->newResources = [];
foreach ($uow->getScheduledEntityInsertions() as $entity) {
if ($entity instanceof EntityResource) {
$this->newResources[] = $entity;
}
}

// Process deleted resources
foreach ($uow->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof EntityResource) {
$acl->processDeletedResource($entity);
}
}
}

public function postFlush()
Expand Down
34 changes: 34 additions & 0 deletions src/Repository/AuthorizationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,38 @@ public function findCascadableAuthorizationsForResource(ResourceInterface $resou

return $qb->getQuery()->getResult();
}

/**
* Remove all the authorizations that apply to the given resource.
*
* @param ResourceInterface $resource
* @throws \RuntimeException If the resource is an entity, it must be persisted.
*/
public function removeAuthorizationsForResource(ResourceInterface $resource)
{
$qb = $this->_em->createQueryBuilder();

$qb->delete($this->getEntityName(), 'a');

if ($resource instanceof EntityResource) {
if ($resource->getId() === null) {
throw new \RuntimeException(sprintf(
'The entity resource %s must be persisted (id not null) to be able to remove the authorizations',
ClassUtils::getClass($resource)
));
}

$qb->andWhere('a.entityClass = :entityClass');
$qb->andWhere('a.entityId = :entityId');
$qb->setParameter('entityClass', ClassUtils::getClass($resource));
$qb->setParameter('entityId', $resource->getId());
}
if ($resource instanceof ClassResource) {
$qb->andWhere('a.entityClass = :entityClass');
$qb->andWhere('a.entityId IS NULL');
$qb->setParameter('entityClass', $resource->getClass());
}

$qb->getQuery()->execute();
}
}
88 changes: 88 additions & 0 deletions tests/Integration/ResourceDeletionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Tests\MyCLabs\ACL\Integration;

use MyCLabs\ACL\Model\Actions;
use Tests\MyCLabs\ACL\Integration\Model\Article;
use Tests\MyCLabs\ACL\Integration\Model\ArticleEditorRole;
use Tests\MyCLabs\ACL\Integration\Model\Category;
use Tests\MyCLabs\ACL\Integration\Model\CategoryManagerRole;
use Tests\MyCLabs\ACL\Integration\Model\User;

/**
* Tests that authorizations are deleted when a resource is deleted.
*
* @coversNothing
*/
class ResourceDeletionTest extends AbstractIntegrationTest
{
public function testSimple()
{
$resource = new Article();
$this->em->persist($resource);
$user = new User();
$this->em->persist($user);
$this->em->flush();

// The role will create 1 authorization
$this->acl->grant($user, new ArticleEditorRole($user, $resource));
$this->assertTrue($this->acl->isAllowed($user, Actions::VIEW, $resource));

// We need to reload the resource because the role hasn't been added automatically to
// the role collection in Article
$this->em->refresh($resource);

// Now we delete the resource
$this->em->remove($resource);
$this->em->flush();

// We check that the authorization is deleted
$query = $this->em->createQuery('SELECT COUNT(a.id) FROM MyCLabs\ACL\Model\Authorization a');
$this->assertEquals(0, $query->getSingleScalarResult(), "The authorization wasn't deleted");

// We check that the role is deleted too
$query = $this->em->createQuery('SELECT COUNT(r.id) FROM Tests\MyCLabs\ACL\Integration\Model\ArticleEditorRole r');
$this->assertEquals(0, $query->getSingleScalarResult(), "The role wasn't deleted");
}

/**
* Here we delete a resource which had no direct roles associated. However it had authorizations
* because of a parent resource.
*
* @link https://github.com/myclabs/ACL/issues/12
*/
public function testDeletionWithCascade()
{
$category = new Category();
$this->em->persist($category);
$subCategory = new Category($category);
$this->em->persist($subCategory);
$user = new User();
$this->em->persist($user);
$this->em->flush();

// We apply a role on the parent resource, authorizations will cascade to the sub-resource
$this->acl->grant($user, new CategoryManagerRole($user, $category));
$this->assertTrue($this->acl->isAllowed($user, Actions::VIEW, $category));
$this->assertTrue($this->acl->isAllowed($user, Actions::VIEW, $subCategory));

// We need to reload the resource because the role hasn't been added automatically to
// the role collection in Category
$this->em->refresh($category);

// Now we delete the sub-resource
$this->em->remove($subCategory);
$this->em->flush();

// We check that the authorization is deleted (there should be 1 left: the one for the parent category)
$query = $this->em->createQuery('SELECT COUNT(a.id) FROM MyCLabs\ACL\Model\Authorization a');
$this->assertEquals(1, $query->getSingleScalarResult(), "The child authorization wasn't deleted");

// We check that the role is not deleted
$query = $this->em->createQuery('SELECT COUNT(r.id) FROM Tests\MyCLabs\ACL\Integration\Model\CategoryManagerRole r');
$this->assertEquals(1, $query->getSingleScalarResult());

// We check that isAllowed still works with the parent resource (which wasn't deleted)
$this->assertTrue($this->acl->isAllowed($user, Actions::VIEW, $category));
}
}
37 changes: 37 additions & 0 deletions tests/Unit/Repository/AuthorizationRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,41 @@ public function testIsAllowedOnEntityClass()
$this->assertTrue($repository->isAllowedOnEntityClass($user, Actions::VIEW, $class));
$this->assertFalse($repository->isAllowedOnEntityClass($user, Actions::EDIT, $class));
}

/**
* @depends testInsertBulk
*/
public function testRemoveForResource()
{
$user = new User();
$this->em->persist($user);

$resource1 = new File();
$this->em->persist($resource1);
$role1 = new FileOwnerRole($user, $resource1);
$this->em->persist($role1);
$this->em->flush();

$resource2 = new File();
$this->em->persist($resource2);
$role2 = new FileOwnerRole($user, $resource2);
$this->em->persist($role2);
$this->em->flush();

$authorizations = [
Authorization::create($role1, new Actions([ Actions::VIEW ]), $resource1),
Authorization::create($role2, new Actions([ Actions::VIEW ]), $resource2),
];

/** @var AuthorizationRepository $repository */
$repository = $this->em->getRepository('MyCLabs\ACL\Model\Authorization');
$repository->insertBulk($authorizations);

// We remove the authorizations for the resource 1
$repository->removeAuthorizationsForResource($resource1);
// We check that they were removed
$this->assertFalse($repository->isAllowedOnEntity($user, Actions::VIEW, $resource1));
// and that authorizations for the resource 2 weren't removed
$this->assertTrue($repository->isAllowedOnEntity($user, Actions::VIEW, $resource2));
}
}

0 comments on commit 0b5044a

Please sign in to comment.