diff --git a/DependencyInjection/Configuration/Configuration.php b/DependencyInjection/Configuration/Configuration.php new file mode 100644 index 0000000..479343f --- /dev/null +++ b/DependencyInjection/Configuration/Configuration.php @@ -0,0 +1,34 @@ +root('curiosity26_acl_helper') + ->children() + ->scalarNode('allowClassAclsDefault') + ->cannotBeEmpty() + ->defaultValue(true) + ->validate() + ->ifNotInArray([true, false]) + ->thenInvalid('allowClassAclsDefault must be a boolean value') + ->end() + ->end() + ->end(); + + return $tree; + } + +} diff --git a/DependencyInjection/Curiosity26AclHelperExtension.php b/DependencyInjection/Curiosity26AclHelperExtension.php index e927059..4bcfc8b 100644 --- a/DependencyInjection/Curiosity26AclHelperExtension.php +++ b/DependencyInjection/Curiosity26AclHelperExtension.php @@ -8,7 +8,9 @@ namespace Curiosity26\AclHelperBundle\DependencyInjection; +use Curiosity26\AclHelperBundle\DependencyInjection\Configuration\Configuration; use Curiosity26\AclHelperBundle\Doctrine\DQL\CastAsInt; +use Curiosity26\AclHelperBundle\Helper\AclHelper; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; @@ -27,6 +29,13 @@ public function load(array $configs, ContainerBuilder $container) { $loader = new YamlFileLoader($container, new FileLocator([__DIR__.'/../Resources/config/'])); $loader->load('services.yaml'); + + $config = $this->processConfiguration(new Configuration(), $configs); + + if ($container->hasDefinition(AclHelper::class)) { + $def = $container->getDefinition(AclHelper::class); + $def->setArgument('$allowClassAclsDefault', $config['allowClassAclsDefault']); + } } public function prepend(ContainerBuilder $container) diff --git a/Helper/AclHelper.php b/Helper/AclHelper.php index 1b2a7c1..0219558 100644 --- a/Helper/AclHelper.php +++ b/Helper/AclHelper.php @@ -9,11 +9,15 @@ namespace Curiosity26\AclHelperBundle\Helper; use Curiosity26\AclHelperBundle\QueryBuilder\AclHelperQueryBuilder; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; use Symfony\Bridge\Doctrine\RegistryInterface; use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface; -class AclHelper +class AclHelper implements LoggerAwareInterface { + use LoggerAwareTrait; + /** * @var AclHelperQueryBuilder */ @@ -29,14 +33,21 @@ class AclHelper */ private $aclProvider; + /** + * @var bool + */ + private $allowClassAclsDefault = true; + public function __construct( RegistryInterface $registry, AclHelperQueryBuilder $queryBuilder, - MutableAclProviderInterface $provider + MutableAclProviderInterface $provider, + bool $allowClassAclsDefault = true ) { - $this->registry = $registry; - $this->queryBuilder = $queryBuilder; - $this->aclProvider = $provider; + $this->registry = $registry; + $this->queryBuilder = $queryBuilder; + $this->aclProvider = $provider; + $this->allowClassAclsDefault = $allowClassAclsDefault; } /** @@ -48,7 +59,13 @@ public function createAgent(string $className): AclHelperAgent { $manager = $this->registry->getManagerForClass($className); - return new AclHelperAgent($className, $manager, $this->queryBuilder); + return new AclHelperAgent( + $className, + $manager, + $this->queryBuilder, + $this->allowClassAclsDefault, + $this->logger + ); } /** diff --git a/Helper/AclHelperAgent.php b/Helper/AclHelperAgent.php index 2a73a97..8dc401c 100644 --- a/Helper/AclHelperAgent.php +++ b/Helper/AclHelperAgent.php @@ -13,7 +13,9 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; +use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Expr\OrderBy; +use Doctrine\ORM\QueryBuilder; use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -47,16 +49,23 @@ class AclHelperAgent */ private $classMetadata; + /** + * @var bool + */ + private $allowClassAclsDefault = true; + public function __construct( string $class, EntityManagerInterface $entityManager, AclHelperQueryBuilder $queryBuilder, + bool $allowClassAcls = true, ?LoggerInterface $logger = null ) { - $this->class = $class; - $this->entityManager = $entityManager; - $this->queryBuilder = $queryBuilder; - $this->classMetadata = $this->entityManager->getClassMetadata($this->class); + $this->class = $class; + $this->entityManager = $entityManager; + $this->queryBuilder = $queryBuilder; + $this->allowClassAclsDefault = $allowClassAcls; + $this->classMetadata = $this->entityManager->getClassMetadata($this->class); $this->setLogger($logger ?: new NullLogger()); } @@ -69,6 +78,8 @@ public function __construct( * @param int|null $limit * @param int|null $offset * @param string $strategy + * @param bool|null $allowClassAcls + * @param bool $criteriaAnd * * @return mixed */ @@ -79,30 +90,36 @@ public function findBy( $orderBy = null, ?int $limit = null, ?int $offset = null, - string $strategy = PermissionGrantingStrategy::ANY + string $strategy = PermissionGrantingStrategy::ANY, + ?bool $allowClassAcls = null, + $criteriaAnd = true ) { $builder = $this->entityManager->createQueryBuilder(); + $aliases = []; $builder->select('e') ->from($this->class, 'e') ; if (!empty($criteria)) { - $predicates = $builder->expr()->andX(); + $predicates = $criteriaAnd ? $builder->expr()->andX() : $builder->expr()->orX(); foreach ($criteria as $field => $criterion) { - if (is_array($criterion)) { - $predicates->add($builder->expr()->in("e.$field", ":$field")); - $builder->setParameter(":$field", $criterion); - } elseif (null !== $criterion) { - $predicates->add($builder->expr()->eq("e.$field", ":$field")); - $builder->setParameter(":$field", $criterion); - } else { - $predicates->add($builder->expr()->isNull("e.$field")); + $prefix = 'e'; + + $col = self::aliasField($field, $builder, $aliases, $prefix.'.'); + + if (($pos = stripos($col, '.')) !== false) { + $prefix = substr($col, 0, $pos); + $field = substr($col, $pos + 1); } + + self::compilePredicate($predicates, $builder, $prefix, $field, $criterion); } - $builder->where($predicates); + if ($predicates->count() > 0) { + $builder->where($predicates); + } } if (null !== $orderBy) { @@ -110,9 +127,11 @@ public function findBy( $builder->orderBy($orderBy); } elseif (is_array($orderBy)) { $sort = key($orderBy); - $builder->orderBy("e.$sort", $orderBy[$sort]); + $col = self::aliasField($sort, $builder, $aliases, 'e.'); + $builder->orderBy($col, $orderBy[$sort]); } else { - $builder->orderBy("e.$orderBy"); + $col = self::aliasField($orderBy, $builder, $aliases, 'e.'); + $builder->orderBy($col); } } @@ -140,7 +159,8 @@ public function findBy( $this->classMetadata, $identities, $mask, - $strategy + $strategy, + null !== $allowClassAcls ? $allowClassAcls : $this->allowClassAclsDefault ); $query = $builder->getQuery(); @@ -159,6 +179,7 @@ public function findBy( * @param $identity * @param array $criteria * @param string $strategy + * @param bool|null $allowClassAcls * * @return mixed|null */ @@ -166,9 +187,10 @@ public function findOneBy( int $mask, $identity, array $criteria = [], - string $strategy = PermissionGrantingStrategy::ANY + string $strategy = PermissionGrantingStrategy::ANY, + ?bool $allowClassAcls = null ) { - $result = $this->findBy($mask, $identity, $criteria, null, 1, 0, $strategy); + $result = $this->findBy($mask, $identity, $criteria, null, 1, 0, $strategy, $allowClassAcls); if (is_array($result)) { return false !== ($entity = reset($result)) ? $entity : null; @@ -181,11 +203,154 @@ public function findOneBy( * @param int $mask * @param $identity * @param string $strategy + * @param bool|null $allowClassAcls * * @return mixed */ - public function findAll(int $mask, $identity, string $strategy = PermissionGrantingStrategy::ANY) + public function findAll( + int $mask, + $identity, + string $strategy = PermissionGrantingStrategy::ANY, + ?bool $allowClassAcls = null + ) { + return $this->findBy($mask, $identity, [], null, null, null, $strategy, $allowClassAcls); + } + + /** + * @param Expr\Composite $predicate + * @param QueryBuilder $qb + * @param $prefix + * @param $field + * @param $value + */ + public static function compilePredicate(Expr\Composite $predicate, QueryBuilder $qb, $prefix, $field, $value) + { + if (is_array($value)) { + $predicate->add(self::parseComplexPredicate($qb, $prefix, $field, $value)); + } elseif ($value instanceof Expr) { + $predicate->add($value); + } else { + $predicate->add("$prefix.$field = :$field"); + $qb->setParameter(":$field", $value); + } + } + + /** + * @param QueryBuilder $qb + * @param $prefix + * @param $field + * @param $value + * + * @return Expr\Comparison|Expr\Func|Expr\Orx + */ + protected static function parseComplexPredicate(QueryBuilder $qb, $prefix, $field, array $value) + { + $keywords = ['=', '!=', '<', '>', '<=', '>=', 'IN', 'LIKE', 'BETWEEN']; + $keys = array_keys($value); + $op = $keys[0]; + + if (count($keys) === 1 && in_array((string)$keys[0], $keywords)) { + $value = $value[$op]; + + if ('BETWEEN' == $op) { + if (!is_array($value)) { + throw new \InvalidArgumentException( + "The value used for a BETWEEN operation must + be an array consisting of a low and high value." + ); + } + $qb->setParameter(":${field}_x", $value[0]); + $qb->setParameter(":${field}_y", $value[1]); + } elseif ('LIKE' == $op) { + $qb->setParameter($field, strtolower($value)); + } else { + $qb->setParameter($field, $value); + } + + return self::convertOpToExpr($qb->expr(), $op, $prefix, $field); + } + + if (!empty($value)) { + if (in_array(key($value), $keywords)) { + $orX = $qb->expr()->orX(); + + foreach ($value as $op => $v) { + self::compilePredicate($orX, $qb, $prefix, $field, $v); + } + + return $orX; + } else { + $qb->setParameter(":$field", $value); + + return $qb->expr()->in("$prefix.$field", ":$field"); + } + } + } + + /** + * @param Expr $expr + * @param $op + * @param $prefix + * @param $field + * + * @return Expr\Comparison|Expr\Func + */ + protected static function convertOpToExpr(Expr $expr, $op, $prefix, $field) { - return $this->findBy($mask, $identity, [], null, null, null, $strategy); + switch ($op) { + case '=': + return $expr->eq("$prefix.$field", ":$field"); + case '!=': + return $expr->neq("$prefix.$field", ":$field"); + case '<': + return $expr->lt("$prefix.$field", ":$field"); + case '>': + return $expr->gt("$prefix.$field", ":$field"); + case '<=': + return $expr->lte("$prefix.$field", ":$field"); + case '>=': + return $expr->gte("$prefix.$field", ":$field"); + case 'IN': + return $expr->in("$prefix.$field", ":$field"); + case 'LIKE': + return $expr->like("LOWER($prefix.$field)", ":$field"); + case 'BETWEEN': + return $expr->between("$prefix.$field", ":${field}_x", ":${field}_y"); + default: + return $expr->eq("$prefix.$field", ":$field"); + } + } + + /** + * @param $field + * @param QueryBuilder $builder + * @param array $aliases + * @param string $initialPrefix + * + * @return string + */ + protected static function aliasField($field, QueryBuilder $builder, array &$aliases, $initialPrefix = 'e.') + { + $part = $field; + $prefix = $initialPrefix; + + while (($start = strpos($part, '.')) !== false) { + $field = substr($field, 0, $start); + if (strlen($field) === 0) { + continue; + } + + $col = $prefix.$field; + if (!array_key_exists($col, $aliases)) { + $aliases[$col] = 'j'.count($aliases); + } + + $alias = $aliases[$col]; + $builder->join($col, $alias); + $prefix = $alias.'.'; + $part = str_replace($field.'.', '', $part); + } + + return "$prefix$part"; } } diff --git a/QueryBuilder/AclHelperQueryBuilder.php b/QueryBuilder/AclHelperQueryBuilder.php index 04fe1b6..fbc567d 100644 --- a/QueryBuilder/AclHelperQueryBuilder.php +++ b/QueryBuilder/AclHelperQueryBuilder.php @@ -14,6 +14,7 @@ use Curiosity26\AclHelperBundle\Entity\SecurityIdentity; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -49,6 +50,7 @@ public function __construct(RegistryInterface $registry, ?RoleHierarchyInterface * @param array $identities * @param int $mask * @param string $strategy + * @param bool $allowClassAcls * * @throws \Doctrine\ORM\Mapping\MappingException */ @@ -57,18 +59,29 @@ public function createAclQueryBuilder( ClassMetadata $classMetadata, array $identities, int $mask, - string $strategy = PermissionGrantingStrategy::ANY + string $strategy = PermissionGrantingStrategy::ANY, + $allowClassAcls = true ) { $aliases = $queryBuilder->getRootAliases(); - $idField = $classMetadata->getSingleIdentifierFieldName(); + $where = $queryBuilder->expr()->orX( + $queryBuilder + ->expr() + ->in("{$aliases[0]}.{$idField}", $this->buildAclQuery($strategy, 0)->getDQL()) + ) + ; - $queryBuilder - ->andWhere( + if ($allowClassAcls) { + $where->add( $queryBuilder ->expr() - ->in("{$aliases[0]}.{$idField}", $this->buildAclQuery($strategy)->getDQL()) - ) + ->lt(0, '('.$this->buildHasClassAclQuery($strategy, 1)->getDQL().')') + ); + } + + + $queryBuilder + ->andWhere($where) ->setParameter('class_type', $classMetadata->getName()) ->setParameter('identities', $this->buildIdentities($identities)) ->setParameter('mask', $mask) @@ -77,48 +90,106 @@ public function createAclQueryBuilder( /** * @param string $strategy + * @param string $aliasSuffix * * @return QueryBuilder - * @throws \Doctrine\DBAL\DBALException */ - public function buildAclQuery($strategy = PermissionGrantingStrategy::ANY) + public function buildAclQuery($strategy = PermissionGrantingStrategy::ANY, $aliasSuffix = '') { - $manager = $this->registry->getEntityManagerForClass(ObjectIdentity::class); + /** @var EntityManagerInterface $manager */ + $manager = $this->registry->getManagerForClass(ObjectIdentity::class); $q = $manager->createQueryBuilder(); $expr = $q->expr() - ->andX($q->expr()->eq('acl_c.classType', ':class_type')) - ->add($q->expr()->in('acl_s.identifier', ':identities')) - ->add($q->expr()->neq('acl_o.objectIdentifier', "'class'")) - ; + ->andX($q->expr()->eq("acl_c$aliasSuffix.classType", ':class_type')) + ->add($q->expr()->in("acl_s$aliasSuffix.identifier", ':identities')) + ->add($q->expr()->neq("acl_o$aliasSuffix.objectIdentifier", "'class'")) + ; switch ($strategy) { case PermissionGrantingStrategy::ALL: - $expr->add($q->expr()->eq('BIT_AND(acl_e.mask, :mask)', ':mask')); + $expr->add($q->expr()->eq("BIT_AND(acl_e$aliasSuffix.mask, :mask)", ':mask')); break; case PermissionGrantingStrategy::EQUAL: - $expr->add($q->expr()->eq('acl_e.mask', ':mask')); + $expr->add($q->expr()->eq("acl_e$aliasSuffix.mask", ':mask')); break; default: - $expr->add($q->expr()->neq('BIT_AND(acl_e.mask, :mask)', 0)); + $expr->add($q->expr()->neq("BIT_AND(acl_e$aliasSuffix.mask, :mask)", 0)); } $q - ->select('INT(acl_o.objectIdentifier)') + ->select("INT(acl_o$aliasSuffix.objectIdentifier)") ->distinct() - ->from(ObjectIdentity::class, 'acl_o') - ->innerJoin(AclClass::class, 'acl_c', Join::WITH, 'acl_c.id = acl_o.class') + ->from(ObjectIdentity::class, "acl_o$aliasSuffix") + ->innerJoin( + AclClass::class, + "acl_c$aliasSuffix", + Join::WITH, + "acl_c$aliasSuffix.id = acl_o$aliasSuffix.class" + ) + ->leftJoin( + Entry::class, + "acl_e$aliasSuffix", + Join::WITH, + "acl_e$aliasSuffix.objectIdentity = acl_o$aliasSuffix.id OR acl_e$aliasSuffix.objectIdentity IS NULL" + ) + ->leftJoin( + SecurityIdentity::class, + "acl_s$aliasSuffix", + Join::WITH, + "acl_e$aliasSuffix.securityIdentity = acl_s$aliasSuffix.id" + ) + ->where( + $expr + ) + ; + + return $q; + } + + private function buildHasClassAclQuery($strategy = PermissionGrantingStrategy::ALL, $aliasSuffix = '') + { + /** @var EntityManagerInterface $manager */ + $manager = $this->registry->getManagerForClass(ObjectIdentity::class); + $q = $manager->createQueryBuilder(); + + $expr = $q->expr() + ->andX($q->expr()->eq("acl_c$aliasSuffix.classType", ':class_type')) + ->add($q->expr()->in("acl_s$aliasSuffix.identifier", ':identities')) + ->add($q->expr()->eq("acl_o$aliasSuffix.objectIdentifier", "'class'")) + ; + + switch ($strategy) { + case PermissionGrantingStrategy::ALL: + $expr->add($q->expr()->eq("BIT_AND(acl_e$aliasSuffix.mask, :mask)", ':mask')); + break; + case PermissionGrantingStrategy::EQUAL: + $expr->add($q->expr()->eq("acl_e$aliasSuffix.mask", ':mask')); + break; + default: + $expr->add($q->expr()->neq("BIT_AND(acl_e$aliasSuffix.mask, :mask)", 0)); + } + + $q + ->select("Count(acl_o$aliasSuffix.objectIdentifier)") + ->from(ObjectIdentity::class, "acl_o$aliasSuffix") + ->innerJoin( + AclClass::class, + "acl_c$aliasSuffix", + Join::WITH, + "acl_c$aliasSuffix.id = acl_o$aliasSuffix.class" + ) ->leftJoin( Entry::class, - 'acl_e', + "acl_e$aliasSuffix", Join::WITH, - 'acl_e.objectIdentity = acl_o.id OR acl_e.objectIdentity IS NULL' + "acl_e$aliasSuffix.objectIdentity = acl_o$aliasSuffix.id OR acl_e$aliasSuffix.objectIdentity IS NULL" ) ->leftJoin( SecurityIdentity::class, - 'acl_s', + "acl_s$aliasSuffix", Join::WITH, - 'acl_e.securityIdentity = acl_s.id' + "acl_e$aliasSuffix.securityIdentity = acl_s$aliasSuffix.id" ) ->where( $expr diff --git a/Resources/config/services.yaml b/Resources/config/services.yaml index df73b36..f82fd29 100644 --- a/Resources/config/services.yaml +++ b/Resources/config/services.yaml @@ -7,6 +7,8 @@ services: $registry: '@Symfony\Bridge\Doctrine\RegistryInterface' $queryBuilder: '@Curiosity26\AclHelperBundle\QueryBuilder\AclHelperQueryBuilder' $provider: '@security.acl.provider' + calls: + - ['setLogger', ['@?Psr\Log\LoggerInterface']] public: true security.acl.dbal.schema_listener: class: 'Curiosity26\AclHelperBundle\EventListener\AclSchemaListener' diff --git a/Tests/AclHelperBundleTest.php b/Tests/AclHelperBundleTest.php index f454eea..3e11d6d 100644 --- a/Tests/AclHelperBundleTest.php +++ b/Tests/AclHelperBundleTest.php @@ -15,22 +15,17 @@ use Curiosity26\AclHelperBundle\Helper\AclHelper; use Curiosity26\AclHelperBundle\QueryBuilder\AclHelperQueryBuilder; use Curiosity26\AclHelperBundle\Tests\Entity\TestObject; +use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface; use Symfony\Component\Security\Acl\Permission\BasicPermissionMap; use Symfony\Component\Security\Acl\Permission\MaskBuilder; -use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Acl\Domain\ObjectIdentity as AclObjectIdentity; class AclHelperBundleTest extends DatabaseTestCase { - /** - * @var AuthorizationCheckerInterface - */ - private $authDecider; - /** * @var MutableAclProviderInterface */ @@ -49,7 +44,6 @@ class AclHelperBundleTest extends DatabaseTestCase protected function setUp()/* The :void return type declaration that should be here would cause a BC issue */ { parent::setUp(); - $this->authDecider = $this->get(AuthorizationCheckerInterface::class); $this->aclProvider = $this->get('security.acl.provider'); $this->aclHelper = $this->get(AclHelper::class); $this->queryBuilder = $this->get(AclHelperQueryBuilder::class); @@ -72,11 +66,6 @@ public function testView() $agent = $this->aclHelper->createAgent(TestObject::class); $aclManager = $this->aclHelper->createAclManager(); $permMap = new BasicPermissionMap(); - $testObject = new TestObject(); - $testObject->setName('Wicked Cool Object'); - - $manager->persist($testObject); - $manager->flush(); $objectIdentity = new AclObjectIdentity('class', TestObject::class); $aclManager->aclFor($objectIdentity); @@ -96,7 +85,15 @@ public function testView() $aclManager->save(); - $aclManager->aclFor($testObject); + $testObject = new TestObject(); + $testObject->setName('Wicked Cool Object'); + + $childObject = new TestObject(); + $childObject->setName('Child'); + $testObject->addChild($childObject); + + $manager->persist($testObject); + $manager->flush(); $testObject2 = new TestObject(); $testObject2->setName('Wicked Cool Object 2'); @@ -114,21 +111,34 @@ public function testView() $mask1 = $maskBuilder->get(); $owner1Objs = $agent->findAll($mask1, $owner1); - $this->assertCount(2, $owner1Objs); + $this->assertCount(3, $owner1Objs); $obj1 = $owner1Objs[0]; $this->assertNotNull($obj1); - $owner2Objs = $agent->findAll($mask1, new User('view', 'view_user1', ['ROLE_USER'])); - $this->assertCount(2, $owner2Objs); + $viewUser = new User('view', 'view_user1', ['ROLE_USER']); + $owner2Objs = $agent->findAll($mask1, $viewUser); + $this->assertCount(3, $owner2Objs); + $owner2Objs = $agent->findAll($mask1, $viewUser, PermissionGrantingStrategy::ANY, false); + $this->assertCount(1, $owner2Objs); + $owner2Objs = $agent->findBy($mask1, $viewUser, ['name' => 'Wicked Cool Object']); + $this->assertCount(1, $owner2Objs); $modObjs = $agent->findAll($mask1, $moderatorIdentity); + $this->assertCount(3, $modObjs); + $modObjs = $agent->findBy($mask1, $moderatorIdentity, ['name' => ['LIKE' => 'Wicked %']]); $this->assertCount(2, $modObjs); - $adminObjs = $agent->findAll($mask1, new RoleSecurityIdentity('ROLE_ADMIN')); - $this->assertCount(2, $adminObjs); + $roleAdmin = new RoleSecurityIdentity('ROLE_ADMIN'); + $adminObjs = $agent->findAll($mask1, $roleAdmin); + $this->assertCount(3, $adminObjs); + $adminObjs = $agent->findBy($mask1, $roleAdmin, ['children.name' => 'Child']); + $this->assertCount(1, $adminObjs); + $adminObjs = $agent->findBy($mask1, $roleAdmin, ['parent.name' => ['LIKE' => 'Wicked %']]); + $this->assertCount(1, $adminObjs); - $supAdminObjs = $agent->findAll($mask1, new RoleSecurityIdentity('ROLE_SUPER_ADMIN')); + $roleSuperAdmin = new RoleSecurityIdentity('ROLE_SUPER_ADMIN'); + $supAdminObjs = $agent->findAll($mask1, $roleSuperAdmin); $this->assertCount(0, $supAdminObjs); } } diff --git a/Tests/Entity/TestObject.php b/Tests/Entity/TestObject.php index ce5ec7c..2f163c8 100644 --- a/Tests/Entity/TestObject.php +++ b/Tests/Entity/TestObject.php @@ -2,6 +2,8 @@ namespace Curiosity26\AclHelperBundle\Tests\Entity; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** @@ -34,6 +36,24 @@ class TestObject */ private $name; + /** + * @var TestObject|null + * @ORM\ManyToOne(targetEntity="Curiosity26\AclHelperBundle\Tests\Entity\TestObject", inversedBy="parent") + */ + private $parent; + + /** + * @var Collection|TestObject[] + * @ORM\OneToMany(targetEntity="Curiosity26\AclHelperBundle\Tests\Entity\TestObject", mappedBy="parent", + * cascade={"all"}) + */ + private $children; + + public function __construct() + { + $this->children = new ArrayCollection(); + } + /** * @return int */ @@ -73,4 +93,87 @@ public function setName(string $name): TestObject return $this; } + + /** + * @return TestObject|null + */ + public function getParent(): ?TestObject + { + return $this->parent; + } + + /** + * @param TestObject|null $parent + * + * @return TestObject + */ + public function setParent(?TestObject $parent): TestObject + { + $oldParent = $this->parent; + $this->parent = $parent; + + if (null !== $oldParent && $oldParent !== $parent) { + $oldParent->removeChild($this); + } + + if (null !== $parent && $oldParent !== $parent) { + $parent->addChild($this); + } + + return $this; + } + + /** + * @return TestObject[]|Collection + */ + public function getChildren() + { + return $this->children; + } + + /** + * @param TestObject[]|Collection $children + * + * @return TestObject + */ + public function setChildren($children): TestObject + { + $this->children = new ArrayCollection(); + + foreach ($children as $child) { + $this->addChild($child); + } + + return $this; + } + + /** + * @param TestObject $child + * + * @return TestObject + */ + public function addChild(TestObject $child): TestObject + { + if (!$this->children->contains($child)) { + $this->children->add($child); + $child->setParent($this); + } + + return $this; + } + + /** + * @param TestObject $child + * + * @return TestObject + */ + public function removeChild(TestObject $child): TestObject + { + if ($this->children->contains($child)) { + $this->children->remove($child); + $child->setParent(null); + } + + return $this; + } } diff --git a/Tests/Resources/config/config.yml b/Tests/Resources/config/config.yml index 53e424c..dbb3d5e 100644 --- a/Tests/Resources/config/config.yml +++ b/Tests/Resources/config/config.yml @@ -38,4 +38,7 @@ doctrine: fidry_alice_data_fixtures: default_purge_mode: truncate db_drivers: - doctrine_orm: true \ No newline at end of file + doctrine_orm: true + +curiosity26_acl_helper: + allowClassAclsDefault: true \ No newline at end of file