From cabb19964622eff5aea17d88a42a8413110ce54b Mon Sep 17 00:00:00 2001 From: Toni Uebernickel Date: Wed, 18 Jan 2012 11:36:19 +0100 Subject: [PATCH] add MutableAclProvider and MutableAcl --- Model/Acl/AclClass.php | 26 ++ Model/Acl/ObjectIdentity.php | 12 + Model/Acl/ObjectIdentityQuery.php | 31 +- Model/Acl/SecurityIdentity.php | 2 +- Resources/config/propel.xml | 7 + Security/Acl/AclProvider.php | 26 +- Security/Acl/Domain/Acl.php | 37 ++ Security/Acl/Domain/Entry.php | 9 +- Security/Acl/Domain/MutableAcl.php | 552 ++++++++++++++++++++++++++++ Security/Acl/MutableAclProvider.php | 264 +++++++++++++ 10 files changed, 951 insertions(+), 15 deletions(-) create mode 100644 Security/Acl/Domain/MutableAcl.php create mode 100644 Security/Acl/MutableAclProvider.php diff --git a/Model/Acl/AclClass.php b/Model/Acl/AclClass.php index 6d1226b..174ef8f 100644 --- a/Model/Acl/AclClass.php +++ b/Model/Acl/AclClass.php @@ -10,9 +10,35 @@ namespace Propel\PropelBundle\Model\Acl; +use PropelPDO; + use Propel\PropelBundle\Model\Acl\om\BaseAclClass; +use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; + class AclClass extends BaseAclClass { + /** + * Return an AclClass for the given ACL ObjectIdentity. + * + * If none can be found, a new one will be saved. + * + * @param ObjectIdentityInterface $objectIdentity + * @param PropelPDO $con + * + * @return AclClass + */ + public static function fromAclObjectIdentity(ObjectIdentityInterface $objectIdentity, PropelPDO $con = null) + { + $obj = AclClassQuery::create() + ->filterByType($objectIdentity->getType()) + ->findOneOrCreate($con) + ; + if ($obj->isNew()) { + $obj->save(); + } + + return $obj; + } } diff --git a/Model/Acl/ObjectIdentity.php b/Model/Acl/ObjectIdentity.php index 2d4e011..51648fc 100644 --- a/Model/Acl/ObjectIdentity.php +++ b/Model/Acl/ObjectIdentity.php @@ -10,9 +10,21 @@ namespace Propel\PropelBundle\Model\Acl; +use PropelPDO; + use Propel\PropelBundle\Model\Acl\om\BaseObjectIdentity; class ObjectIdentity extends BaseObjectIdentity { + public function preInsert(PropelPDO $con = null) + { + // Compatibility with default implementation. + $ancestor = new ObjectIdentityAncestor(); + $ancestor->setObjectIdentityRelatedByObjectIdentityId($this); + $ancestor->setObjectIdentityRelatedByAncestorId($this); + $this->addObjectIdentityAncestorRelatedByAncestorId($ancestor); + + return true; + } } diff --git a/Model/Acl/ObjectIdentityQuery.php b/Model/Acl/ObjectIdentityQuery.php index 40d4c1f..60b01f1 100644 --- a/Model/Acl/ObjectIdentityQuery.php +++ b/Model/Acl/ObjectIdentityQuery.php @@ -10,6 +10,8 @@ namespace Propel\PropelBundle\Model\Acl; +use PropelPDO; + use Propel\PropelBundle\Model\Acl\ObjectIdentity; use Propel\PropelBundle\Model\Acl\om\BaseObjectIdentityQuery; @@ -18,21 +20,40 @@ use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; class ObjectIdentityQuery extends BaseObjectIdentityQuery { /** - * Return an ObjectIdentity object belonging to the given ACL related ObjectIdentity. + * Filter by an ObjectIdentity object belonging to the given ACL related ObjectIdentity. * * @param ObjectIdentityInterface $objectIdentity * - * @return ObjectIdentity + * @return ObjectIdentityQuery $this */ public function filterByAclObjectIdentity(ObjectIdentityInterface $objectIdentity) { + /* + * Not using a JOIN here, because the filter may be applied on 'findOneOrCreate', + * which is currently (Propel 1.6.4-dev) not working. + */ + $aclClass = AclClass::fromAclObjectIdentity($objectIdentity); $this - ->useAclClassQuery() - ->filterByType($objectIdentity->getType()) - ->endUse() + ->filterByClassId($aclClass->getId()) ->filterByIdentifier($objectIdentity->getIdentifier()) ; return $this; } + + /** + * Return an ObjectIdentity object belonging to the given ACL related ObjectIdentity. + * + * @param ObjectIdentityInterface $objectIdentity + * @param PropelPDO $con + * + * @return ObjectIdentity + */ + public function findOneByAclObjectIdentity(ObjectIdentityInterface $objectIdentity, PropelPDO $con = null) + { + return $this + ->filterByAclObjectIdentity($objectIdentity) + ->findOne($con) + ; + } } diff --git a/Model/Acl/SecurityIdentity.php b/Model/Acl/SecurityIdentity.php index 4fe4a0e..d6b89c8 100644 --- a/Model/Acl/SecurityIdentity.php +++ b/Model/Acl/SecurityIdentity.php @@ -64,7 +64,7 @@ class SecurityIdentity extends BaseSecurityIdentity $identifier = $aclIdentity->getRole(); $username = false; } else { - throw new InvalidArgumentException('The ACL identity must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.'); + throw new InvalidArgumentException('The ACL identity must either be an instance of UserSecurityIdentity or RoleSecurityIdentity.'); } $obj = SecurityIdentityQuery::create() diff --git a/Resources/config/propel.xml b/Resources/config/propel.xml index 56e1f11..f1b1102 100644 --- a/Resources/config/propel.xml +++ b/Resources/config/propel.xml @@ -13,6 +13,7 @@ Symfony\Bridge\Propel1\Form\Type\ModelType Propel\PropelBundle\Twig\Extension\SyntaxExtension Symfony\Bridge\Propel1\Form\PropelTypeGuesser + Propel\PropelBundle\Security\Acl\MutableAclProvider @@ -42,5 +43,11 @@ + + + + + + diff --git a/Security/Acl/AclProvider.php b/Security/Acl/AclProvider.php index 8b74dd2..7b07d22 100644 --- a/Security/Acl/AclProvider.php +++ b/Security/Acl/AclProvider.php @@ -11,6 +11,7 @@ namespace Propel\PropelBundle\Security\Acl; use PropelPDO; +use PropelCollection; use Propel\PropelBundle\Model\Acl\EntryQuery; use Propel\PropelBundle\Model\Acl\ObjectIdentityQuery; @@ -69,17 +70,11 @@ class AclProvider implements AclProviderInterface */ public function findChildren(ObjectIdentityInterface $parentObjectIdentity, $directChildrenOnly = false) { - $modelIdentity = ObjectIdentityQuery::create() - ->filterByAclObjectIdentity($parentObjectIdentity) - ->findOne($this->connection) - ; - + $modelIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($parentObjectIdentity, $this->connection); if (empty($modelIdentity)) { return array(); } - $children = array(); - if ($directChildrenOnly) { $collection = ObjectIdentityQuery::create() ->filterByObjectIdentityRelatedByParentObjectIdentityId($modelIdentity) @@ -94,6 +89,7 @@ class AclProvider implements AclProviderInterface ; } + $children = array(); foreach ($collection as $eachChild) { $children[] = new ObjectIdentity($eachChild->getIdentifier(), $eachChild->getAclClass()->getType()); } @@ -132,7 +128,7 @@ class AclProvider implements AclProviderInterface } } - return new Acl($collection, $objectIdentity, $this->permissionGrantingStrategy, $loadedSecurityIdentities); + return $this->getAcl($collection, $objectIdentity, $loadedSecurityIdentities); } /** @@ -154,4 +150,18 @@ class AclProvider implements AclProviderInterface return $result; } + + /** + * Create an ACL. + * + * @param PropelCollection $collection + * @param ObjectIdentityInterface $objectIdentity + * @param array $loadedSecurityIdentities + * + * @return Acl + */ + protected function getAcl(PropelCollection $collection, ObjectIdentityInterface $objectIdentity, array $loadedSecurityIdentities = array()) + { + return new Acl($collection, $objectIdentity, $this->permissionGrantingStrategy, $loadedSecurityIdentities); + } } diff --git a/Security/Acl/Domain/Acl.php b/Security/Acl/Domain/Acl.php index 6fb08b9..d4fbadb 100644 --- a/Security/Acl/Domain/Acl.php +++ b/Security/Acl/Domain/Acl.php @@ -44,6 +44,13 @@ class Acl implements AclInterface protected $loadedSecurityIdentities = array(); + /** + * A list of known associated fields on this ACL. + * + * @var array + */ + protected $fields = array(); + /** * Constructor. * @@ -68,6 +75,7 @@ class Acl implements AclInterface if (null !== $eachEntry->getFieldName() and null === $eachEntry->getObjectIdentityId()) { if (empty($this->classFieldAces[$eachEntry->getFieldName()])) { $this->classFieldAces[$eachEntry->getFieldName()] = array(); + $this->updateFields($eachEntry->getFieldName()); } $this->classFieldAces[$eachEntry->getFieldName()][] = new FieldEntry($eachEntry, $this); @@ -80,6 +88,7 @@ class Acl implements AclInterface if (null !== $eachEntry->getFieldName() and null !== $eachEntry->getObjectIdentityId()) { if (empty($this->objectFieldAces[$eachEntry->getFieldName()])) { $this->objectFieldAces[$eachEntry->getFieldName()] = array(); + $this->updateFields($eachEntry->getFieldName()); } $this->objectFieldAces[$eachEntry->getFieldName()][] = new FieldEntry($eachEntry, $this); @@ -91,6 +100,8 @@ class Acl implements AclInterface $this->parentAcl = $parentAcl; $this->inherited = $inherited; $this->loadedSecurityIdentities = $loadedSecurityIdentities; + + $this->fields = array_unique($this->fields); } /** @@ -282,4 +293,30 @@ class Acl implements AclInterface return $this; } + + /** + * Returns a list of associated fields on this ACL. + * + * @return array + */ + public function getFields() + { + return $this->fields; + } + + /** + * Update the internal list of associated fields on this ACL. + * + * @param string $field + * + * @return MutableAcl $this + */ + protected function updateFields($field) + { + if (!in_array($field, $this->fields)) { + $this->fields[] = $field; + } + + return $this; + } } diff --git a/Security/Acl/Domain/Entry.php b/Security/Acl/Domain/Entry.php index a38229c..0d5e654 100644 --- a/Security/Acl/Domain/Entry.php +++ b/Security/Acl/Domain/Entry.php @@ -49,7 +49,14 @@ class Entry implements AuditableEntryInterface $this->acl = $acl; $this->securityIdentity = SecurityIdentity::toAclIdentity($entry->getSecurityIdentity()); - $this->id = $entry->getId(); + /* + * A new ACE (from a MutableAcl) does not have an ID, + * but will be persisted by the MutableAclProvider afterwards, if issued. + */ + if ($entry->getId()) { + $this->id = $entry->getId(); + } + $this->mask = $entry->getMask(); $this->isGranting = $entry->getGranting(); $this->strategy = $entry->getGrantingStrategy(); diff --git a/Security/Acl/Domain/MutableAcl.php b/Security/Acl/Domain/MutableAcl.php new file mode 100644 index 0000000..bdad01d --- /dev/null +++ b/Security/Acl/Domain/MutableAcl.php @@ -0,0 +1,552 @@ + + */ +class MutableAcl extends Acl implements MutableAclInterface +{ + /** + * The id of the current ACL. + * + * It's the id of the ObjectIdentity model. + * + * @var int + */ + protected $id; + + /** + * A list of all ACL entries from the database. + * + * Contains instances of Propel\PropelBundle\Model\Acl\Entry. + * + * @var PropelCollection + */ + protected $entries; + + /** + * A reference to the ObjectIdentity this ACL is mapped to. + * + * @var ObjectIdentity + */ + protected $modelObjectIdentity; + + /** + * A connection to be used for all changes on the ACL. + * + * @var PropelPDO + */ + protected $con; + + /** + * Constructor. + * + * @param PropelCollection $entries + * @param ObjectIdentityInterface $objectIdentity + * @param PermissionGrantingStrategyInterface $permissionGrantingStrategy + * @param array $loadedSecurityIdentities + * @param AclInterface $parentAcl + * @param boolean $inherited + * @param PropelPDO $con + */ + public function __construct(PropelCollection $entries, ObjectIdentityInterface $objectIdentity, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = false, PropelPDO $con = null) + { + parent::__construct($entries, $objectIdentity, $permissionGrantingStrategy, $loadedSecurityIdentities, $parentAcl, $inherited); + + $this->entries = $entries; + + $this->modelObjectIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($objectIdentity); + $this->id = $this->modelObjectIdentity->getId(); + + $this->con = $con; + } + + /** + * Returns the primary key of this ACL + * + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * Sets whether entries are inherited + * + * @param boolean $boolean + */ + public function setEntriesInheriting($boolean) + { + $this->inherited = $boolean; + } + + /** + * Sets the parent ACL + * + * @param AclInterface $acl + */ + public function setParentAcl(AclInterface $acl) + { + $this->parentAcl = $acl; + } + + /** + * Deletes a class-based ACE + * + * @param integer $index + */ + public function deleteClassAce($index) + { + $this->deleteIndex($this->classAces, $index); + } + + /** + * Deletes a class-field-based ACE + * + * @param integer $index + * @param string $field + */ + public function deleteClassFieldAce($index, $field) + { + $this + ->validateField($this->classFieldAces, $field) + ->deleteIndex($this->classFieldAces[$field], $index) + ; + } + + /** + * Deletes an object-based ACE + * + * @param integer $index + */ + public function deleteObjectAce($index) + { + $this->deleteIndex($this->objectAces, $index); + } + + /** + * Deletes an object-field-based ACE + * + * @param integer $index + * @param string $field + */ + public function deleteObjectFieldAce($index, $field) + { + $this + ->validateField($this->objectFieldAces, $field) + ->deleteIndex($this->objectFieldAces[$field], $index) + ; + } + + /** + * Inserts a class-based ACE + * + * @param SecurityIdentityInterface $securityIdentity + * @param integer $mask + * @param integer $index + * @param boolean $granting + * @param string $strategy + */ + public function insertClassAce(SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null) + { + $this->insertToList($this->classAces, $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting)); + } + + /** + * Inserts a class-field-based ACE + * + * @param string $field + * @param SecurityIdentityInterface $securityIdentity + * @param integer $mask + * @param integer $index + * @param boolean $granting + * @param string $strategy + */ + public function insertClassFieldAce($field, SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null) + { + $this->insertToList($this->classFieldAces, $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting, $field)); + } + + /** + * Inserts an object-based ACE + * + * @param SecurityIdentityInterface $securityIdentity + * @param integer $mask + * @param integer $index + * @param boolean $granting + * @param string $strategy + */ + public function insertObjectAce(SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null) + { + $this->insertToList($this->objectAces, $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting)); + } + + /** + * Inserts an object-field-based ACE + * + * @param string $field + * @param SecurityIdentityInterface $securityIdentity + * @param integer $mask + * @param integer $index + * @param boolean $granting + * @param string $strategy + */ + public function insertObjectFieldAce($field, SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null) + { + $this->insertToList($this->objectFieldAces, $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting, $field)); + } + + /** + * Updates a class-based ACE + * + * @param integer $index + * @param integer $mask + * @param string $strategy if null the strategy should not be changed + */ + public function updateClassAce($index, $mask, $strategy = null) + { + $this->updateAce($this->classAces, $index, $mask, $strategy); + } + + /** + * Updates a class-field-based ACE + * + * @param integer $index + * @param string $field + * @param integer $mask + * @param string $strategy if null the strategy should not be changed + */ + public function updateClassFieldAce($index, $field, $mask, $strategy = null) + { + $this + ->validateField($this->classFieldAces, $field) + ->updateAce($this->classFieldAces[$field], $index, $mask, $strategy, $field) + ; + } + + /** + * Updates an object-based ACE + * + * @param integer $index + * @param integer $mask + * @param string $strategy if null the strategy should not be changed + */ + public function updateObjectAce($index, $mask, $strategy = null) + { + $this->updateAce($this->objectAces, $index, $mask, $strategy); + } + + /** + * Updates an object-field-based ACE + * + * @param integer $index + * @param string $field + * @param integer $mask + * @param string $strategy if null the strategy should not be changed + */ + public function updateObjectFieldAce($index, $field, $mask, $strategy = null) + { + $this->validateField($this->objectFieldAces, $field); + $this->updateAce($this->objectFieldAces[$field], $index, $mask, $strategy, $field); + } + + /** + * String representation of object + * + * @link http://php.net/manual/en/serializable.serialize.php + * + * @return string the string representation of the object or &null; + */ + public function serialize() + { + return serialize(array( + $this->id, + $this->entries, + $this->modelObjectIdentity, + $this->model, + $this->classAces, + $this->classFieldAces, + $this->objectAces, + $this->objectFieldAces, + $this->objectIdentity, + $this->parentAcl, + $this->permissionGrantingStrategy, + $this->inherited, + $this->loadedSecurityIdentities, + )); + } + + /** + * Constructs the object + * + * @link http://php.net/manual/en/serializable.unserialize.php + * + * @param string $serialized + * + * @return mixed the original value unserialized. + */ + public function unserialize($serialized) + { + list( + $this->id, + $this->entries, + $this->modelObjectIdentity, + $this->model, + $this->classAces, + $this->classFieldAces, + $this->objectAces, + $this->objectFieldAces, + $this->objectIdentity, + $this->parentAcl, + $this->permissionGrantingStrategy, + $this->inherited, + $this->loadedSecurityIdentities, + ) = unserialize($serialized); + + return $this; + } + + /** + * Insert a given entry into the list on the given index by shifting all others. + * + * @param array $list + * @param int $index + * @param Entry $entry + * + * @return MutableAcl $this + */ + protected function insertToList(array &$list, $index, Entry $entry) + { + $this->isWithinBounds($list, $index); + + if ($entry instanceof FieldEntry) { + $this->updateFields($entry->getField()); + } + + $list = array_merge( + array_slice($list, 0, $index), + array($entry), + array_splice($list, $index) + ); + + return $this; + } + + /** + * Update a single ACE of this ACL. + * + * @param array $list + * @param int $index + * @param int $mask + * @param string $strategy + * @param string $field + * + * @return MutableAcl $this + */ + protected function updateAce(array &$list, $index, $mask, $strategy = null, $field = null) + { + $this->validateIndex($list, $index); + + $beforeAce = $list[$index]; + /* @var $beforeAce Entry */ + $entry = new ModelEntry(); + + // Already persisted before? + if ($beforeAce->getId()) { + $entry->setId($beforeAce->getId()); + } + + if (null === $strategy) { + $strategy = $beforeAce->getStrategy(); + } + + $entry + ->setMask($mask) + ->setGranting($beforeAce->isGranting()) + ->setGrantingStrategy($strategy) + ->setSecurityIdentity(SecurityIdentity::fromAclIdentity($beforeAce->getSecurityIdentity())) + ; + + if (null !== $field) { + $this->updateFields($field); + + $entry->setFieldName($field); + $newAce = new FieldEntry($entry, $this); + } else { + $newAce = new Entry($entry, $this); + } + + $list[$index] = $newAce; + + return $this; + } + + /** + * Delete the ACE of the given list and index. + * + * The list will be re-ordered to have a valid 0..x list. + * + * @param array $list + * @param $index + * + * @return MutableAcl $this + */ + protected function deleteIndex(array &$list, $index) + { + $this->validateIndex($list, $index); + unset($list[$index]); + $this->reorderList($list, $index-1); + + return $this; + } + + /** + * Validate the index on the given list of ACEs. + * + * @throws OutOfBoundsException + * + * @param array $list + * @param int $index + * + * @return MutableAcl $this + */ + protected function isWithinBounds(array &$list, $index) + { + // No count()-1, the count is one ahead of index, and could create the next valid entry! + if ($index < 0 or $index > count($list)) { + throw new OutOfBoundsException(sprintf('The index must be in the interval [0, %d].', count($list))); + } + + return $this; + } + + /** + * Check the index for existence in the given list. + * + * @throws OutOfBoundsException + * + * @param array $list + * @param $index + * + * @return MutableAcl $this + */ + protected function validateIndex(array &$list, $index) + { + if (!isset($list[$index])) { + throw new OutOfBoundsException(sprintf('The index "%d" does not exist.', $index)); + } + + return $this; + } + + /** + * Validate the given field to be present. + * + * @throws InvalidArgumentException + * + * @param array $list + * @param string $field + * + * @return MutableAcl $this + */ + protected function validateField(array &$list, $field) + { + if (!isset($list[$field])) { + throw new InvalidArgumentException(sprintf('The given field "%s" does not exist.', $field)); + } + + return $this; + } + + /** + * Order the given list to have numeric indexes from 0..x + * + * @param array $list + * @param int $index The right boundary to which the list is valid. + * + * @return MutableAcl $this + */ + protected function reorderList(array &$list, $index) + { + $list = array_merge( + array_slice($list, 0, $index+1), // +1 to get length + array_splice($list, $index+1) // +1 to get first index to re-order + ); + + return $this; + } + + /** + * Create a new ACL Entry. + * + * @param int $mask + * @param int $index + * @param SecurityIdentityInterface $securityIdentity + * @param string $strategy + * @param bool $granting + * @param string $field + * + * @return Entry|FieldEntry + */ + protected function createAce($mask, $index, SecurityIdentityInterface $securityIdentity, $strategy = null, $granting = true, $field = null) + { + if (!is_int($mask)) { + throw new InvalidArgumentException('The given mask is not valid. Please provide an integer.'); + } + + // Compatibility with default implementation + if (null === $strategy) { + if (true === $granting) { + $strategy = PermissionGrantingStrategy::ALL; + } else { + $strategy = PermissionGrantingStrategy::ANY; + } + } + + $model = new ModelEntry(); + $model + ->setAceOrder($index) + ->setMask($mask) + ->setGrantingStrategy($strategy) + ->setGranting($granting) + ->setSecurityIdentity(SecurityIdentity::fromAclIdentity($securityIdentity)) + ; + + if (null !== $field) { + $model->setFieldName($field); + + return new FieldEntry($model, $this); + } + + return new Entry($model, $this); + } +} diff --git a/Security/Acl/MutableAclProvider.php b/Security/Acl/MutableAclProvider.php new file mode 100644 index 0000000..857f862 --- /dev/null +++ b/Security/Acl/MutableAclProvider.php @@ -0,0 +1,264 @@ + + */ +class MutableAclProvider extends AclProvider implements MutableAclProviderInterface +{ + /** + * Constructor. + * + * @param PermissionGrantingStrategyInterface $permissionGrantingStrategy + * @param PropelPDO $connection + * @param AclCacheInterface $cache + */ + public function __construct(PermissionGrantingStrategyInterface $permissionGrantingStrategy, PropelPDO $connection = null, AclCacheInterface $cache = null) + { + if (null === $connection) { + $connection = Propel::getConnection(EntryPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + parent::__construct($permissionGrantingStrategy, $connection, $cache); + } + + /** + * Creates a new ACL for the given object identity. + * + * @throws AclAlreadyExistsException When there already is an ACL for the given object identity. + * + * @param ObjectIdentityInterface $objectIdentity + * + * @return MutableAclInterface + */ + public function createAcl(ObjectIdentityInterface $objectIdentity) + { + $entries = EntryQuery::create()->findByAclIdentity($objectIdentity, array(), $this->connection); + if (count($entries)) { + throw new AclAlreadyExistsException('An ACL for the given object identity already exists, find and update that one.'); + } + + $objIdentity = ObjectIdentityQuery::create() + ->filterByAclObjectIdentity($objectIdentity) + ->findOneOrCreate($this->connection) + ; + + if ($objIdentity->isNew()) { + // This is safe to do, it makes the ID available and does not affect changes to any ACL. + $objIdentity->save($this->connection); + } + + return new MutableAcl($entries, $objectIdentity, $this->permissionGrantingStrategy, array(), null, false, $this->connection); + } + + /** + * Deletes the ACL for a given object identity. + * + * This will automatically trigger a delete for any child ACLs. If you don't + * want child ACLs to be deleted, you will have to set their parent ACL to null. + * + * @throws AclException + * + * @param ObjectIdentityInterface $objectIdentity + * + * @return bool + */ + public function deleteAcl(ObjectIdentityInterface $objectIdentity) + { + try { + $objIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($objectIdentity, array(), $this->connection); + if (null === $objIdentity) { + // No object identity, no ACL, so deletion is successful (expected result is given). + return true; + } + + EntryQuery::create() + ->filterByObjectIdentity($objIdentity) + ->delete($this->connection) + ; + + return true; + } catch (Exception $e) { + throw new AclException('An error occurred while deleting the ACL.', 1, $e); + } + } + + /** + * Persists any changes which were made to the ACL, or any associated access control entries. + * + * Changes to parent ACLs are not persisted. + * + * @todo Add handling of parent ACL changes (tree changes). + * + * @throws AclException + * + * @param MutableAclInterface $acl + * + * @return bool + */ + public function updateAcl(MutableAclInterface $acl) + { + if (!$acl instanceof Acl) { + throw new InvalidArgumentException('The given ACL is not tracked by this provider. Please provide Propel\PropelBundle\Security\Acl\Domain\Acl only.'); + } + + try { + $modelEntries = EntryQuery::create()->findByAclIdentity($acl->getObjectIdentity(), array(), $this->connection); + $objectIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($acl->getObjectIdentity(), $this->connection); + + $this->connection->beginTransaction(); + + $keepEntries = array_merge( + $this->persistAcl($acl->getClassAces(), $objectIdentity), + $this->persistAcl($acl->getObjectAces(), $objectIdentity, true) + ); + + foreach ($acl->getFields() as $eachField) { + $keepEntries = array_merge($keepEntries, + $this->persistAcl($acl->getClassFieldAces($eachField), $objectIdentity), + $this->persistAcl($acl->getObjectFieldAces($eachField), $objectIdentity, true) + ); + } + + foreach ($modelEntries as &$eachEntry) { + if (!in_array($eachEntry->getId(), $keepEntries)) { + $eachEntry->delete($this->connection); + } + } + + $this->connection->commit(); + + return true; + } catch (Exception $e) { + $this->connection->rollBack(); + + throw new AclException('An error occurred while updating the ACL.', 0, $e); + } + } + + /** + * Persist the given ACEs. + * + * @param array $accessControlEntries + * @param ObjectIdentity $objectIdentity + * @param bool $object + * + * @return array The IDs of the persisted ACEs. + */ + protected function persistAcl(array $accessControlEntries, ObjectIdentity $objectIdentity, $object = false) + { + $entries = array(); + + /* @var $eachAce \Symfony\Component\Security\Acl\Model\EntryInterface */ + foreach ($accessControlEntries as $order => $eachAce) { + // If the given ACE has never been persisted, create a new one. + if (null === $entry = $this->getPersistedAce($eachAce)) { + $entry = new ModelEntry(); + } + + if ($eachAce instanceof FieldEntryInterface) { + $entry->setFieldName($eachAce->getField()); + } + + $entry + ->setAceOrder($order) + ->setAclClass($objectIdentity->getAclClass()) + ->setMask($eachAce->getMask()) + ->setGranting($eachAce->isGranting()) + ->setGrantingStrategy($eachAce->getStrategy()) + ->setSecurityIdentity(SecurityIdentity::fromAclIdentity($eachAce->getSecurityIdentity())) + ; + + if (true === $object) { + $entry->setObjectIdentity($objectIdentity); + } + + $entry->save($this->connection); + + $entries[] = $entry->getId(); + } + + return $entries; + } + + /** + * Retrieve the persisted model for the given ACE. + * + * If none is given, null is returned. + * + * @param Entry $ace + * + * @return ModelEntry|null + */ + protected function getPersistedAce(Entry $ace) + { + if (null === $ace->getId()) { + return null; + } + + if (null === $entry = EntryQuery::create()->findPk($ace->getId(), $this->connection)) { + return null; + } + + // Retrieve fresh data from the database not from any caching. + $entry->reload(false, $this->connection); + + return $entry; + } + + + /** + * Get an ACL for this provider. + * + * @param PropelCollection $collection + * @param ObjectIdentityInterface $objectIdentity + * @param array $loadedSecurityIdentities + * + * @return MutableAcl + */ + protected function getAcl(PropelCollection $collection, ObjectIdentityInterface $objectIdentity, array $loadedSecurityIdentities = array()) + { + return new MutableAcl($collection, $objectIdentity, $this->permissionGrantingStrategy, $loadedSecurityIdentities); + } +} \ No newline at end of file