diff --git a/src/Common/LdapObject.php b/src/Common/LdapObject.php index 777e213..73684a9 100644 --- a/src/Common/LdapObject.php +++ b/src/Common/LdapObject.php @@ -2,6 +2,7 @@ namespace Ulmus\Ldap\Common; +use http\Exception\InvalidArgumentException; use function ldap_set_option, ldap_start_tls, ldap_bind, ldap_unbind, ldap_connect, ldap_close, ldap_get_entries, ldap_mod_replace, ldap_count_entries, ldap_errno, ldap_error; class LdapObject { @@ -12,6 +13,8 @@ class LdapObject { public $search; + protected string $host; + public int $rowCount = 0; public int $bufferedRows = 0; @@ -22,6 +25,8 @@ class LdapObject { public bool $binded = false; + public bool $deleteRecursively = true; + public function __destruct() { isset($this->connection) and ldap_close($this->connection); @@ -29,6 +34,8 @@ class LdapObject { public function connect(string $host, string $baseDn) : bool { + $this->host = $host; + $this->connection = ldap_connect("ldap://$host"); $this->dn = $baseDn; @@ -54,7 +61,12 @@ class LdapObject { throw new \Exception("LdapObject is already binded with a user. Use the unbind() method to release it."); } - $this->binded = ldap_bind($this->connection, $dn, $password); + try { + $this->binded = ldap_bind($this->connection, $dn, $password); + } + catch(\Throwable $ex) { + throw new \ErrorException(sprintf("%s [ using: %s on %s ]", $ex->getMessage(), $dn, $this->host )); + } return $this->binded; } @@ -82,7 +94,9 @@ class LdapObject { { static::$dump && call_user_func_array(static::$dump, [ $filter, $fields ]); - $this->search = ldap_search($this->connection, $this->dn, $filter['filters'], $filter['fields'] ?? [], 0, 0); + $this->search = @ldap_search($this->connection, $this->dn, $filter['filters'], $filter['fields'] ?? [], 0, $filter['limit'] ?? 0); + + $this->throwLdapException(); $this->rowCount = $this->bufferedRows = ldap_count_entries($this->connection, $this->search); @@ -127,10 +141,10 @@ class LdapObject { return false; } - public function fetchAll() - { - return ldap_get_entries($this->connection, $result); - } + #public function fetchAll() + #{ + # return ldap_get_entries($this->connection, $result); + #} public function rowCount() : int { @@ -187,9 +201,22 @@ class LdapObject { return $this; } - public function runDeleteQuery(array $filter, array $control) + public function runDeleteQuery(array $filter) { - static::$dump && call_user_func_array(static::$dump, [ $filter, $dataset ]); + static::$dump && call_user_func_array(static::$dump, [ $filter ]); + + if ( empty($filter['dn']) ) { + throw new InvalidArgumentException("A valid DN must be provided to run a 'delete' query on LDAP connector"); + } + + if ($this->deleteRecursively) { + $list = ldap_list($this->connection, $filter['dn'], "ObjectClass=*", [""]); + $info = ldap_get_entries($this->connection, $list); + + for($i=0; $i < $info['count']; $i++){ + $this->runDeleteQuery($info[$i]); + } + } if ( false === ( $queryResult = ldap_delete($this->connection, $filter['dn']) ) ) { $this->throwLdapException(); @@ -202,7 +229,15 @@ class LdapObject { protected function throwLdapException() : void { - throw new \Exception(sprintf('LDAP error #%s `%s`', ldap_errno($this->connection), ldap_error($this->connection))); + $no = ldap_errno($this->connection); + + switch($no) { + case 0: # Success + case 4: # Skipping 'Size limit exceeded' error + return; + } + + throw new \Exception(sprintf('LDAP error #%s `%s`', $no, ldap_error($this->connection))); } public function closeCursor() : void {} diff --git a/src/Entity/User.php b/src/Entity/User.php index 6267534..ab74f3e 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -61,9 +61,12 @@ class User #[Field] public string $title; - ##[Virtual] + ##[Field(readonly:true)] # public ? array $memberOf; + ##[Field(readonly:true)] + #public ? array $proxyAddresses; + #[Field] public int $userAccountControl; @@ -132,7 +135,13 @@ class User #[Field(name: "lastLogon", readonly: true)] public LdapDatetime $lastLogon; - + + #[Field(name: "whenCreated", readonly: true)] + public LdapDatetime $whenCreated; + + #[Field(name: "whenChanged", readonly: true)] + public LdapDatetime $whenChanged; + public function __toString() : string { return implode(' ', array_filter([ $this->firstname ?? "", $this->lastname ?? "" ])) ?: ( $this->displayName ?? "" ); @@ -145,4 +154,27 @@ class User return $arr; } + + public function brokenMigration() : bool + { + if ( empty($this->targetAddress) ) { + foreach($this->proxyAddresses ?? [] as $proxy) { + if ( str_contains(strtolower($proxy), 'x500') ) { + return true; + } + } + } + + return false; + } + + public function migrated() : bool + { + return ! empty($this->targetAddress); + } + + public function unmigrated() : bool + { + return empty($this->targetAddress) && empty($this->proxyAddresses); + } } \ No newline at end of file diff --git a/src/Query/Set.php b/src/Query/Set.php index abe4c78..21b46e8 100644 --- a/src/Query/Set.php +++ b/src/Query/Set.php @@ -4,7 +4,7 @@ namespace Ulmus\Ldap\Query; class Set extends \Ulmus\Query\Set { - public function render() /* : mixed */ + public function render() : mixed { foreach($this->dataset as $key => $value) { $this->queryBuilder->addParameter($value, $key); diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 549f5dd..c0f75c8 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -4,7 +4,7 @@ namespace Ulmus\Ldap; use Ulmus; -class QueryBuilder implements Ulmus\Query\QueryBuilderInterface +class QueryBuilder implements Ulmus\QueryBuilder\QueryBuilderInterface { public Query\Filter $where; @@ -20,7 +20,7 @@ class QueryBuilder implements Ulmus\Query\QueryBuilderInterface * Those values are to be inserted or updated */ public array $values = []; - + public string $whereConditionOperator = Ulmus\Query\Where::CONDITION_AND; public string $havingConditionOperator = Ulmus\Query\Where::CONDITION_AND; @@ -195,14 +195,14 @@ class QueryBuilder implements Ulmus\Query\QueryBuilderInterface return $this; } - public function push(Ulmus\Query\Fragment $queryFragment) : self + public function push(Ulmus\Query\QueryFragmentInterface $queryFragment) : self { $this->queryStack[] = $queryFragment; return $this; } - public function pull(Ulmus\Query\Fragment $queryFragment) : self + public function pull(Ulmus\Query\QueryFragmentInterface $queryFragment) : self { return array_shift($this->queryStack); } @@ -241,7 +241,7 @@ class QueryBuilder implements Ulmus\Query\QueryBuilderInterface return null; } - public function removeFragment(array|string|Ulmus\Query\Fragment|\Stringable $fragment) : void + public function removeFragment(array|string|Ulmus\Query\QueryFragmentInterface|\Stringable $fragment) : void { foreach($this->queryStack as $key => $item) { if ( $item === $fragment ) { diff --git a/src/Repository.php b/src/Repository.php index 09be466..34c280e 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -91,9 +91,9 @@ class Repository extends \Ulmus\Repository throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`"); } - $this->delete($entity->dn)->deleteAll()->rowCount(); + $deleted = $this->delete($entity->dn)->deleteAll()->rowCount(); - return false; + return $deleted > 0; } public function loadAllFromOU(string $ou) : EntityCollection