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