diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php index f906fa6ff4285..808f5372ca4b9 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php @@ -13,6 +13,7 @@ use Symfony\Component\Ldap\Adapter\EntryManagerInterface; use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\UpdateOperationException; use Symfony\Component\Ldap\Exception\LdapException; use Symfony\Component\Ldap\Exception\NotBoundException; @@ -121,4 +122,21 @@ private function getConnectionResource() return $this->connection->getResource(); } + + /** + * @param iterable|UpdateOperation[] $operations An array or iterable of UpdateOperation instances + * + * @throws UpdateOperationException in case of an error + */ + public function applyOperations(string $dn, iterable $operations): void + { + $operationsMapped = array(); + foreach ($operations as $modification) { + $operationsMapped[] = $modification->toArray(); + } + + if (!@ldap_modify_batch($this->getConnectionResource(), $dn, $operationsMapped)) { + throw new UpdateOperationException(sprintf('Error executing UpdateOperation on "%s": "%s".', $dn, ldap_error($this->getConnectionResource()))); + } + } } diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php new file mode 100644 index 0000000000000..87bd451eb7229 --- /dev/null +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Adapter\ExtLdap; + +use Symfony\Component\Ldap\Exception\UpdateOperationException; + +class UpdateOperation +{ + private $operationType; + private $values; + private $attribute; + + private $validOperationTypes = array( + LDAP_MODIFY_BATCH_ADD, + LDAP_MODIFY_BATCH_REMOVE, + LDAP_MODIFY_BATCH_REMOVE_ALL, + LDAP_MODIFY_BATCH_REPLACE, + ); + + /** + * @param int $operationType An LDAP_MODIFY_BATCH_* constant + * @param string $attribute The attribute to batch modify on + * + * @throws UpdateOperationException on consistency errors during construction + */ + public function __construct(int $operationType, string $attribute, ?array $values) + { + $this->assertValidOperationType($operationType); + $this->assertNullValuesOnRemoveAll($operationType, $values); + + $this->operationType = $operationType; + $this->attribute = $attribute; + $this->values = $values; + } + + public function toArray(): array + { + return array( + 'attrib' => $this->attribute, + 'modtype' => $this->operationType, + 'values' => $this->values, + ); + } + + /** + * @param int $operationType + */ + private function assertValidOperationType(int $operationType): void + { + if (!in_array($operationType, $this->validOperationTypes, true)) { + throw new UpdateOperationException(sprintf('"%s" is not a valid modification type.', $operationType)); + } + } + + /** + * @param int $operationType + * @param array|null $values + * + * @throws \Symfony\Component\Ldap\Exception\UpdateOperationException + */ + private function assertNullValuesOnRemoveAll(int $operationType, ?array $values): void + { + if (LDAP_MODIFY_BATCH_REMOVE_ALL === $operationType && null !== $values) { + throw new UpdateOperationException(sprintf('$values must be null for LDAP_MODIFY_BATCH_REMOVE_ALL operation, "%s" given.', gettype($values))); + } + } +} diff --git a/src/Symfony/Component/Ldap/Exception/UpdateOperationException.php b/src/Symfony/Component/Ldap/Exception/UpdateOperationException.php new file mode 100644 index 0000000000000..f220e4e52dd05 --- /dev/null +++ b/src/Symfony/Component/Ldap/Exception/UpdateOperationException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Exception; + +class UpdateOperationException extends LdapException +{ +} diff --git a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php index 76478d465946f..cb282f1cfff5e 100644 --- a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php +++ b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php @@ -13,7 +13,9 @@ use Symfony\Component\Ldap\Adapter\ExtLdap\Adapter; use Symfony\Component\Ldap\Adapter\ExtLdap\Collection; +use Symfony\Component\Ldap\Adapter\ExtLdap\UpdateOperation; use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\UpdateOperationException; use Symfony\Component\Ldap\Exception\LdapException; use Symfony\Component\Ldap\Exception\NotBoundException; @@ -238,4 +240,105 @@ public function testLdapAddAttributeValuesError() $entryManager->addAttributeValues($entry, 'mail', $entry->getAttribute('mail')); } + + public function testLdapApplyOperationsRemoveAllWithArrayError() + { + $entryManager = $this->adapter->getEntryManager(); + + $result = $this->executeSearchQuery(1); + $entry = $result[0]; + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(UpdateOperationException::class); + + $entryManager->applyOperations($entry->getDn(), array(new UpdateOperation(LDAP_MODIFY_BATCH_REMOVE_ALL, 'mail', array()))); + } + + public function testLdapApplyOperationsWithWrongConstantError() + { + $entryManager = $this->adapter->getEntryManager(); + + $result = $this->executeSearchQuery(1); + $entry = $result[0]; + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(UpdateOperationException::class); + + $entryManager->applyOperations($entry->getDn(), array(new UpdateOperation(512, 'mail', array()))); + } + + public function testApplyOperationsAddRemoveAttributeValues() + { + $entryManager = $this->adapter->getEntryManager(); + + $result = $this->executeSearchQuery(1); + $entry = $result[0]; + + $entryManager->applyOperations($entry->getDn(), array( + new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', array('fabpot@example.org', 'fabpot2@example.org')), + new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', array('fabpot3@example.org', 'fabpot4@example.org')), + )); + + $result = $this->executeSearchQuery(1); + $newEntry = $result[0]; + + $this->assertCount(6, $newEntry->getAttribute('mail')); + + $entryManager->applyOperations($entry->getDn(), array( + new UpdateOperation(LDAP_MODIFY_BATCH_REMOVE, 'mail', array('fabpot@example.org', 'fabpot2@example.org')), + new UpdateOperation(LDAP_MODIFY_BATCH_REMOVE, 'mail', array('fabpot3@example.org', 'fabpot4@example.org')), + )); + + $result = $this->executeSearchQuery(1); + $newNewEntry = $result[0]; + + $this->assertCount(2, $newNewEntry->getAttribute('mail')); + } + + public function testUpdateOperationsWithIterator() + { + $iteratorAdd = new \ArrayIterator(array( + new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', array('fabpot@example.org', 'fabpot2@example.org')), + new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', array('fabpot3@example.org', 'fabpot4@example.org')), + )); + + $iteratorRemove = new \ArrayIterator(array( + new UpdateOperation(LDAP_MODIFY_BATCH_REMOVE, 'mail', array('fabpot@example.org', 'fabpot2@example.org')), + new UpdateOperation(LDAP_MODIFY_BATCH_REMOVE, 'mail', array('fabpot3@example.org', 'fabpot4@example.org')), + )); + + $entryManager = $this->adapter->getEntryManager(); + + $result = $this->executeSearchQuery(1); + $entry = $result[0]; + + $entryManager->applyOperations($entry->getDn(), $iteratorAdd); + + $result = $this->executeSearchQuery(1); + $newEntry = $result[0]; + + $this->assertCount(6, $newEntry->getAttribute('mail')); + + $entryManager->applyOperations($entry->getDn(), $iteratorRemove); + + $result = $this->executeSearchQuery(1); + $newNewEntry = $result[0]; + + $this->assertCount(2, $newNewEntry->getAttribute('mail')); + } + + public function testUpdateOperationsThrowsExceptionWhenAddedDuplicatedValue() + { + $duplicateIterator = new \ArrayIterator(array( + new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', array('fabpot@example.org')), + new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', array('fabpot@example.org')), + )); + + $entryManager = $this->adapter->getEntryManager(); + + $result = $this->executeSearchQuery(1); + $entry = $result[0]; + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(UpdateOperationException::class); + + $entryManager->applyOperations($entry->getDn(), $duplicateIterator); + } }