- Updated to match Notes-2.x

This commit is contained in:
Dave M. 2024-10-10 13:38:28 -04:00
parent 4b9a162696
commit 5cd0d13404
14 changed files with 243 additions and 70 deletions

View File

@ -9,6 +9,7 @@ use Ulmus\{Adapter\AdapterInterface,
Migration\FieldDefinition,
Ulmus};
use LDAP\Result;
use Ulmus\Ldap\Common\LdapObject;
use function ldap_set_option, ldap_start_tls, ldap_bind, ldap_unbind, ldap_connect, ldap_close, ldap_get_entries;
@ -28,15 +29,15 @@ class Ldap implements \Ulmus\Adapter\AdapterInterface {
public bool $encrypt;
public bool $forceSSL = false;
public array $hosts;
public string $baseDn;
public string $username;
public string $password;
public string $accountSuffix;
public string $pathCertCrt;
@ -73,11 +74,11 @@ class Ldap implements \Ulmus\Adapter\AdapterInterface {
}
}
public function authenticate(string $dn, string $password) : bool
public function authenticate(string $dn, string $password) : false|Result
{
$this->ldapObject = $this->getLdapObject();
return $this->ldapObject->bind($dn, $password);;
return $this->ldapObject->bind($dn, $password);
}
public function connect() : object
@ -92,7 +93,7 @@ class Ldap implements \Ulmus\Adapter\AdapterInterface {
public function bindUser() : void
{
if ( ! $this->ldapObject->bind($this->username, $this->password) ) {
throw new \Exception("LDAP bind failed with given user $usr.");
throw new \Exception("LDAP bind failed with given user {$this->username}.");
}
}
@ -118,7 +119,7 @@ class Ldap implements \Ulmus\Adapter\AdapterInterface {
elseif ($this->forceSSL) {
$ldapObject->startTLS();
}
return $ldapObject;
}
@ -126,11 +127,11 @@ class Ldap implements \Ulmus\Adapter\AdapterInterface {
{
return "";
}
public function setup(array $configuration) : void
{
$configuration = array_change_key_case($configuration, \CASE_LOWER);
if ( false === ( $this->hosts = $configuration['hosts'] ?? false ) ) {
throw new AdapterConfigurationException("Your `host` setting is missing. It is a mandatory parameter for this driver.");
}
@ -166,7 +167,7 @@ class Ldap implements \Ulmus\Adapter\AdapterInterface {
return $value;
}
public function escapeIdentifier(string $segment, int $type, string $ignore = "") : string
{
switch($type) {

View File

@ -3,7 +3,8 @@
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;
use LDAP\Result;
use function ldap_set_option, ldap_start_tls, ldap_bind_ext, ldap_unbind, ldap_connect, ldap_close, ldap_get_entries, ldap_mod_replace, ldap_count_entries, ldap_errno, ldap_error;
class LdapObject {
@ -23,7 +24,9 @@ class LdapObject {
public string $dn;
public bool $binded = false;
public false|Result $binded = false;
public false|Result $lastQuery = false;
public bool $deleteRecursively = true;
@ -55,14 +58,14 @@ class LdapObject {
ldap_start_tls($this->connection);
}
public function bind(? string $dn, ? string $password = null) : bool
public function bind(? string $dn, ? string $password = null) : false|Result
{
if ($this->binded) {
throw new \Exception("LdapObject is already binded with a user. Use the unbind() method to release it.");
}
try {
$this->binded = ldap_bind($this->connection, $dn, $password);
$this->binded = ldap_bind_ext($this->connection, $dn, $password);
}
catch(\Throwable $ex) {
throw new \ErrorException(sprintf("%s [ using: %s on %s ]", $ex->getMessage(), $dn, $this->host ));
@ -172,31 +175,50 @@ class LdapObject {
}
try {
if (false === ($queryResult = ldap_add($this->connection, $dn, $combine))) {
$this->lastQuery = ldap_add_ext($this->connection, $dn, $combine);
if ($this->lastQuery === false) {
$this->throwLdapException();
}
else {
$this->rowCount = 1;
$this->lastInsertId = $dn;
}
}
catch(\Throwable $e) {
$this->throwLdapException();
}
if ($queryResult) {
$this->rowCount = (int)$queryResult;
$this->lastInsertId = $dn;
return $this;
}
public function runAddQuery(array $filter, array $dataset)
{
static::$dump && call_user_func_array(static::$dump, [ $filter, $dataset ]);
$this->lastQuery = ldap_mod_add_ext($this->connection, $filter['dn'], $dataset);
if ( $this->lastQuery === false ) {
$this->throwLdapException();
}
$this->rowCount = $this->lastQuery === false ? 0 : 1;
return $this;
}
public function runUpdateQuery(array $filter, array $dataset)
{
static::$dump && call_user_func_array(static::$dump, [ $filter, $dataset ]);
if ( false === ( $queryResult = ldap_mod_replace($this->connection, $filter['dn'], $dataset) ) ) {
$this->lastQuery = ldap_mod_replace_ext($this->connection, $filter['dn'], $dataset);
if ( $this->lastQuery === false ) {
$this->throwLdapException();
}
$this->rowCount = (int) $queryResult;
$this->rowCount = $this->lastQuery === false ? 0 : 1;
return $this;
}
@ -218,11 +240,13 @@ class LdapObject {
}
}
if ( false === ( $queryResult = ldap_delete($this->connection, $filter['dn']) ) ) {
$this->lastQuery = ldap_delete_ext($this->connection, $filter['dn']);
if ( $this->lastQuery === false ) {
$this->throwLdapException();
}
$this->rowCount = (int) $queryResult;
$this->rowCount = $this->lastQuery === false ? 0 : 1;
return $this;
}
@ -237,7 +261,9 @@ class LdapObject {
return;
}
throw new \Exception(sprintf('LDAP error #%s `%s`', $no, ldap_error($this->connection)));
ldap_get_option($this->connection, \LDAP_OPT_DIAGNOSTIC_MESSAGE, $err);
throw new \Exception(sprintf('LDAP error #%s `%s`. %s', $no, ldap_error($this->connection), $err));
}
public function closeCursor() : void {}

View File

@ -4,9 +4,12 @@ namespace Ulmus\Ldap\Entity;
use Ulmus\Ldap\Entity\Field\{ Datetime };
use Ulmus\Attribute\Property\Field;
use Ulmus\Attribute\Property\{Field, Filter, Relation, Virtual};
use Ulmus\EntityCollection;
use Ulmus\Ldap\Attribute\Obj\ObjectClass;
use Ulmus\Ldap\Repository;
use Ulmus\Repository\RepositoryInterface;
#[ObjectClass("group")]
class Group
@ -17,10 +20,10 @@ class Group
public string $samaccountname;
#[Field]
public array $members;
public string $name;
#[Field]
public string $name;
public string $description;
#[Field]
public string $canonicalName;
@ -28,6 +31,10 @@ class Group
#[Field(name: "objectGUID")]
public string $guid;
# You will need to override this method in your entity because an adapter needs to be set for the User entity.
#[Relation(type: Relation\RelationTypeEnum::oneToMany, key: 'dn', foreignKey: "memberOf", entity: User::class)]
public EntityCollection $members;
public function __toString() : string
{
return $this->name;

View File

@ -0,0 +1,21 @@
<?php
namespace Ulmus\Ldap\Entity;
use Ulmus\Ldap\Entity\Field\{ Datetime };
use Ulmus\Attribute\Property\{Field, Filter, Relation, Virtual};
use Ulmus\EntityCollection;
use Ulmus\Ldap\Attribute\Obj\ObjectClass;
use Ulmus\Ldap\Repository;
use Ulmus\Repository\RepositoryInterface;
#[ObjectClass("group")]
class GroupMember
{
use \Ulmus\Ldap\EntityTrait;
#[Field]
public string $member;
}

View File

@ -61,8 +61,8 @@ class User
#[Field]
public string $title;
##[Field(readonly:true)]
# public ? array $memberOf;
#[Field(readonly:true)]
public ? array $memberOf;
##[Field(readonly:true)]
#public ? array $proxyAddresses;
@ -149,12 +149,17 @@ class User
public function memberOfGroup() : array
{
$arr = array_map(fn($e) => explode('=', explode(',', $e)[0])[1], $this->memberOf);
$arr = array_map(fn($e) => explode('=', explode(',', $e)[0])[1], (array) $this->memberOf);
usort($arr, 'strcasecmp');
return $arr;
}
public function isMemberOf(string $name) : bool
{
return in_array(strtolower($name), array_map('strtolower', $this->memberOfGroup()), true);
}
public function brokenMigration() : bool
{
if ( empty($this->targetAddress) ) {

View File

@ -2,8 +2,16 @@
namespace Ulmus\Ldap;
use Ulmus\{Attribute\Property\Field, Ulmus, EventTrait, Query, Common\EntityResolver, Common\EntityField};
use Ulmus\{Attribute\Property\Field,
ConnectionAdapter,
Repository,
Ulmus,
EventTrait,
Query,
Common\EntityResolver,
Common\EntityField};
use Notes\Attribute\Ignore;
use Ulmus\Ldap\Annotation\Classes\{ ObjectClass, ObjectType, };
trait EntityTrait {
@ -18,25 +26,29 @@ trait EntityTrait {
#[Field]
public array $objectClass;
#[Ignore]
public static function resolveEntity() : EntityResolver
{
return Ulmus::resolveEntity(static::class);
}
public static function repository(string $alias = Repository::DEFAULT_ALIAS) : Repository
#[Ignore]
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository
{
return new Repository(static::class, $alias);
return Ulmus::repository(static::class, $alias, $adapter);
}
public static function field($name, ? string $alias = null) : EntityField
#[Ignore]
public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField
{
return new EntityField(static::class, $name, $alias ?: Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class));
return new EntityField(static::class, $name, false, Ulmus::resolveEntity(static::class));
}
public static function fields(array $fields, ? string $alias = null) : string
#[Ignore]
public static function fields(array $fields, null|string|false $alias = Repository::DEFAULT_ALIAS, string $separator = ', ') : string
{
return implode(', ', array_map(function($item) use ($alias){
return static::field($item, $alias);
return implode(', ', array_map(function($item) {
return static::field($item, false);
}, $fields));
}
}

7
src/Query/Add.php Normal file
View File

@ -0,0 +1,7 @@
<?php
namespace Ulmus\Ldap\Query;
class Add extends Dn {
}

View File

@ -2,16 +2,6 @@
namespace Ulmus\Ldap\Query;
class Delete extends \Ulmus\Query\Fragment {
class Delete extends Dn {
public int $order = -100;
public string $dn;
public function render() : array
{
return [
'dn' => $this->dn,
];
}
}

17
src/Query/Dn.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace Ulmus\Ldap\Query;
class Dn extends \Ulmus\Query\Fragment {
public int $order = -100;
public string $dn;
public function render() : array
{
return [
'dn' => $this->dn,
];
}
}

View File

@ -45,14 +45,20 @@ class Filter extends Fragment {
return $this;
}
public function render(bool $skipToken = false) : array
public function render(bool $raw = false) : array
{
if ($raw) {
return [
'filters' => $this->conditionList
];
}
$stack = [];
foreach ($this->conditionList ?? [] as $key => $item) {
if ( $item instanceof Filter ) {
if ( $item->conditionList ?? false ) {
$stack[] = ( $key !== 0 ? "{$item->condition} " : "" ) . "(" . implode('', $item->render($skipToken)) . ")";
$stack[] = ( $key !== 0 ? "{$item->condition} " : "" ) . "(" . implode('', $item->render()) . ")";
}
}
else {

View File

@ -2,16 +2,6 @@
namespace Ulmus\Ldap\Query;
class Update extends \Ulmus\Query\Fragment {
class Update extends Dn {
public int $order = -100;
public string $dn;
public function render() : array
{
return [
'dn' => $this->dn,
];
}
}

View File

@ -79,10 +79,34 @@ class QueryBuilder implements Ulmus\QueryBuilder\QueryBuilderInterface
return $this;
}
public function dn(string $dn) : self
{
if ( null === ( $update = $this->getFragment(Query\Dn::class) ) ) {
$update = new Query\Dn();
$this->push($update);
}
$update->dn = $dn;
return $this;
}
public function add(string $dn) : self
{
if ( null === ( $add = $this->getFragment(Query\Add::class) ) ) {
$add = new Query\Add();
$this->push($add);
}
$add->dn = $dn;
return $this;
}
public function update(string $dn) : self
{
if ( null === ( $update = $this->getFragment(Query\Update::class) ) ) {
$update = new Query\Update($this);
$update = new Query\Update();
$this->push($update);
}
@ -94,7 +118,7 @@ class QueryBuilder implements Ulmus\QueryBuilder\QueryBuilderInterface
public function delete(string $dn) : self
{
if ( null === ( $delete = $this->getFragment(Query\Delete::class) ) ) {
$delete = new Query\Delete($this);
$delete = new Query\Delete();
$this->push($delete);
}

View File

@ -2,8 +2,8 @@
namespace Ulmus\Ldap;
use Ulmus\Ldap\Annotation\Classes\{ ObjectClass, ObjectType };
use Ulmus\{EntityCollection, Ulmus, SearchRequest, Query};
use Ulmus\Ldap\Attribute\Obj\{ ObjectClass, ObjectType };
use Ulmus\{EntityCollection, Event\Repository\CollectionFromQueryItemInterface, Ulmus, SearchRequest, Ldap\Query};
use Ulmus\Annotation\Property\{ Where, Having, Relation, Join, WithJoin, Relation\Ignore as RelationIgnore };
use Ulmus\Common\EntityResolver;
@ -21,17 +21,52 @@ class Repository extends \Ulmus\Repository
$this->queryBuilder = new QueryBuilder();
}
public function saveAdd(object|array $entity, ? array $fieldsAndValue = null, bool $replace = false) : bool
{
if (is_array($entity)) {
$entity = (new $this->entityClass())->fromArray($entity);
}
$dataset = $entity->toArray();
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
if ( $primaryKeyDefinition === null ) {
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
}
$diff = $fieldsAndValue ?? $this->generateWritableDataset($entity);
if ( [] !== $diff ) {
$pkField = key($primaryKeyDefinition);
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
$this->where($pkFieldName, $dataset[$pkFieldName]);
$this->updateSqlQuery($diff)->finalizeQuery();
$ldapObject = $this->adapter->connector()->runAddQuery($this->queryBuilder->render(), array_merge($this->queryBuilder->values ?? [], $this->queryBuilder->parameters ?? []));
$this->queryBuilder->reset();
$entity->entityFillFromDataset($dataset, true);
return $ldapObject ? (bool) $ldapObject->rowCount : false;
}
return false;
}
protected function selectSqlQuery() : self
{
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
$this->select(array_keys($this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME)));
}
if ( null !== $objectClass = $this->entityResolver->getAnnotationFromClassname( ObjectClass::class, false ) ) {
if ( null !== $objectClass = $this->entityResolver->getAttributeImplementing( ObjectClass::class ) ) {
$this->where('objectclass', $objectClass->type);
}
if ( null !== $objectClass = $this->entityResolver->getAnnotationFromClassname( ObjectType::class, false ) ) {
if ( null !== $objectClass = $this->entityResolver->getAttributeImplementing( ObjectType::class ) ) {
$this->where('objecttype', $objectClass->type);
}
@ -53,6 +88,7 @@ class Repository extends \Ulmus\Repository
protected function updateSqlQuery(array $dataset) : self
{
# Backward compatibility here !
$condition = array_pop($this->queryBuilder->where->conditionList);
if ($condition[0] === 'dn') {
@ -130,6 +166,13 @@ class Repository extends \Ulmus\Repository
return $this;
}
public function add(string $dn) : self
{
$this->queryBuilder->add($dn);
return $this;
}
public function escapeValue($identifier) : string
{
return is_object($identifier) ? $identifier : $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\Ldap::IDENTIFIER_FILTER);
@ -157,4 +200,20 @@ class Repository extends \Ulmus\Repository
return parent::filterServerRequest($searchRequest, false);
}
public function collectionFromQuery(? string $entityClass = null) : EntityCollection
{
$this->eventRegister(new class implements CollectionFromQueryItemInterface {
public function execute(array &$data): array
{
# If user is member of only one group, it is received as a single string.
$data['memberOf'] = isset($data['memberOf']) ? (array) $data['memberOf'] : null;
return $data;
}
});
return parent::collectionFromQuery($entityClass);
}
}

View File

@ -135,4 +135,12 @@ trait ConditionTrait
return $this;
}
public function dn(string $value) : Repository
{
$this->queryBuilder->dn($value);
return $this;
}
}