début migration ldap
This commit is contained in:
parent
6219c47452
commit
65fbe881f4
25
cli/LdapApplication.php
Normal file
25
cli/LdapApplication.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
namespace cli;
|
||||||
|
|
||||||
|
use nulib\app\cli\Application;
|
||||||
|
|
||||||
|
abstract class LdapApplication extends Application {
|
||||||
|
use TLdapApplication;
|
||||||
|
|
||||||
|
const LOAD_PARAMS = true;
|
||||||
|
|
||||||
|
const LDAP_SECTION = [
|
||||||
|
"title" => "CONNEXION LDAP",
|
||||||
|
["-C", "--config", "args" => "file"],
|
||||||
|
["-H", "--uri", "args" => 1],
|
||||||
|
["-D", "--binddn", "args" => 1],
|
||||||
|
["-w", "--password", "args" => 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
const ARGS = [
|
||||||
|
"sections" => [
|
||||||
|
self::VERBOSITY_SECTION,
|
||||||
|
self::LDAP_SECTION,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
35
cli/LdapDeleteApp.php
Normal file
35
cli/LdapDeleteApp.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
namespace cli;
|
||||||
|
|
||||||
|
use cli\LdapApplication;
|
||||||
|
use nulib\output\msg;
|
||||||
|
use nur\ldap\LdapSearch;
|
||||||
|
use nur\ldap\LdapWalker;
|
||||||
|
|
||||||
|
class LdapDeleteApp extends LdapApplication {
|
||||||
|
const ARGS = [
|
||||||
|
"merge" => parent::ARGS,
|
||||||
|
["-s", "--scope", "args" => 1],
|
||||||
|
["-b", "--searchbase", "args" => 1],
|
||||||
|
["-B", "--searchbase-exact", "args" => 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $scope;
|
||||||
|
protected $searchbase, $searchbaseExact;
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
$conn = $this->getConn();
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
LdapSearch::parse_args($params, $this->args
|
||||||
|
, $this->searchbase, $this->searchbaseExact
|
||||||
|
, $this->scope);
|
||||||
|
/** @var LdapWalker $lo */
|
||||||
|
$lo = $conn->search(null, $params);
|
||||||
|
while ($lo->next($first)) {
|
||||||
|
msg::action("Suppression $lo[dn]");
|
||||||
|
$lo->delete();
|
||||||
|
msg::asuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
cli/LdapGetInfosApp.php
Normal file
29
cli/LdapGetInfosApp.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
namespace cli;
|
||||||
|
|
||||||
|
use cli\LdapApplication;
|
||||||
|
|
||||||
|
class LdapGetInfosApp extends LdapApplication {
|
||||||
|
const ARGS = [
|
||||||
|
"merge" => parent::ARGS,
|
||||||
|
["-o", "--output", "args" => 1],
|
||||||
|
["-f", "--overwrite-shared", "value" => true],
|
||||||
|
["-u", "--update", "value" => true, "help" => "Mettre à jour le fichier de connexion (nécessite --config et implique --output et --overwrite-shared)"]
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $output, $overwriteShared = false;
|
||||||
|
protected $update = false;
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
$conn = $this->getConn();
|
||||||
|
if ($this->update) {
|
||||||
|
$config = $this->config;
|
||||||
|
if ($config === null) {
|
||||||
|
self::die("Vous devez spécifier la configuration à mettre à jour");
|
||||||
|
}
|
||||||
|
$this->output = $config;
|
||||||
|
$this->overwriteShared = true;
|
||||||
|
}
|
||||||
|
$conn->saveConfig($this->output, $this->overwriteShared);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
cli/LdapSearchApp.php
Normal file
62
cli/LdapSearchApp.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
namespace cli;
|
||||||
|
|
||||||
|
use cli\LdapApplication;
|
||||||
|
use nur\b\IllegalAccessException;
|
||||||
|
use nur\ldap\io\LdapWriter;
|
||||||
|
use nur\ldap\io\LdifWriter;
|
||||||
|
use nur\ldap\io\YamlWriter;
|
||||||
|
use nur\ldap\LdapSearch;
|
||||||
|
use nur\ldap\LdapWalker;
|
||||||
|
|
||||||
|
class LdapSearchApp extends LdapApplication {
|
||||||
|
const ARGS = [
|
||||||
|
"merge" => parent::ARGS,
|
||||||
|
["-s", "--scope", "args" => 1],
|
||||||
|
["-b", "--searchbase", "args" => 1],
|
||||||
|
["-B", "--searchbase-exact", "args" => 1],
|
||||||
|
["-o", "--output", "args" => "file"],
|
||||||
|
["group",
|
||||||
|
["-F", "--format", "args" => 1],
|
||||||
|
["--ldif", "dest" => "format", "value" => "ldif"],
|
||||||
|
["--yaml", "dest" => "format", "value" => "yaml"],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $scope;
|
||||||
|
protected $searchbase, $searchbaseExact;
|
||||||
|
protected $output;
|
||||||
|
protected $format = "ldif";
|
||||||
|
|
||||||
|
function getWriter(): LdapWriter {
|
||||||
|
switch ($this->format) {
|
||||||
|
case "ldif":
|
||||||
|
case "l":
|
||||||
|
return new LdifWriter($this->output);
|
||||||
|
case "yaml":
|
||||||
|
case "y":
|
||||||
|
return new YamlWriter($this->output);
|
||||||
|
}
|
||||||
|
throw IllegalAccessException::unexpected_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
$conn = $this->getConn();
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
LdapSearch::parse_args($params, $this->args
|
||||||
|
, $this->searchbase, $this->searchbaseExact
|
||||||
|
, $this->scope);
|
||||||
|
/** @var LdapWalker $lo */
|
||||||
|
$lo = $conn->search(null, $params);
|
||||||
|
$writer = null;
|
||||||
|
while ($lo->next($first)) {
|
||||||
|
if ($first) {
|
||||||
|
$first = false;
|
||||||
|
$writer = $this->getWriter();
|
||||||
|
}
|
||||||
|
$writer->write($lo);
|
||||||
|
}
|
||||||
|
if ($writer !== null) $writer->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
34
cli/TLdapApplication.php
Normal file
34
cli/TLdapApplication.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
namespace cli;
|
||||||
|
|
||||||
|
use nur\A;
|
||||||
|
use nur\ldap\LdapConn;
|
||||||
|
|
||||||
|
trait TLdapApplication {
|
||||||
|
protected $config;
|
||||||
|
protected $uri, $binddn, $password;
|
||||||
|
|
||||||
|
protected function fixConfig(?string &$config): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConn(?array $supplParams=null): LdapConn {
|
||||||
|
$config = $this->config;
|
||||||
|
$this->fixConfig($config);
|
||||||
|
$loadParams = static::LOAD_PARAMS;
|
||||||
|
$autoconnect = $autofillParams = null;
|
||||||
|
if ($config === null) {
|
||||||
|
$params = [];
|
||||||
|
} else {
|
||||||
|
$params = require $config;
|
||||||
|
if (!$loadParams) $autoconnect = $autofillParams = false;
|
||||||
|
}
|
||||||
|
A::merge($params, A::filter_n([
|
||||||
|
"uri" => $this->uri,
|
||||||
|
"binddn" => $this->binddn,
|
||||||
|
"password" => $this->password,
|
||||||
|
"autoconnect" => $autoconnect,
|
||||||
|
"autofill_params" => $autofillParams,
|
||||||
|
]), $supplParams);
|
||||||
|
return new LdapConn($params);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/ldap/CompositeAttr.php
Normal file
61
src/ldap/CompositeAttr.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
use nulib\ldap\syntaxes\CompositeSyntax;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CompositeAttr: une liste de valeurs composites
|
||||||
|
*/
|
||||||
|
class CompositeAttr extends LdapAttr {
|
||||||
|
function reset(?array &$values): self {
|
||||||
|
if ($values !== null) {
|
||||||
|
/** @var CompositeSyntax $syntax */
|
||||||
|
$syntax = $this->syntax;
|
||||||
|
$tmp = [];
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$cvalue = $syntax->ldap2php($value);
|
||||||
|
$key = $cvalue->getKey();
|
||||||
|
$value = $cvalue->formatLdap();
|
||||||
|
$tmp[$key] = $value;
|
||||||
|
}
|
||||||
|
$values = $tmp;
|
||||||
|
}
|
||||||
|
$this->data =& $values;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function add($value, bool $unique=true, bool $strict=false): self {
|
||||||
|
/** @var CompositeSyntax $syntax */
|
||||||
|
$syntax = $this->syntax;
|
||||||
|
$value = A::first($syntax->ensureArray($value));
|
||||||
|
$cvalue = $syntax->ensureComposite($value);
|
||||||
|
if ($cvalue !== null) {
|
||||||
|
$key = $cvalue->getKey();
|
||||||
|
$value = $cvalue->formatLdap();
|
||||||
|
$this->data[$key] = $value;
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function del($value, int $maxCount=-1, bool $strict=false): self {
|
||||||
|
if ($value !== null && $this->data !== null) {
|
||||||
|
/** @var CompositeSyntax $syntax */
|
||||||
|
$syntax = $this->syntax;
|
||||||
|
$value = A::first($syntax->ensureArray($value));
|
||||||
|
$cvalue = $syntax->ensureComposite($value);
|
||||||
|
if ($cvalue !== null) {
|
||||||
|
$key = $cvalue->getKey();
|
||||||
|
unset($this->data[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ins(int $index, $value): self {
|
||||||
|
throw IllegalAccessException::not_allowed("composite attrs don't use indexes");
|
||||||
|
}
|
||||||
|
function unset(int $index): self {
|
||||||
|
throw IllegalAccessException::not_allowed("composite attrs don't use indexes");
|
||||||
|
}
|
||||||
|
}
|
||||||
153
src/ldap/CompositeValue.php
Normal file
153
src/ldap/CompositeValue.php
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CompositeValue: une valeur composite
|
||||||
|
*/
|
||||||
|
abstract class CompositeValue extends BaseArray {
|
||||||
|
/** @var array schéma des champs de la valeur composite */
|
||||||
|
const SCHEMA = null;
|
||||||
|
|
||||||
|
/** @var array syntaxes associées aux champs */
|
||||||
|
const SYNTAXES = null;
|
||||||
|
|
||||||
|
/** @var array liste et ordre des éléments obligatoires */
|
||||||
|
const MANDATORY_KEYS = null;
|
||||||
|
|
||||||
|
/** @var array liste et ordre des éléments facultatifs connus */
|
||||||
|
const OPTIONAL_KEYS = null;
|
||||||
|
|
||||||
|
/** @var array liste des clés qui identifient cet objet */
|
||||||
|
const KEY_KEYS = null;
|
||||||
|
|
||||||
|
static function compute_keys(array $values): string {
|
||||||
|
$keys = static::KEY_KEYS;
|
||||||
|
if ($keys === null) $keys = static::MANDATORY_KEYS;
|
||||||
|
if ($keys === null) $keys = array_keys($values);
|
||||||
|
$parts = [];
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$parts[] = A::get($values, $key);
|
||||||
|
}
|
||||||
|
return implode("-", $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $ldapKeys, $keys, $optionalKeys;
|
||||||
|
|
||||||
|
protected $syntaxes;
|
||||||
|
|
||||||
|
/** initialiser l'objet */
|
||||||
|
function setup(LdapConn $conn): self {
|
||||||
|
$ldapKeys = [];
|
||||||
|
$keys = [];
|
||||||
|
$mandatoryKeys = ValueException::check_nn(static::MANDATORY_KEYS
|
||||||
|
, "Vous devez définir MANDATORY_KEYS");
|
||||||
|
$index = 0;
|
||||||
|
foreach ($mandatoryKeys as $key => $ldapKey) {
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
$key = $ldapKey;
|
||||||
|
}
|
||||||
|
$ldapKeys[$key] = $ldapKey;
|
||||||
|
$keys[$ldapKey] = $key;
|
||||||
|
}
|
||||||
|
$optionalKeys = [];
|
||||||
|
$index = 0;
|
||||||
|
foreach (A::with(static::OPTIONAL_KEYS) as $key => $ldapKey) {
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
$key = $ldapKey;
|
||||||
|
}
|
||||||
|
$ldapKeys[$key] = $ldapKey;
|
||||||
|
$keys[$ldapKey] = $key;
|
||||||
|
$optionalKeys[] = $key;
|
||||||
|
}
|
||||||
|
$schemaKeys = A::keys(static::SCHEMA);
|
||||||
|
foreach ($schemaKeys as $key) {
|
||||||
|
if (!in_array($key, $keys)) {
|
||||||
|
$ldapKeys[$key] = $key;
|
||||||
|
$keys[$key] = $key;
|
||||||
|
$optionalKeys[] = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->ldapKeys = $ldapKeys;
|
||||||
|
$this->keys = $keys;
|
||||||
|
$this->optionalKeys = $optionalKeys;
|
||||||
|
##
|
||||||
|
$syntaxClasses = static::SYNTAXES;
|
||||||
|
if ($syntaxClasses !== null) {
|
||||||
|
$syntaxes = [];
|
||||||
|
foreach ($schemaKeys as $key) {
|
||||||
|
$class = A::get($syntaxClasses, $key);
|
||||||
|
if ($class !== null) {
|
||||||
|
$syntaxes[$key] = $conn->getSyntax($class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->syntaxes = $syntaxes;
|
||||||
|
}
|
||||||
|
##
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function has($key): bool { return $this->_has($key); }
|
||||||
|
function &get($key, $default=null) { return $this->_get($key, $default); }
|
||||||
|
function set($key, $value): self { return $this->_set($key, $value); }
|
||||||
|
function add($value): self { return $this->_set(null, $value); }
|
||||||
|
function del($key): self { return $this->_del($key); }
|
||||||
|
|
||||||
|
/** obtenir la clé qui identifie cet objet */
|
||||||
|
function getKey(): string {
|
||||||
|
return self::compute_keys($this->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** initialiser cet objet avec une valeur LDAP */
|
||||||
|
function parseLdap(string $value): self {
|
||||||
|
if (!preg_match_all('/\[.*?]/', $value, $ms)) {
|
||||||
|
throw ValueException::invalid_value($value, "composite value");
|
||||||
|
}
|
||||||
|
$this->data = [];
|
||||||
|
foreach ($ms[0] as $nameValue) {
|
||||||
|
if (preg_match('/\[(.*?)=(.*)]/', $nameValue, $ms)) {
|
||||||
|
$ldapKey = names::ldap_unescape($ms[1]);
|
||||||
|
$key = A::get($this->keys, $ldapKey, $ldapKey);
|
||||||
|
$value = names::ldap_unescape($ms[2]);
|
||||||
|
/** @var AbstractSyntax $syntax */
|
||||||
|
$syntax = A::get($this->syntaxes, $key);
|
||||||
|
if ($syntax !== null) $value = $syntax->ldap2php($value);
|
||||||
|
$this->data[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** retourner cette valeur au format LDAP */
|
||||||
|
function formatLdap(): string {
|
||||||
|
$optionalKeys = $this->optionalKeys;
|
||||||
|
$parts = [];
|
||||||
|
foreach ($this->ldapKeys as $key => $ldapKey) {
|
||||||
|
$value = A::get($this->data, $key);
|
||||||
|
if ($value === null && in_array($key, $optionalKeys)) continue;
|
||||||
|
/** @var AbstractSyntax $syntax */
|
||||||
|
$syntax = A::get($this->syntaxes, $key);
|
||||||
|
if ($syntax !== null) $value = $syntax->php2ldap($value);
|
||||||
|
$ldapKey = ldap_escape($ldapKey, 0, LDAP_ESCAPE_FILTER);
|
||||||
|
$value = ldap_escape($value, 0, LDAP_ESCAPE_FILTER);
|
||||||
|
$parts[] = "[$ldapKey=$value]";
|
||||||
|
}
|
||||||
|
return implode("", $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(?array $values): CompositeValue {
|
||||||
|
$md = Metadata::with(static::SCHEMA);
|
||||||
|
$md->ensureSchema($values);
|
||||||
|
$this->data = $values;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
static function _AUTOGEN_PROPERTIES(): array {
|
||||||
|
return cvalues::autogen_properties(static::SCHEMA);
|
||||||
|
}
|
||||||
|
## rajouter ceci dans les classes dérivées
|
||||||
|
#const _AUTOGEN_PROPERTIES = [[self::class, "_AUTOGEN_PROPERTIES"]];
|
||||||
|
}
|
||||||
9
src/ldap/ILdapWalker.php
Normal file
9
src/ldap/ILdapWalker.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
interface ILdapWalker extends ICloseable {
|
||||||
|
function resetSearch(LdapSearch $search): ILdapWalker;
|
||||||
|
|
||||||
|
function next(): bool;
|
||||||
|
}
|
||||||
33
src/ldap/IObjectWorkflow.php
Normal file
33
src/ldap/IObjectWorkflow.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface IObjectWorkflow: un objet permettant de créer et/ou mettre à jour
|
||||||
|
* un objet LDAP dans le cadre d'une synchronisation
|
||||||
|
*/
|
||||||
|
interface IObjectWorkflow {
|
||||||
|
/** retourner le nom du workflox */
|
||||||
|
function getWorkflowName(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* synchroniser les données spécifiées vers l'objet correspndant, en le créant
|
||||||
|
* si nécessaire.
|
||||||
|
*
|
||||||
|
* $updated=true si l'objet a été créé ou mis à jour, false sinon
|
||||||
|
*/
|
||||||
|
function createOrUpdate(array $data, ?array $params=null, ?bool &$updated=null): ?LdapObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* modifier uniquement le mot de passe de l'objet correspondant
|
||||||
|
*
|
||||||
|
* @return bool true si l'objet correspondant a été trouvé et qu'il a été mis
|
||||||
|
* à jour
|
||||||
|
*/
|
||||||
|
function updatePassword(array $data, string $password): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* supprimer l'objet correspondant. retourner true si l'objet a été supprimé,
|
||||||
|
* false s'il n'existait pas
|
||||||
|
*/
|
||||||
|
function delete(array $data, ?array $params=null): bool;
|
||||||
|
}
|
||||||
221
src/ldap/LdapAttr.php
Normal file
221
src/ldap/LdapAttr.php
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Countable;
|
||||||
|
use Iterator;
|
||||||
|
|
||||||
|
class LdapAttr implements ArrayAccess, Countable, Iterator {
|
||||||
|
use TIterableArray;
|
||||||
|
|
||||||
|
const MONOVALUED = 1, BINARY = 2, ORDERED = 4, NOT_HUMAN_READABLE = 8;
|
||||||
|
|
||||||
|
function __construct(string $name, ?array &$values, ?AbstractSyntax $syntax, ?int $flags) {
|
||||||
|
$this->name = $name;
|
||||||
|
$this->syntax = $syntax;
|
||||||
|
$this->flags = $flags;
|
||||||
|
$this->reset($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
function name(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var ?array */
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
function reset(?array &$values): self {
|
||||||
|
$this->data =& $values;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var AbstractSyntax */
|
||||||
|
protected $syntax;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $flags;
|
||||||
|
|
||||||
|
function isMonovalued(): bool {
|
||||||
|
return $this->flags !== null && $this->flags & self::MONOVALUED != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBinary(): bool {
|
||||||
|
return $this->flags !== null && $this->flags & self::BINARY != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOrdered(): bool {
|
||||||
|
return $this->flags !== null && $this->flags & self::ORDERED != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotHumanReadable(): bool {
|
||||||
|
return $this->flags !== null && $this->flags & self::NOT_HUMAN_READABLE != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fromLdap($value) {
|
||||||
|
$syntax = $this->syntax;
|
||||||
|
if ($syntax !== null) {
|
||||||
|
if ($this->isMonovalued()) $value = $syntax->fromMonovaluedLdap($value);
|
||||||
|
else $value = $syntax->fromMultivaluedLdap($value);
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
protected function fromPhp($value): ?iterable {
|
||||||
|
$syntax = $this->syntax;
|
||||||
|
if ($syntax !== null) $value = $syntax->fromPhp($value);
|
||||||
|
else A::ensure_narray($value);
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** retourner un tableau si multivalué, une valeur scalaire si monovalué */
|
||||||
|
function get($index=null) {
|
||||||
|
$value = $this->fromLdap($this->data);
|
||||||
|
if ($index !== null && is_array($value)) {
|
||||||
|
$value = array_key_exists($index, $value)? $value[$index]: null;
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retourner toutes les valeurs
|
||||||
|
*
|
||||||
|
* @param string $checkPrefixDel ne retourner que les valeurs qui commencent
|
||||||
|
* par ce préfixe ET enlever le préfixe
|
||||||
|
*/
|
||||||
|
function all(?string $checkPrefixDel=null): ?array {
|
||||||
|
if ($this->syntax === null) $values = $this->data;
|
||||||
|
else $values = $this->syntax->fromMultivaluedLdap($this->data);
|
||||||
|
if ($checkPrefixDel !== null && $values !== null) {
|
||||||
|
$filtered = [];
|
||||||
|
foreach ($values as $value) {
|
||||||
|
if (str::del_prefix($value, $checkPrefixDel)) {
|
||||||
|
$filtered[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$values = $filtered;
|
||||||
|
}
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** retourner la première valeur */
|
||||||
|
function first(?string $checkPrefixDel=null) {
|
||||||
|
return A::first($this->all($checkPrefixDel));
|
||||||
|
}
|
||||||
|
|
||||||
|
function set($values, bool $unlessNn=false): self {
|
||||||
|
if ($values instanceof LdapAttr) $values = $values->array();
|
||||||
|
if (!$unlessNn || $this->data === null) {
|
||||||
|
$this->data = $this->fromPhp($values);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function in_array(string $needle, array $haystack, bool $strict, ?int &$index=null): bool {
|
||||||
|
if (!$strict) $needle = strtolower($needle);
|
||||||
|
foreach ($haystack as $index => $hay) {
|
||||||
|
if ($strict && $hay === $needle) return true;
|
||||||
|
if (!$strict && strtolower($hay) == $needle) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** vérifier si la valeur spécifiée figure dans l'attribut */
|
||||||
|
function contains($value, bool $strict=false): bool {
|
||||||
|
$value = A::first($this->fromPhp($value));
|
||||||
|
if ($value === null || $this->data === null) return false;
|
||||||
|
return self::in_array($value, $this->data, $strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* l'unicité est calculée ainsi:
|
||||||
|
* - en mode strict, ce doit être une égalité parfaite
|
||||||
|
* - en mode non strict, la comparaison est insensible à la casse
|
||||||
|
* XXX à terme, implémenter la comparaison en fonction de la syntaxe
|
||||||
|
*/
|
||||||
|
function add($value, bool $unique=true, bool $strict=false): self {
|
||||||
|
$value = A::first($this->fromPhp($value));
|
||||||
|
if ($value !== null) {
|
||||||
|
if (!$unique || $this->data === null ||
|
||||||
|
!self::in_array($value, $this->data, $strict)) {
|
||||||
|
$this->data[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAll(?iterable $values): self {
|
||||||
|
if ($values !== null) {
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$this->add($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function del($value, int $maxCount=-1, bool $strict=false): self {
|
||||||
|
if ($value !== null && $this->data !== null) {
|
||||||
|
$value = A::first($this->fromPhp($value));
|
||||||
|
$rekey = false;
|
||||||
|
while ($maxCount != 0) {
|
||||||
|
if (!self::in_array($value, $this->data, $strict, $index)) break;
|
||||||
|
unset($this->data[$index]);
|
||||||
|
$rekey = true;
|
||||||
|
if ($maxCount > 0) $maxCount--;
|
||||||
|
}
|
||||||
|
if ($rekey) $this->data = array_values($this->data);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ins(int $index, $value): self {
|
||||||
|
$value = A::first($this->fromPhp($value));
|
||||||
|
if ($value !== null) {
|
||||||
|
A::insert($this->data, $index, $value);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unset(int $index): self {
|
||||||
|
if ($this->data !== null) {
|
||||||
|
$count = count($this->array());
|
||||||
|
if ($count > 0 && $index < 0) {
|
||||||
|
while ($index < 0) $index += $count;
|
||||||
|
}
|
||||||
|
unset($this->data[$index]);
|
||||||
|
$this->data = array_values($this->data);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function key() { return $this->_key(); }
|
||||||
|
function current() {
|
||||||
|
$current = $this->_current();
|
||||||
|
$syntax = $this->syntax;
|
||||||
|
if ($syntax !== null) $current = $syntax->ldap2php($current);
|
||||||
|
return $current;
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# données au format LDAP
|
||||||
|
|
||||||
|
function __toString() {
|
||||||
|
return implode("\n", $this->data);
|
||||||
|
}
|
||||||
|
/** retourner les données au format LDAP */
|
||||||
|
function &array(): ?array { return $this->data; }
|
||||||
|
function count(): int { return count($this->data); }
|
||||||
|
function keys(): array { return array_keys($this->data); }
|
||||||
|
function offsetExists($key) {
|
||||||
|
return $this->data !== null && array_key_exists($key, $this->data);
|
||||||
|
}
|
||||||
|
function offsetGet($key) { return array_key_exists($key, $this->data)? $this->data[$key]: null; }
|
||||||
|
function offsetSet($key, $value) { $this->data[$key] = $value; }
|
||||||
|
function offsetUnset($key) { unset($this->data[$key]); }
|
||||||
|
|
||||||
|
function __isset($key) { return $this->offsetExists($key); }
|
||||||
|
function __get($key) { return $this->offsetGet($key); }
|
||||||
|
function __set($key, $value) { $this->offsetSet($key, $value); }
|
||||||
|
function __unset($key) { $this->offsetUnset($key); }
|
||||||
|
}
|
||||||
434
src/ldap/LdapConn.php
Normal file
434
src/ldap/LdapConn.php
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LdapConn: une connexion à un serveur LDAP
|
||||||
|
*/
|
||||||
|
class LdapConn extends Parametrable implements ICloseable {
|
||||||
|
use Tparametrable;
|
||||||
|
|
||||||
|
const URI = "ldap://localhost:389";
|
||||||
|
const BINDDN = null;
|
||||||
|
const PASSWORD = null;
|
||||||
|
const CONTROLS = null;
|
||||||
|
|
||||||
|
const PARAMETRABLE_PARAMS_SCHEMA = [
|
||||||
|
"uri" => ["string", null, "URI du serveur LDAP"],
|
||||||
|
"binddn" => ["?string", null, "DN avec lequel se lier"],
|
||||||
|
"password" => ["?string", null, "mot de passe"],
|
||||||
|
"controls" => ["array", [], "contrôle de connexion"],
|
||||||
|
"protocol" => ["int", 3, "version du protocole"],
|
||||||
|
"autoconnect" => ["bool", true, "faut-il se connecter dès la création de l'objet?"],
|
||||||
|
# paramètres par défaut
|
||||||
|
"suffix" => ["?string", null, "DN de base du serveur"],
|
||||||
|
"domain" => ["?string", null, "domaine DNS de l'établissement"],
|
||||||
|
"etab" => ["?string", null, "code de l'établissement"],
|
||||||
|
"autofill_params" => ["bool", true, "faut-il calculer automatiquement les paramètres par défaut?"],
|
||||||
|
# configuration du serveur
|
||||||
|
"root_dse" => ["?array", null, "configuration du serveur"],
|
||||||
|
"ldap_syntaxes" => ["?array", null, "définition des syntaxes"],
|
||||||
|
"attribute_types" => ["?array", null, "définition des attributs"],
|
||||||
|
"object_classes" => ["?array", null, "définition des classes d'objets"],
|
||||||
|
];
|
||||||
|
|
||||||
|
function __construct(?array $params=null) {
|
||||||
|
self::set_parametrable_params_defaults($params, [
|
||||||
|
"uri" => static::URI,
|
||||||
|
"binddn" => static::BINDDN,
|
||||||
|
"password" => static::PASSWORD,
|
||||||
|
"controls" => static::CONTROLS,
|
||||||
|
]);
|
||||||
|
parent::__construct($params);
|
||||||
|
if ($this->ppAutoconnect) $this->connect();
|
||||||
|
if ($this->ppAutofillParams) $this->fillParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $ppUri;
|
||||||
|
|
||||||
|
/** @var ?string */
|
||||||
|
protected $ppBinddn;
|
||||||
|
|
||||||
|
/** @var ?string */
|
||||||
|
protected $ppPassword;
|
||||||
|
|
||||||
|
/** @var ?array */
|
||||||
|
protected $ppControls;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $ppProtocol;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
protected $ppAutoconnect;
|
||||||
|
|
||||||
|
/** @var ?string */
|
||||||
|
protected $ppSuffix;
|
||||||
|
|
||||||
|
function getSuffix(): ?string {
|
||||||
|
return $this->ppSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var ?string */
|
||||||
|
protected $ppDomain;
|
||||||
|
|
||||||
|
function getDomain(): ?string {
|
||||||
|
return $this->ppDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var ?string */
|
||||||
|
protected $ppEtab;
|
||||||
|
|
||||||
|
function getEtab(bool $withPrefix=true): ?string {
|
||||||
|
$etab = $this->ppEtab;
|
||||||
|
if (!$withPrefix) {
|
||||||
|
$etab = preg_replace('/^\{[^}]+}/', "", $etab);
|
||||||
|
}
|
||||||
|
return $etab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
protected $ppAutofillParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $conn
|
||||||
|
* @throws LdapException
|
||||||
|
*/
|
||||||
|
function tryConnect(?string $binddn=null, ?string $password=null, ?array $controls=null, $conn=null) {
|
||||||
|
if ($conn === null) {
|
||||||
|
$uri = $this->ppUri;
|
||||||
|
$conn = LdapException::check("connect $uri", null
|
||||||
|
, ldap_connect($uri));
|
||||||
|
$procotol = $this->ppProtocol;
|
||||||
|
LdapException::check("set_option protocol=$procotol", $conn
|
||||||
|
, ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, $procotol));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($binddn === null) $binddn = $this->ppBinddn;
|
||||||
|
if ($password === null) $password = $this->ppPassword;
|
||||||
|
if ($controls === null) $controls = $this->ppControls;
|
||||||
|
$operation = "bind $binddn";
|
||||||
|
$r = LdapException::check($operation, $conn
|
||||||
|
, ldap_bind_ext($conn, $binddn, $password, $controls));
|
||||||
|
LdapException::check_result($operation, $conn, $r);
|
||||||
|
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var resource */
|
||||||
|
protected $conn;
|
||||||
|
|
||||||
|
function connect(?string $binddn=null, ?string $password=null, ?array $controls=null): void {
|
||||||
|
$this->conn = $this->tryConnect($binddn, $password, $controls, $this->conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* se reconnecter, mais seulement s'il y a une erreur sur la connection
|
||||||
|
*
|
||||||
|
* @return true si la reconnexion a effectivement eu lieu
|
||||||
|
*/
|
||||||
|
function reconnect(bool $force=false): bool {
|
||||||
|
if (!$force) {
|
||||||
|
try {
|
||||||
|
$this->_search(null, [
|
||||||
|
"attrs" => ["objectClass"],
|
||||||
|
"scope" => "base",
|
||||||
|
"suffix" => "",
|
||||||
|
])->first();
|
||||||
|
} catch (LdapException $e) {
|
||||||
|
$force = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($force) $this->connect();
|
||||||
|
return $force;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return resource */
|
||||||
|
protected function conn() {
|
||||||
|
if ($this->conn === null) $this->connect();
|
||||||
|
return $this->conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** retourner un objet vide permettant de construire un objet depuis zéro */
|
||||||
|
function empty(?LdapObject $object=null): LdapObject {
|
||||||
|
if ($object === null) $object = new LdapObject();
|
||||||
|
return $object->reset(null, null, [], $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _search(?string $searchbase=null, $params=null): LdapSearch {
|
||||||
|
LdapSearch::search_md()->ensureSchema($params);
|
||||||
|
A::replace_n($params, "searchbase", $searchbase);
|
||||||
|
A::replace_n($params, "suffix", $this->ppSuffix);
|
||||||
|
return new LdapSearch($this->conn(), $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function search(?string $searchbase=null, $params=null, ?ILdapWalker $walker=null): ILdapWalker {
|
||||||
|
if ($walker === null) {
|
||||||
|
$walker = new LdapWalker($this);
|
||||||
|
} else {
|
||||||
|
$walker->close();
|
||||||
|
$walker->reset(null, null, null, $this);
|
||||||
|
}
|
||||||
|
return $walker->resetSearch($this->_search($searchbase, $params));
|
||||||
|
}
|
||||||
|
|
||||||
|
function first(?string $searchbase=null, $params=null, ?LdapObject $object=null): ?LdapObject {
|
||||||
|
$search = $this->_search($searchbase, $params);
|
||||||
|
$entry = $search->first($dn);
|
||||||
|
if ($entry === null) return null;
|
||||||
|
else return $this->empty($object)->load($dn, $entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
function read(string $dn, ?array $params=null, ?LdapObject $object=null): ?LdapObject {
|
||||||
|
A::merge($params, [
|
||||||
|
"scope" => "base",
|
||||||
|
"suffix" => $dn,
|
||||||
|
]);
|
||||||
|
return $this->first(null, $params, $object);
|
||||||
|
}
|
||||||
|
|
||||||
|
function add(string $dn, array $attrs, $params=null): void {
|
||||||
|
ldap::add($this->conn(), $dn, $attrs, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function modify(string $dn, array $modattrs, $params=null): void {
|
||||||
|
ldap::modify($this->conn(), $dn, $modattrs, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rename(string $dn, string $newRdn, $params=null): string {
|
||||||
|
if (ldap::prepare_rename($dn, $newRdn, $params)) {
|
||||||
|
return ldap::rename($this->conn(), $dn, $newRdn, $params);
|
||||||
|
} else {
|
||||||
|
# renommage non nécessaire
|
||||||
|
return $dn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete(string $dn, $params=null): void {
|
||||||
|
ldap::delete($this->conn(), $dn, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
if ($this->conn !== null) {
|
||||||
|
ldap_unbind($this->conn);
|
||||||
|
$this->conn = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Si $rdn se termine par le suffixe, le retourner tel quel, sinon rajouter
|
||||||
|
* le suffixe si ce n'est pas un DN qui est dans un des contextes valides
|
||||||
|
*/
|
||||||
|
function ensureDn(string $rdn): string {
|
||||||
|
$suffix = $this->ppSuffix;
|
||||||
|
if (names::have_suffix($rdn, $suffix)) return $rdn;
|
||||||
|
$rootDse = $this->getRootDseForContexts();
|
||||||
|
$namingContexts = $rootDse->get("namingContexts", []);
|
||||||
|
foreach ($namingContexts as $namingContext) {
|
||||||
|
if (names::have_suffix($rdn, $suffix)) return $rdn;
|
||||||
|
}
|
||||||
|
return names::join($rdn, $suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corriger un label de la forme {UAI::XXX} en insérant le code de
|
||||||
|
* l'établissement
|
||||||
|
*/
|
||||||
|
function fixLabel(string $labeledValue): string {
|
||||||
|
if (!preg_match('/^(\{[A-Za-z0-9:._-]+})(.*)/', $labeledValue, $ms)) {
|
||||||
|
return $labeledValue;
|
||||||
|
}
|
||||||
|
$label = $ms[1];
|
||||||
|
$value = $ms[2];
|
||||||
|
if (str::del_prefix($label, "{UAI::")) {
|
||||||
|
$label = "{UAI:".$this->getEtab(false).":$label";
|
||||||
|
} elseif (str::del_prefix($label, "{UAI:}")) {
|
||||||
|
$label = "{UAI:".$this->getEtab(false)."}$label";
|
||||||
|
}
|
||||||
|
return $label.$value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
/** @var SchemaManager */
|
||||||
|
protected $scheman;
|
||||||
|
|
||||||
|
protected function scheman(): SchemaManager {
|
||||||
|
if ($this->scheman === null) {
|
||||||
|
$this->scheman = new SchemaManager($this);
|
||||||
|
}
|
||||||
|
return $this->scheman;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSyntax($class): AbstractSyntax {
|
||||||
|
$syntax = $this->scheman()->getSyntax($class);
|
||||||
|
$syntax->initConn($this);
|
||||||
|
return $syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
protected function loadRootDse(?array $attrs=null): LdapObject {
|
||||||
|
if ($attrs === null) $attrs = ["+", "*"];
|
||||||
|
$entry = $this->_search(null, [
|
||||||
|
"attrs" => $attrs,
|
||||||
|
"scope" => "base",
|
||||||
|
"suffix" => "",
|
||||||
|
])->first($dn);
|
||||||
|
return $this->empty()->load($dn, $entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var LdapObject */
|
||||||
|
protected $ppRootDse;
|
||||||
|
|
||||||
|
function pp_setRootDse(array $rootDse) {
|
||||||
|
$this->ppRootDse = $this->empty()->reset("", $rootDse);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRootDse(): LdapObject {
|
||||||
|
if ($this->ppRootDse === null) $this->ppRootDse = $this->loadRootDse();
|
||||||
|
return $this->ppRootDse;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRootDseForContexts(): LdapObject {
|
||||||
|
$rootDse = $this->ppRootDse;
|
||||||
|
if ($rootDse === null) {
|
||||||
|
$rootDse = $this->loadRootDse(["defaultNamingContext", "namingContexts"]);
|
||||||
|
}
|
||||||
|
return $rootDse;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadTopObject(?array $attrs=null): LdapObject {
|
||||||
|
if ($attrs === null) $attrs = ["+", "*"];
|
||||||
|
$entry = $this->_search("", [
|
||||||
|
"attrs" => $attrs,
|
||||||
|
"scope" => "base",
|
||||||
|
])->first($dn);
|
||||||
|
return $this->empty()->load($dn, $entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $ppLdapSyntaxes;
|
||||||
|
|
||||||
|
protected $ppAttributeTypes;
|
||||||
|
|
||||||
|
protected $ppObjectClasses;
|
||||||
|
|
||||||
|
function getSchemaInfos(): array {
|
||||||
|
$ldapSyntaxes = $this->ppLdapSyntaxes;
|
||||||
|
$attributeTypes = $this->ppAttributeTypes;
|
||||||
|
$objectClasses = $this->ppObjectClasses;
|
||||||
|
if ($ldapSyntaxes === null || $attributeTypes === null || $objectClasses === null) {
|
||||||
|
$lse = new LdapSchemaExtractor();
|
||||||
|
[
|
||||||
|
"ldap_syntaxes" => $ldapSyntaxes,
|
||||||
|
"attribute_types" => $attributeTypes,
|
||||||
|
"object_classes" => $objectClasses,
|
||||||
|
] = $lse->loadSchema($this);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"ldap_syntaxes" => $this->ppLdapSyntaxes = $ldapSyntaxes,
|
||||||
|
"attribute_types" => $this->ppAttributeTypes = $attributeTypes,
|
||||||
|
"object_classes" => $this->ppObjectClasses = $objectClasses,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfig($output, bool $overwriteShared=false): void {
|
||||||
|
$uri = $this->ppUri;
|
||||||
|
$sharedname = ldap_config::get_shared_file($uri);
|
||||||
|
if (is_string($output)) {
|
||||||
|
# corriger éventuellement le nom du fichier
|
||||||
|
$output = ldap_config::get_file($output);
|
||||||
|
# calculer le chemin vers fichier partagé
|
||||||
|
$shared = path::join(path::dirname($output), $sharedname);
|
||||||
|
# écrire la configuration partagée
|
||||||
|
if ($overwriteShared) {
|
||||||
|
# forcer le recalcul
|
||||||
|
$this->ppRootDse = null;
|
||||||
|
$this->ppLdapSyntaxes = null;
|
||||||
|
$this->ppAttributeTypes = null;
|
||||||
|
$this->ppObjectClasses = null;
|
||||||
|
}
|
||||||
|
if (!file_exists($shared) || $overwriteShared) {
|
||||||
|
$rootDse = $this->getRootDse()->array();
|
||||||
|
[
|
||||||
|
"ldap_syntaxes" => $ldapSyntaxes,
|
||||||
|
"attribute_types" => $attributeTypes,
|
||||||
|
"object_classes" => $objectClasses,
|
||||||
|
] = $this->getSchemaInfos();
|
||||||
|
$config = [
|
||||||
|
"uri" => $uri,
|
||||||
|
"controls" => $this->ppControls,
|
||||||
|
"protocol" => $this->ppProtocol,
|
||||||
|
"suffix" => $this->ppSuffix,
|
||||||
|
"domain" => $this->ppDomain,
|
||||||
|
"etab" => $this->ppEtab,
|
||||||
|
"root_dse" => $rootDse,
|
||||||
|
"ldap_syntaxes" => $ldapSyntaxes,
|
||||||
|
"attribute_types" => $attributeTypes,
|
||||||
|
"object_classes" => $objectClasses,
|
||||||
|
];
|
||||||
|
$src = new SrcGenerator();
|
||||||
|
$literals = [];
|
||||||
|
foreach (consts::LDAP_CONTROL_CONSTANTS as $constant) {
|
||||||
|
if (defined($constant)) {
|
||||||
|
$literals[] = [constant($constant), $constant];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
A::merge($literals, consts::ROOT_DSE_LITERALS);
|
||||||
|
$src
|
||||||
|
->genSof()
|
||||||
|
->genLiteral("# shared configuration for $uri")
|
||||||
|
->genReturn($config, null, $literals);
|
||||||
|
writer::with($shared, "wb")->writeLines($src->getLines())->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# écrire la configuration
|
||||||
|
$config = [
|
||||||
|
"binddn" => $this->ppBinddn,
|
||||||
|
"password" => $this->ppPassword,
|
||||||
|
];
|
||||||
|
$src = new SrcGenerator();
|
||||||
|
$src
|
||||||
|
->genSof()
|
||||||
|
->genLiteral("return array_merge(require __DIR__.'/$sharedname',")
|
||||||
|
->addValue($config)
|
||||||
|
->genLiteral(");");
|
||||||
|
writer::with($output, "wb")->writeLines($src->getLines())->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calculer automatiquement les paramètres par défaut s'ils ne sont pas
|
||||||
|
* spécifiés, tels que:
|
||||||
|
* - suffix
|
||||||
|
* - domain
|
||||||
|
* - etab
|
||||||
|
*/
|
||||||
|
function fillParams(): void {
|
||||||
|
if ($this->ppSuffix === null) {
|
||||||
|
$rootDse = $this->getRootDseForContexts();
|
||||||
|
$suffix = $rootDse->get("defaultNamingContext");
|
||||||
|
if ($suffix === null) {
|
||||||
|
$namingContexts = $rootDse->get("namingContexts", []);
|
||||||
|
foreach ($namingContexts as $namingContext) {
|
||||||
|
if (str::_starts_with("dc=", strtolower($namingContext))) {
|
||||||
|
$suffix = $namingContext;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($suffix === null) $suffix = $namingContexts[0];
|
||||||
|
}
|
||||||
|
$this->ppSuffix = $suffix;
|
||||||
|
}
|
||||||
|
if ($this->ppDomain === null) {
|
||||||
|
$parts = ldap_explode_dn($this->ppSuffix, 1);
|
||||||
|
unset($parts["count"]);
|
||||||
|
$this->ppDomain = implode(".", $parts);
|
||||||
|
}
|
||||||
|
if ($this->ppEtab === null) {
|
||||||
|
$topObject = $this->loadTopObject();
|
||||||
|
$this->ppEtab = $topObject->first("supannEtablissement");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/ldap/LdapException.php
Normal file
75
src/ldap/LdapException.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
class LdapException extends UserException {
|
||||||
|
/** @param $r ?resource */
|
||||||
|
static function check(string $message, $r, $value, ?int $allow_errno=null) {
|
||||||
|
if ($value !== false) return $value;
|
||||||
|
if ($r !== null) {
|
||||||
|
$errno = ldap_errno($r);
|
||||||
|
if ($allow_errno !== null && $errno === $allow_errno) return $value;
|
||||||
|
throw new self($message, $errno, null, ldap_error($r));
|
||||||
|
} else {
|
||||||
|
throw new self($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function check_result(string $message, $conn, $r) {
|
||||||
|
ldap_parse_result($conn, $r, $errorCode, $matchedDn, $errorMessage, $referrals, $controls);
|
||||||
|
if ($errorCode != 0) {
|
||||||
|
if (!$errorMessage) $errorMessage = ldap_err2str($errorCode);
|
||||||
|
throw new LdapException($message, $errorCode, $matchedDn, $errorMessage, $referrals, $controls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct(string $userMessage
|
||||||
|
, ?int $errorCode=null, ?string $matchedDn=null, ?string $errorMessage=null
|
||||||
|
, ?array $referrals=null, ?array $controls=null) {
|
||||||
|
if ($errorCode == 0) {
|
||||||
|
parent::__construct($userMessage);
|
||||||
|
} else {
|
||||||
|
$this->matchedDn = $matchedDn;
|
||||||
|
$this->errorMessage = $errorMessage;
|
||||||
|
$this->referrals = $referrals;
|
||||||
|
$this->controls = $controls;
|
||||||
|
$parts = ["error $errorCode"];
|
||||||
|
if ($errorMessage) $parts[] = $errorMessage;
|
||||||
|
if ($matchedDn) $parts[] = "matched_dn: $matchedDn";
|
||||||
|
if ($referrals) $parts[] = "referrals: ".implode(" ", $referrals);
|
||||||
|
$techMessage = implode(", ", $parts);
|
||||||
|
parent::__construct([
|
||||||
|
"user" => $userMessage,
|
||||||
|
"tech" => $techMessage,
|
||||||
|
], $errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $matchedDn;
|
||||||
|
|
||||||
|
function getMatchedDn(): ?string {
|
||||||
|
return $this->matchedDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $errorMessage;
|
||||||
|
|
||||||
|
function getErrorMessage(): ?string {
|
||||||
|
return $this->errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var ?array */
|
||||||
|
protected $referrals;
|
||||||
|
|
||||||
|
function getReferrals(): ?array {
|
||||||
|
return $this->referrals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var ?array */
|
||||||
|
protected $controls;
|
||||||
|
|
||||||
|
function getControls(): ?array {
|
||||||
|
return $this->controls;
|
||||||
|
}
|
||||||
|
}
|
||||||
372
src/ldap/LdapObject.php
Normal file
372
src/ldap/LdapObject.php
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Countable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LdapObject: un objet LDAP
|
||||||
|
*/
|
||||||
|
class LdapObject implements ArrayAccess, Countable {
|
||||||
|
static function with(?string $dn, ?array $entry): ?self {
|
||||||
|
if ($entry === null) return null;
|
||||||
|
else return (new self())->load($dn, $entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string[] liste des classes par défaut lors de la création de l'objet */
|
||||||
|
const OBJECT_CLASSES = ["top"];
|
||||||
|
/** @var string DN dans lequel cet objet est créé par défaut */
|
||||||
|
const PARENT_RDN = null;
|
||||||
|
/**
|
||||||
|
* @var array|string nom des attribut(s) utilisé(s) pour nommer cet objet par
|
||||||
|
* défaut
|
||||||
|
*/
|
||||||
|
const DN_NAMES = null;
|
||||||
|
|
||||||
|
function __construct(?string $dn=null, ?array $attrs=null, ?array $initialNames=null, ?LdapConn $conn=null) {
|
||||||
|
$this->reset($dn, $attrs, A::with($initialNames), $conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var LdapConn */
|
||||||
|
protected $conn;
|
||||||
|
|
||||||
|
function getConn(): LdapConn {
|
||||||
|
return $this->conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array attributs initialement demandés lors de la recherche */
|
||||||
|
protected $initialNames;
|
||||||
|
|
||||||
|
protected function initialNames(): array {
|
||||||
|
return $this->initialNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array valeurs originale des attributs avant modification */
|
||||||
|
protected $orig;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $lkey2names;
|
||||||
|
|
||||||
|
/** @var array liste des attributs utilisés pour nommer l'objet */
|
||||||
|
protected $dnNames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LdapAttr[] pour chaque attribut, l'instance de {@link LdapAttr} qui
|
||||||
|
* gère les valeurs correspondantes de $data
|
||||||
|
*/
|
||||||
|
protected $attrs;
|
||||||
|
|
||||||
|
protected function resetAttrs(): void {
|
||||||
|
# refaire les attributs le cas échéant
|
||||||
|
if ($this->attrs === null) return;
|
||||||
|
foreach (array_keys($this->data) as $name) {
|
||||||
|
if (array_key_exists($name, $this->attrs)) {
|
||||||
|
$this->attrs[$name]->reset($this->data[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function n($key): string {
|
||||||
|
$lkey = strtolower(strval($key));
|
||||||
|
$name = A::get($this->lkey2names, $lkey);
|
||||||
|
if ($name === null) {
|
||||||
|
# si $key n'existe pas, l'ajouter
|
||||||
|
$name = $this->lkey2names[$lkey] = $key;
|
||||||
|
}
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function &array(): ?array { return $this->data; }
|
||||||
|
function count(): int { return count($this->data); }
|
||||||
|
function keys(): array { return array_keys($this->data); }
|
||||||
|
function has($name): bool {
|
||||||
|
return $this->data !== null && array_key_exists($this->n($name), $this->data);
|
||||||
|
}
|
||||||
|
function _get(string $name): LdapAttr {
|
||||||
|
$name = $this->n($name);
|
||||||
|
if ($this->attrs === null || !array_key_exists($name, $this->attrs)) {
|
||||||
|
$attribute = A::get(static::SCHEMA(), strtolower($name));
|
||||||
|
if ($attribute !== null && $this->conn !== null) {
|
||||||
|
["class" => $class, "flags" => $flags] = $attribute;
|
||||||
|
$syntax = $this->conn->getSyntax($class);
|
||||||
|
} else {
|
||||||
|
$syntax = $flags = null;
|
||||||
|
}
|
||||||
|
if ($syntax !== null) {
|
||||||
|
$attr = $syntax->newAttr($name, $this->data[$name], $flags);
|
||||||
|
} else {
|
||||||
|
$attr = new LdapAttr($name, $this->data[$name], $syntax, $flags);
|
||||||
|
}
|
||||||
|
$this->attrs[$name] = $attr;
|
||||||
|
}
|
||||||
|
return $this->attrs[$name];
|
||||||
|
}
|
||||||
|
function _del(string $name): void {
|
||||||
|
unset($this->data[$this->n($name)]);
|
||||||
|
}
|
||||||
|
function get($name) { return $this->_get($name)->get(); }
|
||||||
|
function first($name) { return $this->_get($name)->first(); }
|
||||||
|
function all($name): iterable { return $this->_get($name)->all(); }
|
||||||
|
function set($name, $values, bool $unlessNn=false): self { $this->_get($name)->set($values, $unlessNn); return $this; }
|
||||||
|
function add($name, $value, bool $unique=true): self { $this->_get($name)->add($value, $unique); return $this; }
|
||||||
|
function del($name, $value, int $maxCount=-1, bool $strict=false): self { $this->_get($name)->del($value, $maxCount, $strict); return $this; }
|
||||||
|
function ins($name, int $index, $value): self { $this->_get($name)->ins($index, $value); return $this; }
|
||||||
|
function unset($name, int $index): self { $this->_get($name)->unset($index); return $this; }
|
||||||
|
function merge(?array $attrs): self {
|
||||||
|
if ($attrs !== null) {
|
||||||
|
foreach ($attrs as $name => $values) {
|
||||||
|
$this->set($name, $values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function offsetExists($key) { return $this->has($key); }
|
||||||
|
function offsetGet($key) { return $this->_get($key)->get(); }
|
||||||
|
function offsetSet($key, $value) { $this->_get($key)->set($value); }
|
||||||
|
function offsetUnset($key) { $this->_del($key); }
|
||||||
|
|
||||||
|
function __isset($key) { return $this->has($key); }
|
||||||
|
function __get($key) { return $this->_get($key)->get(); }
|
||||||
|
function __set($key, $value) { $this->_get($key)->set($value); }
|
||||||
|
function __unset($key) { $this->_del($key); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialiser cet objet avec des données construites à la volée.
|
||||||
|
* - si $dn === null, c'est un nouvel objet
|
||||||
|
* - sinon c'est un objet existant déjà dans LDAP
|
||||||
|
*/
|
||||||
|
function reset(?string $dn, ?array $attrs=null, ?array $initialNames=null, ?LdapConn $conn=null): self {
|
||||||
|
if ($conn !== null) $this->conn = $conn;
|
||||||
|
if ($initialNames !== null) $this->initialNames = $initialNames;
|
||||||
|
# attributs demandés
|
||||||
|
$lkey2names = ["dn" => "dn"];
|
||||||
|
foreach ($this->initialNames() as $name) {
|
||||||
|
if ($name == "+" || $name == "*") continue;
|
||||||
|
$lkey2names[strtolower($name)] = $name;
|
||||||
|
}
|
||||||
|
# attributs obtenus effectivement
|
||||||
|
A::merge_nn($attrs, [
|
||||||
|
"objectClass" => static::OBJECT_CLASSES,
|
||||||
|
]);
|
||||||
|
$orig = ["dn" => [$dn]];
|
||||||
|
foreach ($attrs as $name => $value) {
|
||||||
|
$orig[$name] = $value;
|
||||||
|
$lkey2names[strtolower($name)] = $name;
|
||||||
|
}
|
||||||
|
# ensuite, mettre à null les attributs qui n'ont pas été obtenus
|
||||||
|
foreach ($lkey2names as $name) {
|
||||||
|
if (!array_key_exists($name, $orig)) {
|
||||||
|
$orig[$name] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# calculer les clés qui composent le DN
|
||||||
|
$dnNames = names::get_dn_names($dn, $lkey2names);
|
||||||
|
# finaliser le paramétrage
|
||||||
|
$this->data = $this->orig = $orig;
|
||||||
|
$this->lkey2names = $lkey2names;
|
||||||
|
$this->dnNames = $dnNames;
|
||||||
|
$this->resetAttrs();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** initialiser cet objet avec le résultat d'une recherche */
|
||||||
|
function load(string $dn, array $entry): self {
|
||||||
|
[$this->orig, $this->lkey2names, $this->dnNames,
|
||||||
|
] = LdapSearch::cook($this->initialNames(), $dn, $entry);
|
||||||
|
$this->data = $this->orig;
|
||||||
|
$this->resetAttrs();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** recharger l'objet depuis le serveur */
|
||||||
|
function reload(?LdapConn $conn=null): self {
|
||||||
|
if ($conn === null) $conn = $this->conn;
|
||||||
|
$dn = $this->data["dn"][0];
|
||||||
|
$entry = $conn->_search($dn, [
|
||||||
|
"attrs" => $this->initialNames(),
|
||||||
|
"scope" => "base",
|
||||||
|
])->first($dn);
|
||||||
|
if ($entry === null) {
|
||||||
|
throw new IllegalAccessException("object $dn no longer exists");
|
||||||
|
}
|
||||||
|
return $this->load($dn, $entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDn(?string $parentDn=null, $dnNames=null, ?LdapConn $conn=null): void {
|
||||||
|
if ($conn === null) $conn = $this->conn;
|
||||||
|
if ($parentDn === null) $parentDn = static::PARENT_RDN;
|
||||||
|
if ($conn !== null) $parentDn = $conn->ensureDn($parentDn);
|
||||||
|
if ($dnNames === null) $dnNames = static::DN_NAMES;
|
||||||
|
$rdn = [];
|
||||||
|
foreach (A::with($dnNames) as $name) {
|
||||||
|
$rdn[$name] = $this->get($name);
|
||||||
|
}
|
||||||
|
$dn = names::join($rdn, $parentDn);
|
||||||
|
$this->data["dn"] = [$dn];
|
||||||
|
$this->dnNames = names::get_dn_names($dn, $this->lkey2names);
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeAddattrs(array $data): array {
|
||||||
|
$attrs = [];
|
||||||
|
$first = true;
|
||||||
|
foreach ($data as $name => $values) {
|
||||||
|
if ($first) {
|
||||||
|
# ne pas inclure le DN
|
||||||
|
$first = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
# ne pas inclure les valeurs vides et nulles
|
||||||
|
if ($values === null || $values === []) continue;
|
||||||
|
# utiliser array_values pour être sûr d'avoir un tableau séquentiel (les
|
||||||
|
# valeurs composites sont indexées sur la clé calculée)
|
||||||
|
$attrs[$name] = array_values(A::with($values));
|
||||||
|
}
|
||||||
|
return $attrs;
|
||||||
|
}
|
||||||
|
function computeModattr(string $name, $orig, $value): array {
|
||||||
|
# utiliser array_values pour être sûr d'avoir un tableau séquentiel (les
|
||||||
|
# valeurs composites sont indexées sur la clé calculée)
|
||||||
|
$orig = array_values(A::with($orig));
|
||||||
|
$value = array_values(A::with($value));
|
||||||
|
if ($value === $orig) return [];
|
||||||
|
if (!$orig) return [["add", $name => $value]];
|
||||||
|
elseif (!$value) return [["delete", $name]];
|
||||||
|
else return [["replace", $name => $value]];
|
||||||
|
#XXX pour certains attributs (comme member), ou si le nombre d'éléments
|
||||||
|
# dépasse un certain seuil, remplacer replace par un ensemble de add et/ou
|
||||||
|
# delete
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retourner true si update() provoquerait une mise à jour du serveur LDAP, en
|
||||||
|
* d'autres termes si l'objet est nouveau ou a des modifications
|
||||||
|
*/
|
||||||
|
function willUpdate(): bool {
|
||||||
|
$create = $this->orig["dn"][0] === null;
|
||||||
|
if ($create) return true;
|
||||||
|
foreach ($this->data as $name => $value) {
|
||||||
|
$orig = A::get($this->orig, $name);
|
||||||
|
$modattr = $this->computeModattr($name, $orig, $value);
|
||||||
|
if ($modattr != null) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool true si la modification a été faite, false si elle n'était pas
|
||||||
|
* nécessaire
|
||||||
|
*/
|
||||||
|
function update($params=null, ?LdapConn $conn=null, ?bool $create=null): bool {
|
||||||
|
if ($conn === null) $conn = $this->conn;
|
||||||
|
$dn = $this->data["dn"][0];
|
||||||
|
if ($create === null) {
|
||||||
|
$origDn = $this->orig["dn"][0];
|
||||||
|
$create = $origDn === null;
|
||||||
|
}
|
||||||
|
if ($create) {
|
||||||
|
# création de l'objet
|
||||||
|
$attrs = $this->computeAddattrs($this->data);
|
||||||
|
$conn->add($dn, $attrs, $params);
|
||||||
|
} else {
|
||||||
|
# mise à jour de l'objet
|
||||||
|
$modattrs = [];
|
||||||
|
foreach ($this->data as $name => $value) {
|
||||||
|
$orig = A::get($this->orig, $name);
|
||||||
|
$modattr = $this->computeModattr($name, $orig, $value);
|
||||||
|
if ($modattr != null) {
|
||||||
|
if (in_array($name, $this->dnNames)) {
|
||||||
|
throw IllegalAccessException::not_allowed("modifying DN attrs");
|
||||||
|
}
|
||||||
|
A::merge($modattrs, $modattr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$modattrs) return false;
|
||||||
|
$conn->modify($dn, $modattrs);
|
||||||
|
}
|
||||||
|
# s'il y a des références sur $this->data, alors une simple "copie" fera
|
||||||
|
# que $this->orig garde ces références. c'est la raison pour laquelle on
|
||||||
|
# doit refaire les attributs
|
||||||
|
$this->orig = $this->data;
|
||||||
|
$this->attrs = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rename(string $newRdn, $params=null, ?LdapConn $conn=null): void {
|
||||||
|
if ($conn === null) $conn = $this->conn;
|
||||||
|
$dn = $this->data["dn"][0];
|
||||||
|
if (ldap::prepare_rename($dn, $newRdn, $params)) {
|
||||||
|
$dn = $conn->rename($dn, $newRdn, $params);
|
||||||
|
$this->orig["dn"] = [$dn];
|
||||||
|
$this->data["dn"] = [$dn];
|
||||||
|
$this->dnNames = names::get_dn_names($dn, $this->lkey2names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete($params=null, ?LdapConn $conn=null): void {
|
||||||
|
if ($conn === null) $conn = $this->conn;
|
||||||
|
$conn->delete($this->data["dn"][0], $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tester s'il existe un objet nommé $attr=$value dans branche $parent qui
|
||||||
|
* vaut par défaut la branche dans laquelle est situé cet objet
|
||||||
|
*/
|
||||||
|
function existsSibling(string $value, ?string $attr=null, ?string $parent=null, ?LdapConn $conn=null): bool {
|
||||||
|
if ($conn === null) $conn = $this->conn;
|
||||||
|
$dn = $this->data["dn"][0];
|
||||||
|
names::split_dn($dn, $myRdn, $myParent);
|
||||||
|
if ($attr === null) {
|
||||||
|
$myAttrs = names::split_rdn($myRdn);
|
||||||
|
$attr = A::first_key($myAttrs);
|
||||||
|
}
|
||||||
|
if ($parent === null) $parent = $myParent;
|
||||||
|
$entry = $conn->_search(null, [
|
||||||
|
"scope" => "one",
|
||||||
|
"suffix" => $parent,
|
||||||
|
"filter" => [$attr => $value],
|
||||||
|
"attrs" => ["dn"],
|
||||||
|
])->first();
|
||||||
|
return $entry !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
static function _AUTOGEN_SCHEMA(): array {
|
||||||
|
return scheman::autogen_schema(static::OBJECT_CLASSES);
|
||||||
|
}
|
||||||
|
static function _AUTOGEN_PROPERTIES(): array {
|
||||||
|
return scheman::autogen_properties(self::_AUTOGEN_SCHEMA());
|
||||||
|
}
|
||||||
|
static function _AUTOGEN_METHODS(): array {
|
||||||
|
return scheman::autogen_methods(self::_AUTOGEN_SCHEMA());
|
||||||
|
}
|
||||||
|
const SCHEMA = null;
|
||||||
|
protected static function SCHEMA(): array {
|
||||||
|
# il faut au moins la définition qui indique que dn est monovalué
|
||||||
|
$schema = static::SCHEMA;
|
||||||
|
if ($schema === null) {
|
||||||
|
$schema = [
|
||||||
|
"dn" => [
|
||||||
|
"name" => "dn",
|
||||||
|
"class" => StringSyntax::class,
|
||||||
|
"flags" => LdapAttr::MONOVALUED,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
function __call(string $name, ?array $args) {
|
||||||
|
$schema = static::SCHEMA();
|
||||||
|
if (is_array($schema) && array_key_exists(strtolower($name), $schema)) {
|
||||||
|
return $this->_get($name);
|
||||||
|
}
|
||||||
|
throw IllegalAccessException::not_implemented($name);
|
||||||
|
}
|
||||||
|
## rajouter ceci dans les classes dérivées
|
||||||
|
#const _AUTOGEN_CONSTS = ["SCHEMA"];
|
||||||
|
#const _AUTOGEN_PROPERTIES = [[self::class, "_AUTOGEN_PROPERTIES"]];
|
||||||
|
#const _AUTOGEN_METHODS = [[self::class, "_AUTOGEN_METHODS"]];
|
||||||
|
}
|
||||||
215
src/ldap/LdapSearch.php
Normal file
215
src/ldap/LdapSearch.php
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
use IteratorAggregate;
|
||||||
|
use nulib\output\msg;
|
||||||
|
|
||||||
|
class LdapSearch extends Parametrable implements IteratorAggregate {
|
||||||
|
use Tparametrable;
|
||||||
|
|
||||||
|
static function parse_args(?array &$params, ?array $args
|
||||||
|
, ?string $searchbase=null, ?string $searchbase_exact=null
|
||||||
|
, ?string $scope=null): void {
|
||||||
|
$first = true;
|
||||||
|
$filter = null;
|
||||||
|
$attrs = null;
|
||||||
|
foreach ($args as $arg) {
|
||||||
|
if ($first) {
|
||||||
|
$first = false;
|
||||||
|
if (strpos($arg, "=") !== false) $filter = $arg;
|
||||||
|
else $attrs[] = $arg;
|
||||||
|
} else {
|
||||||
|
$attrs[] = $arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($filter !== null) $params["filter"] = $filter;
|
||||||
|
if ($attrs !== null) $params["attrs"] = $attrs;
|
||||||
|
if ($searchbase_exact !== null) {
|
||||||
|
$searchbase = $searchbase_exact;
|
||||||
|
$params["suffix"] = "";
|
||||||
|
}
|
||||||
|
if ($searchbase !== null) $params["searchbase"] = $searchbase;
|
||||||
|
if ($scope !== null) $params["scope"] = $scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SCOPE_SUBTREE = 2, SCOPE_ONELEVEL = 1, SCOPE_BASE = 0;
|
||||||
|
|
||||||
|
const PARAMETRABLE_PARAMS_SCHEMA = [
|
||||||
|
"filter" => ["?content", "objectClass=*", "filtre de recherche"],
|
||||||
|
"attrs" => ["?array", [], "attributs à retourner"],
|
||||||
|
"searchbase" => ["?string", null, "DN de base pour la recherche"],
|
||||||
|
"scope" => ["?string", "sub", "étendue de la recherche"],
|
||||||
|
"suffix" => ["?string", null, "DN de base du serveur"],
|
||||||
|
"attributes_only" => ["bool", false, "faut-il ne retourner que les attributs?"],
|
||||||
|
"sizelimit" => ["int", -1, "limite de taille"],
|
||||||
|
"timelimit" => ["int", -1, "limite de temps"],
|
||||||
|
"deref" => ["int", LDAP_DEREF_NEVER, "type de déférencement"],
|
||||||
|
"controls" => ["array", [], "contrôles de la recherche"],
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $search_md;
|
||||||
|
|
||||||
|
static function search_md(): Metadata {
|
||||||
|
return md_utils::ensure_md(self::$search_md, self::PARAMETRABLE_PARAMS_SCHEMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct($conn, array $params) {
|
||||||
|
$this->conn = $conn;
|
||||||
|
parent::__construct($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var resource */
|
||||||
|
protected $conn;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $ppSearchbase;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $filter;
|
||||||
|
|
||||||
|
function pp_setFilter($filter): void {
|
||||||
|
$this->filter = filters::parse($filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $ppAttrs;
|
||||||
|
|
||||||
|
/** retourner la liste des attributs demandés */
|
||||||
|
function getAttrs(): array {
|
||||||
|
return $this->ppAttrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $scope;
|
||||||
|
|
||||||
|
function pp_setScope(string $scope): void {
|
||||||
|
switch ($scope) {
|
||||||
|
case self::SCOPE_SUBTREE:
|
||||||
|
case "subtree":
|
||||||
|
case "sub":
|
||||||
|
case "s":
|
||||||
|
$this->scope = self::SCOPE_SUBTREE;
|
||||||
|
break;
|
||||||
|
case self::SCOPE_ONELEVEL:
|
||||||
|
case "onelevel":
|
||||||
|
case "one":
|
||||||
|
case "o":
|
||||||
|
$this->scope = self::SCOPE_ONELEVEL;
|
||||||
|
break;
|
||||||
|
case self::SCOPE_BASE:
|
||||||
|
case "base":
|
||||||
|
case "b":
|
||||||
|
$this->scope = self::SCOPE_BASE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw ValueException::invalid_value($scope, "scope");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $ppSuffix;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
protected $ppAttributesOnly;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $ppSizelimit;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $ppTimelimit;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $ppDeref;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $ppControls;
|
||||||
|
|
||||||
|
/** @throws LdapException */
|
||||||
|
function getIterator() {
|
||||||
|
$conn = $this->conn;
|
||||||
|
$args = [$conn];
|
||||||
|
$base = [];
|
||||||
|
if ($this->ppSearchbase) $base[] = $this->ppSearchbase;
|
||||||
|
if ($this->ppSuffix) $base[] = $this->ppSuffix;
|
||||||
|
$args[] = implode(",", $base);
|
||||||
|
A::merge($args, [
|
||||||
|
$this->filter?: "",
|
||||||
|
$this->ppAttrs?: [],
|
||||||
|
$this->ppAttributesOnly,
|
||||||
|
$this->ppSizelimit,
|
||||||
|
$this->ppTimelimit,
|
||||||
|
$this->ppDeref,
|
||||||
|
$this->ppControls,
|
||||||
|
]);
|
||||||
|
msg::debug("Searching searchbase=$args[1] filter=$args[2]");
|
||||||
|
|
||||||
|
$scope = $this->scope;
|
||||||
|
if ($scope == self::SCOPE_SUBTREE) $rr = @ldap_search(...$args);
|
||||||
|
elseif ($scope == self::SCOPE_ONELEVEL) $rr = @ldap_list(...$args);
|
||||||
|
elseif ($scope == self::SCOPE_BASE) $rr = @ldap_read(...$args);
|
||||||
|
else throw ValueException::invalid_value($scope, "scope");
|
||||||
|
|
||||||
|
$rr = LdapException::check("search", $conn, $rr, 32);
|
||||||
|
if ($rr === false) return; // pas trouvé
|
||||||
|
|
||||||
|
try {
|
||||||
|
$er = ldap_first_entry($conn, $rr);
|
||||||
|
while ($er !== false) {
|
||||||
|
$dn = ldap_get_dn($conn, $er);
|
||||||
|
$entry = ldap_get_attributes($conn, $er);
|
||||||
|
yield $dn => $entry;
|
||||||
|
$er = ldap_next_entry($conn, $er);
|
||||||
|
}
|
||||||
|
} catch (StopException $e) {
|
||||||
|
} finally {
|
||||||
|
ldap_free_result($rr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retourner la première entrée du résultat de la recherche ou null si la
|
||||||
|
* recherche ne retourne aucun résultat
|
||||||
|
*
|
||||||
|
* @throws LdapException
|
||||||
|
*/
|
||||||
|
function first(?string &$dn=null): ?array {
|
||||||
|
$it = $this->getIterator();
|
||||||
|
$it->rewind();
|
||||||
|
if (!$it->valid()) return null;
|
||||||
|
try {
|
||||||
|
$dn = $it->key();
|
||||||
|
return $it->current();
|
||||||
|
} finally {
|
||||||
|
iter::close($it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function cook(array $initial_names, string $dn, array $entry): array {
|
||||||
|
# attributs demandés
|
||||||
|
$lkey2names = ["dn" => "dn"];
|
||||||
|
foreach ($initial_names as $name) {
|
||||||
|
if ($name == "+" || $name == "*") continue;
|
||||||
|
$lkey2names[strtolower($name)] = $name;
|
||||||
|
}
|
||||||
|
# attributs obtenus effectivement
|
||||||
|
$count = $entry["count"];
|
||||||
|
$attrs = ["dn" => [$dn]];
|
||||||
|
for ($i = 0; $i < $count; $i++) {
|
||||||
|
$name = $entry[$i];
|
||||||
|
$attr = $entry[$name];
|
||||||
|
unset($attr["count"]);
|
||||||
|
$attrs[$name] = $attr;
|
||||||
|
$lkey2names[strtolower($name)] = $name;
|
||||||
|
}
|
||||||
|
# ensuite, mettre à null les attributs qui n'ont pas été obtenus
|
||||||
|
foreach ($lkey2names as $name) {
|
||||||
|
if (!array_key_exists($name, $attrs)) {
|
||||||
|
$attrs[$name] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# calculer les clés qui composent le DN
|
||||||
|
$dn_names = names::get_dn_names($dn, $lkey2names);
|
||||||
|
|
||||||
|
return [$attrs, $lkey2names, $dn_names];
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/ldap/LdapWalker.php
Normal file
10
src/ldap/LdapWalker.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LdapWalker: une classe permettant de parcourir les résultats d'une
|
||||||
|
* recherche
|
||||||
|
*/
|
||||||
|
class LdapWalker extends LdapObject implements ILdapWalker {
|
||||||
|
use TLdapWalker;
|
||||||
|
}
|
||||||
24
src/ldap/TCompositeValue.php
Normal file
24
src/ldap/TCompositeValue.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
trait TCompositeValue {
|
||||||
|
use TArrayMd;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private static $optional_keys;
|
||||||
|
|
||||||
|
protected function getOptionalKeys(): array {
|
||||||
|
$optionalKeys = self::$optional_keys;
|
||||||
|
if ($optionalKeys === null) {
|
||||||
|
$optionalKeys = self::$optional_keys = parent::getOptionalKeys();
|
||||||
|
}
|
||||||
|
return $optionalKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(?array $values): CompositeValue {
|
||||||
|
$this->md()->ensureSchema($values);
|
||||||
|
$this->data = $values;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/ldap/TLdapWalker.php
Normal file
53
src/ldap/TLdapWalker.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
|
||||||
|
trait TLdapWalker {
|
||||||
|
function __construct(?LdapConn $conn=null, ?LdapSearch $search=null) {
|
||||||
|
parent::__construct(null, null, null, $conn);
|
||||||
|
if ($search !== null) $this->resetSearch($search);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var LdapSearch */
|
||||||
|
protected $search;
|
||||||
|
|
||||||
|
function resetSearch(LdapSearch $search): ILdapWalker {
|
||||||
|
$this->close();
|
||||||
|
$this->reset(null, null, $search->getAttrs());
|
||||||
|
$this->search = $search;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Iterator */
|
||||||
|
protected $it;
|
||||||
|
|
||||||
|
protected function loadNext(): bool {
|
||||||
|
$it = $this->it;
|
||||||
|
if (!$it->valid()) {
|
||||||
|
$this->close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->load($it->key(), $it->current());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function next(?bool &$found=null): bool {
|
||||||
|
if ($this->it === null) {
|
||||||
|
$this->it = $this->search->getIterator();
|
||||||
|
$this->it->rewind();
|
||||||
|
$updateFound = true;
|
||||||
|
} else {
|
||||||
|
$this->it->next();
|
||||||
|
$updateFound = false;
|
||||||
|
}
|
||||||
|
$haveNext = $this->loadNext();
|
||||||
|
if ($updateFound) $found = $haveNext;
|
||||||
|
return $haveNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
iter::close($this->it);
|
||||||
|
$this->it = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
315
src/ldap/consts.php
Normal file
315
src/ldap/consts.php
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
class consts {
|
||||||
|
/**
|
||||||
|
* @var array[] définitions connues des syntaxes, au cas où le serveur ne les
|
||||||
|
* retourne pas
|
||||||
|
*/
|
||||||
|
const KNOWN_SLAPD_SYNTAXES = [
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.4' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.4',
|
||||||
|
'desc' => 'Audio',
|
||||||
|
'x_not_human_readable' => true,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.5' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.5',
|
||||||
|
'desc' => 'Binary',
|
||||||
|
'x_not_human_readable' => true,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.6' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.6',
|
||||||
|
'desc' => 'Bit String',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.7' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.7',
|
||||||
|
'desc' => 'Boolean',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.8' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.8',
|
||||||
|
'desc' => 'Certificate',
|
||||||
|
'x_not_human_readable' => true,
|
||||||
|
'x_binary_transfer_required' => true,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.9' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.9',
|
||||||
|
'desc' => 'Certificate List',
|
||||||
|
'x_not_human_readable' => true,
|
||||||
|
'x_binary_transfer_required' => true,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.10' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.10',
|
||||||
|
'desc' => 'Certificate Pair',
|
||||||
|
'x_not_human_readable' => true,
|
||||||
|
'x_binary_transfer_required' => true,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.4203.666.11.10.2.1' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.4203.666.11.10.2.1',
|
||||||
|
'desc' => 'X.509 AttributeCertificate',
|
||||||
|
'x_not_human_readable' => true,
|
||||||
|
'x_binary_transfer_required' => true,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.12' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.12',
|
||||||
|
'desc' => 'Distinguished Name',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.2.36.79672281.1.5.0' => [
|
||||||
|
'oid' => '1.2.36.79672281.1.5.0',
|
||||||
|
'desc' => 'RDN',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.14' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.14',
|
||||||
|
'desc' => 'Delivery Method',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.15' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.15',
|
||||||
|
'desc' => 'Directory String',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.22' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.22',
|
||||||
|
'desc' => 'Facsimile Telephone Number',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.23' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.23',
|
||||||
|
'desc' => 'Fax image',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.24' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.24',
|
||||||
|
'desc' => 'Generalized Time',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.25' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.25',
|
||||||
|
'desc' => 'Guide',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.26' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.26',
|
||||||
|
'desc' => 'IA5 String',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.27' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.27',
|
||||||
|
'desc' => 'Integer',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.28' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.28',
|
||||||
|
'desc' => 'JPEG',
|
||||||
|
'x_not_human_readable' => true,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.34' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.34',
|
||||||
|
'desc' => 'Name And Optional UID',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.36' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.36',
|
||||||
|
'desc' => 'Numeric String',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.38' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.38',
|
||||||
|
'desc' => 'OID',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.39' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.39',
|
||||||
|
'desc' => 'Other Mailbox',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.40' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.40',
|
||||||
|
'desc' => 'Octet String',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.41' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.41',
|
||||||
|
'desc' => 'Postal Address',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.44' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.44',
|
||||||
|
'desc' => 'Printable String',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.11' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.11',
|
||||||
|
'desc' => 'Country String',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.45' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.45',
|
||||||
|
'desc' => 'SubtreeSpecification',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.49' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.49',
|
||||||
|
'desc' => 'Supported Algorithm',
|
||||||
|
'x_not_human_readable' => true,
|
||||||
|
'x_binary_transfer_required' => true,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.50' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.50',
|
||||||
|
'desc' => 'Telephone Number',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.51' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.51',
|
||||||
|
'desc' => 'Teletex Terminal Identifier',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.52' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.52',
|
||||||
|
'desc' => 'Telex Number',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.53' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.53',
|
||||||
|
'desc' => 'UTC Time',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.54' => [
|
||||||
|
'oid' => '1.3.6.1.4.1.1466.115.121.1.54',
|
||||||
|
'desc' => 'LDAP Syntax Description',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.1.1.0.0' => [
|
||||||
|
'oid' => '1.3.6.1.1.1.0.0',
|
||||||
|
'desc' => 'RFC2307 NIS Netgroup Triple',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.1.1.0.1' => [
|
||||||
|
'oid' => '1.3.6.1.1.1.0.1',
|
||||||
|
'desc' => 'RFC2307 Boot Parameter',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
'1.3.6.1.1.16.1' => [
|
||||||
|
'oid' => '1.3.6.1.1.16.1',
|
||||||
|
'desc' => 'UUID',
|
||||||
|
'x_not_human_readable' => false,
|
||||||
|
'x_binary_transfer_required' => false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
const KNOWN_SYNTAX_CLASSES = [
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.4' => BinarySyntax::class, // audio
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.5' => BinarySyntax::class, // binary
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.6' => BinarySyntax::class, // bit string
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.7' => BooleanSyntax::class, // boolean
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.8' => BinarySyntax::class, // certificate
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.9' => BinarySyntax::class, // certificate list
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.10' => BinarySyntax::class, // certificate pair
|
||||||
|
'1.3.6.1.4.1.4203.666.11.10.2.1' => BinarySyntax::class, // X.509 AttributeCertificate
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.12' => StringSyntax::class, // DN
|
||||||
|
'1.2.36.79672281.1.5.0' => StringSyntax::class, // RDN
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.14' => StringSyntax::class, // delivery method
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.15' => StringSyntax::class, // directory string
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.22' => TelephoneSyntax::class, // fax number
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.24' => DateSyntax::class, // generalized time
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.26' => StringSyntax::class, // IA5 string
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.27' => IntegerSyntax::class, // integer
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.28' => BinarySyntax::class, // jpeg
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.34' => StringSyntax::class, // name and (opt.) oid
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.36' => IntegerSyntax::class, // numeric string
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.38' => StringSyntax::class, // oid
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.39' => MailSyntax::class, // other mailbox
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.40' => StringSyntax::class, // octet string
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.41' => PostalAddressSyntax::class, // postal address
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.44' => PrintableSyntax::class, // printable string
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.11' => StringSyntax::class, // country string
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.45' => StringSyntax::class, // subtree spec
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.49' => BinarySyntax::class, // supported algorithm
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.50' => TelephoneSyntax::class, // telephone number
|
||||||
|
'1.3.6.1.4.1.1466.115.121.1.52' => TelephoneSyntax::class, // telex number
|
||||||
|
'1.3.6.1.1.1.0.0' => StringSyntax::class, // RFC2307 NIS Netgroup Triple
|
||||||
|
'1.3.6.1.1.1.0.1' => StringSyntax::class, // RFC2307 Boot Parameter
|
||||||
|
'1.3.6.1.1.16.1' => StringSyntax::class, // uuid
|
||||||
|
];
|
||||||
|
|
||||||
|
const LDAP_CONTROL_CONSTANTS = [
|
||||||
|
# pas toutes ne sont définies en fonction de la version de PHP
|
||||||
|
"LDAP_CONTROL_MANAGEDSAIT",
|
||||||
|
"LDAP_CONTROL_PROXY_AUTHZ",
|
||||||
|
"LDAP_CONTROL_SUBENTRIES",
|
||||||
|
"LDAP_CONTROL_VALUESRETURNFILTER",
|
||||||
|
"LDAP_CONTROL_ASSERT",
|
||||||
|
"LDAP_CONTROL_PRE_READ",
|
||||||
|
"LDAP_CONTROL_POST_READ",
|
||||||
|
"LDAP_CONTROL_SORTREQUEST",
|
||||||
|
"LDAP_CONTROL_SORTRESPONSE",
|
||||||
|
"LDAP_CONTROL_PAGEDRESULTS",
|
||||||
|
"LDAP_CONTROL_SYNC",
|
||||||
|
"LDAP_CONTROL_SYNC_STATE",
|
||||||
|
"LDAP_CONTROL_SYNC_DONE",
|
||||||
|
"LDAP_CONTROL_DONTUSECOPY",
|
||||||
|
"LDAP_CONTROL_PASSWORDPOLICYREQUEST",
|
||||||
|
"LDAP_CONTROL_PASSWORDPOLICYRESPONSE",
|
||||||
|
"LDAP_CONTROL_X_INCREMENTAL_VALUES",
|
||||||
|
"LDAP_CONTROL_X_DOMAIN_SCOPE",
|
||||||
|
"LDAP_CONTROL_X_PERMISSIVE_MODIFY",
|
||||||
|
"LDAP_CONTROL_X_SEARCH_OPTIONS",
|
||||||
|
"LDAP_CONTROL_X_TREE_DELETE",
|
||||||
|
"LDAP_CONTROL_X_EXTENDED_DN",
|
||||||
|
"LDAP_CONTROL_VLVREQUEST",
|
||||||
|
"LDAP_CONTROL_VLVRESPONSE",
|
||||||
|
"LDAP_EXOP_MODIFY_PASSWD",
|
||||||
|
"LDAP_EXOP_REFRESH",
|
||||||
|
"LDAP_EXOP_START_TLS",
|
||||||
|
"LDAP_EXOP_TURN",
|
||||||
|
"LDAP_EXOP_WHO_AM_I",
|
||||||
|
"LDAP_CONTROL_AUTHZID_REQUEST",
|
||||||
|
"LDAP_CONTROL_AUTHZID_RESPONSE",
|
||||||
|
];
|
||||||
|
|
||||||
|
const ROOT_DSE_LITERALS = [
|
||||||
|
# Constantes non définies de façon normalisée
|
||||||
|
["1.3.6.1.1.8", "/*Cancel Extended Request*/ \"1.3.6.1.1.8\""],
|
||||||
|
["1.3.6.1.1.14", "/*Modify-Increment*/ \"1.3.6.1.1.14\""],
|
||||||
|
["1.3.6.1.4.1.4203.1.5.1", "/*All Op Attrs*/ \"1.3.6.1.4.1.4203.1.5.1\""],
|
||||||
|
["1.3.6.1.4.1.4203.1.5.2", "/*OC AD Lists*/ \"1.3.6.1.4.1.4203.1.5.2\""],
|
||||||
|
["1.3.6.1.4.1.4203.1.5.3", "/*LDAP Protocol Mechanism*/ \"1.3.6.1.4.1.4203.1.5.3\""],
|
||||||
|
["1.3.6.1.4.1.4203.1.5.4", "/*draft-zeilenga-ldap-rfc2596*/ \"1.3.6.1.4.1.4203.1.5.4\""],
|
||||||
|
["1.3.6.1.4.1.4203.1.5.5", "/*draft-zeilenga-ldap-rfc2596*/ \"1.3.6.1.4.1.4203.1.5.5\""],
|
||||||
|
];
|
||||||
|
}
|
||||||
96
src/ldap/filters.php
Normal file
96
src/ldap/filters.php
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
class filters {
|
||||||
|
private static function _escape(array $parts): string {
|
||||||
|
$op = false;
|
||||||
|
$first = true;
|
||||||
|
$fparts = [];
|
||||||
|
$index = 0;
|
||||||
|
foreach ($parts as $name => $part) {
|
||||||
|
if ($first) {
|
||||||
|
$first = false;
|
||||||
|
switch ($part) {
|
||||||
|
case "&": case "and": $op = "&"; break;
|
||||||
|
case "|": case "or": $op = "|"; break;
|
||||||
|
case "!": case "not": $op = "!"; break;
|
||||||
|
}
|
||||||
|
if ($op) {
|
||||||
|
if ($index === $name) $index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($index === $name) {
|
||||||
|
# séquentiel
|
||||||
|
$index++;
|
||||||
|
if (is_array($part)) {
|
||||||
|
$fparts[] = self::_escape($part);
|
||||||
|
} else {
|
||||||
|
str::add_prefix($part, "(");
|
||||||
|
str::add_suffix($part, ")");
|
||||||
|
$fparts[] = $part;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# associatif
|
||||||
|
$name = ldap_escape($name, "", LDAP_ESCAPE_FILTER);
|
||||||
|
foreach (A::with($part) as $value) {
|
||||||
|
$value = ldap_escape($value, "", LDAP_ESCAPE_FILTER);
|
||||||
|
$fparts[] = "($name=$value)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$filter = implode("", $fparts);
|
||||||
|
if (count($fparts) > 1 || $op === "!") {
|
||||||
|
if (!$op) $op = "&";
|
||||||
|
$filter = "($op$filter)";
|
||||||
|
}
|
||||||
|
return $filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function parse($filter): string {
|
||||||
|
if (!$filter) $filter = "objectClass=*";
|
||||||
|
return self::_escape(A::with($filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
static function not(string $filter): string {
|
||||||
|
str::add_prefix($filter, "(");
|
||||||
|
str::add_suffix($filter, ")");
|
||||||
|
return "(!$filter)";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** mettre en échappement ($attr$op$value) en ignorant les wildcards */
|
||||||
|
private static function _filter(string $name, string $op, string $value): string {
|
||||||
|
$name = ldap_escape($name, "*", LDAP_ESCAPE_FILTER);
|
||||||
|
$value = ldap_escape($value, "*", LDAP_ESCAPE_FILTER);
|
||||||
|
return "($name$op$value)";
|
||||||
|
}
|
||||||
|
|
||||||
|
static function exists(string $name): string {
|
||||||
|
return self::_filter($name, "=", "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function eq(string $name, string $value): string {
|
||||||
|
return self::_filter($name, "=", $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function ge(string $name, string $value): string {
|
||||||
|
return self::_filter($name, ">=", $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function le(string $name, string $value): string {
|
||||||
|
return self::_filter($name, "<=", $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function gt(string $name, string $value): string {
|
||||||
|
return self::not(self::le($name, $value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static function lt(string $name, string $value): string {
|
||||||
|
return self::not(self::ge($name, $value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static function approx(string $name, string $value): string {
|
||||||
|
return self::_filter($name, "~=", $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/ldap/io/LdapWriter.php
Normal file
25
src/ldap/io/LdapWriter.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\io;
|
||||||
|
|
||||||
|
use nulib\file;
|
||||||
|
use nulib\file\IWriter;
|
||||||
|
use nulib\ldap\LdapObject;
|
||||||
|
|
||||||
|
abstract class LdapWriter {
|
||||||
|
static function write_object($output, LdapObject $object, ?array $names=null): void {
|
||||||
|
$writer = new static($output);
|
||||||
|
$writer->write($object, $names);
|
||||||
|
$writer->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct($output=null) {
|
||||||
|
$this->writer = file::writer($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var IWriter */
|
||||||
|
protected $writer;
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
$this->writer->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/ldap/io/LdifWriter.php
Normal file
29
src/ldap/io/LdifWriter.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\io;
|
||||||
|
|
||||||
|
use nulib\ldap\LdapObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LdifWriter
|
||||||
|
*/
|
||||||
|
class LdifWriter extends LdapWriter {
|
||||||
|
function write(?LdapObject $object, ?array $names=null): self {
|
||||||
|
if ($object !== null) {
|
||||||
|
$writer = $this->writer;
|
||||||
|
if ($names === null) $names = $object->keys();
|
||||||
|
if (!in_array("dn", $names)) {
|
||||||
|
array_unshift($names, "dn");
|
||||||
|
}
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$values = $object->_get($name)->array();
|
||||||
|
if ($values !== null) {
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$writer->fwrite("$name: $value\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$writer->fwrite("\n");
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/ldap/io/YamlWriter.php
Normal file
30
src/ldap/io/YamlWriter.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\io;
|
||||||
|
|
||||||
|
|
||||||
|
use nulib\ext\yaml;
|
||||||
|
use nulib\ldap\LdapObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class YamlWriter
|
||||||
|
*/
|
||||||
|
class YamlWriter extends LdapWriter {
|
||||||
|
function write(?LdapObject $object, ?array $names=null): self {
|
||||||
|
if ($object !== null) {
|
||||||
|
if ($names === null) $names = $object->keys();
|
||||||
|
if (!in_array("dn", $names)) {
|
||||||
|
array_unshift($names, "dn");
|
||||||
|
}
|
||||||
|
$values = [];
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$value = $object->all($name);
|
||||||
|
if (count($value) == 1) $value = $value[0];
|
||||||
|
$values[$name] = $value;
|
||||||
|
}
|
||||||
|
$writer = $this->writer;
|
||||||
|
$writer->fwrite(yaml::with($values));
|
||||||
|
$writer->fwrite("\n");
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/ldap/labels.php
Normal file
22
src/ldap/labels.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
class labels {
|
||||||
|
static function strip(?string &$value, ?string &$label=null): void {
|
||||||
|
if ($value === null) return;
|
||||||
|
if (preg_match('/^(\{[^}]*})/', $value, $ms, PREG_OFFSET_CAPTURE)) {
|
||||||
|
$label = $ms[1][0];
|
||||||
|
$value = substr($value, $ms[1][1] + strlen($label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function add(?string $value, ?string $label): ?string {
|
||||||
|
if ($value === null) return null;
|
||||||
|
if ($label !== null) {
|
||||||
|
str::add_prefix($label, "{");
|
||||||
|
str::add_suffix($label, "}");
|
||||||
|
}
|
||||||
|
return "$label$value";
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/ldap/ldap.php
Normal file
157
src/ldap/ldap.php
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
class ldap {
|
||||||
|
#############################################################################
|
||||||
|
const ADD_SCHEMA = [
|
||||||
|
"controls" => ["array", []],
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var Metadata */
|
||||||
|
private static $add_md;
|
||||||
|
static function add_md(): Metadata {
|
||||||
|
return md_utils::ensure_md(self::$add_md, self::ADD_SCHEMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function add($conn, string $dn, array $attrs, $params=null): void {
|
||||||
|
self::add_md()->ensureSchema($params);
|
||||||
|
$r = LdapException::check("add", $conn
|
||||||
|
, @ldap_add_ext($conn, $dn, $attrs, $params["controls"]));
|
||||||
|
LdapException::check_result("add", $conn, $r);
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
const MODIFY_SCHEMA = [
|
||||||
|
"controls" => ["array", []],
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var Metadata */
|
||||||
|
private static $modify_md;
|
||||||
|
static function modify_md(): Metadata {
|
||||||
|
return md_utils::ensure_md(self::$modify_md, self::MODIFY_SCHEMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function prepare_modify(array $modattrs): array {
|
||||||
|
$modifs = [];
|
||||||
|
foreach ($modattrs as $modattr) {
|
||||||
|
$modtype = false;
|
||||||
|
$first = true;
|
||||||
|
$index = 0;
|
||||||
|
foreach ($modattr as $name => $value) {
|
||||||
|
if ($first && $name === $index) {
|
||||||
|
$first = false;
|
||||||
|
$index++;
|
||||||
|
switch ($value) {
|
||||||
|
case "add":
|
||||||
|
$modtype = LDAP_MODIFY_BATCH_ADD;
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
$modtype = LDAP_MODIFY_BATCH_REMOVE;
|
||||||
|
break;
|
||||||
|
case "replace":
|
||||||
|
$modtype = LDAP_MODIFY_BATCH_REPLACE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($name === $index) {
|
||||||
|
$index++;
|
||||||
|
$modifs[] = [
|
||||||
|
"modtype" => LDAP_MODIFY_BATCH_REMOVE_ALL,
|
||||||
|
"attrib" => $value,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$modifs[] = [
|
||||||
|
"modtype" => $modtype,
|
||||||
|
"attrib" => $name,
|
||||||
|
"values" => $value
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $modifs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function modify($conn, string $dn, array $modattrs, $params=null): void {
|
||||||
|
self::modify_md()->ensureSchema($params);
|
||||||
|
$modifs = self::prepare_modify($modattrs);
|
||||||
|
LdapException::check("modify", $conn
|
||||||
|
, @ldap_modify_batch($conn, $dn, $modifs, $params["controls"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
const RENAME_SCHEMA = [
|
||||||
|
"new_parent" => ["?string", null],
|
||||||
|
"delete_old_rdn" => ["bool", true],
|
||||||
|
"controls" => ["array", []],
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var Metadata */
|
||||||
|
private static $rename_md;
|
||||||
|
static function rename_md(): Metadata {
|
||||||
|
return md_utils::ensure_md(self::$rename_md, self::RENAME_SCHEMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* préparer les paramètres pour le renommage
|
||||||
|
*
|
||||||
|
* si $newRdn n'est pas vide:
|
||||||
|
* - si $params["new_parent"] n'est pas spécifié ou null, alors on ne fait
|
||||||
|
* qu'un renommage: prendre le suffixe de $dn
|
||||||
|
* - sinon, le nouveau DN est "$newRdn,$params[new_parent]"
|
||||||
|
*
|
||||||
|
* si $newRdn est vide:
|
||||||
|
* - il s'agit d'un déplacement de branche. $params["new_parent"] ne doit pas
|
||||||
|
* être vide et c'est la nouvelle destination. le RDN n'est pas modifié
|
||||||
|
*/
|
||||||
|
static function prepare_rename(string $dn, string &$newRdn, &$params = null): bool {
|
||||||
|
self::rename_md()->ensureSchema($params);
|
||||||
|
names::split_dn($dn, $origRdn, $origParent);
|
||||||
|
$newParent = $params["new_parent"];
|
||||||
|
if ($newRdn != "") {
|
||||||
|
# renommage et éventuellement déplacement
|
||||||
|
if (strpos($newRdn, "=") === false) {
|
||||||
|
# si le rdn ne comporte que la valeur, alors prendre le nom de
|
||||||
|
# l'attribut depuis origRdn
|
||||||
|
$name = A::first_key(names::split_rdn($origRdn));
|
||||||
|
$newRdn = names::build_rdn($name, $newRdn);
|
||||||
|
}
|
||||||
|
if ($newParent === null) $newParent = $origParent;
|
||||||
|
} else {
|
||||||
|
# déplacement avec le même RDN
|
||||||
|
$newRdn = $origRdn;
|
||||||
|
}
|
||||||
|
$newDn = names::join($newRdn, $newParent);
|
||||||
|
names::split_dn($newDn, $newRdn, $newParent);
|
||||||
|
$params["new_parent"] = $newParent;
|
||||||
|
return $newDn !== $dn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function rename($conn, string $dn, string $newRdn, array $params): string {
|
||||||
|
$newParent = $params["new_parent"];
|
||||||
|
$r = LdapException::check("rename", $conn
|
||||||
|
, @ldap_rename_ext($conn, $dn, $newRdn, $newParent
|
||||||
|
, $params["delete_old_rdn"], $params["controls"]));
|
||||||
|
LdapException::check_result("rename", $conn, $r);
|
||||||
|
return names::join($newRdn, $newParent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
const DELETE_SCHEMA = [
|
||||||
|
"controls" => ["array", []],
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var Metadata */
|
||||||
|
private static $delete_md;
|
||||||
|
static function delete_md(): Metadata {
|
||||||
|
return md_utils::ensure_md(self::$delete_md, self::DELETE_SCHEMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function delete($conn, string $dn, $params=null): void {
|
||||||
|
self::delete_md()->ensureSchema($params);
|
||||||
|
$r = LdapException::check("delete", $conn
|
||||||
|
, @ldap_delete_ext($conn, $dn, $params["controls"]));
|
||||||
|
LdapException::check_result("delete", $conn, $r);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/ldap/ldap_config.php
Normal file
31
src/ldap/ldap_config.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
class ldap_config {
|
||||||
|
static function get_shared_file(string $uri): string {
|
||||||
|
if ($uri == "ldapi://") {
|
||||||
|
$file = "ldapi__.ldaphost";
|
||||||
|
} else {
|
||||||
|
$parts = parse_url($uri);
|
||||||
|
if ($parts === false) throw ValueException::invalid_value($uri, "uri");
|
||||||
|
$scheme = A::get($parts, "scheme", "ldap");
|
||||||
|
$host = A::get($parts, "host");
|
||||||
|
$port = A::get($parts, "port");
|
||||||
|
if ($port === null) {
|
||||||
|
if ($scheme === "ldap") $port = 389;
|
||||||
|
elseif ($scheme === "ldaps") $port = 636;
|
||||||
|
}
|
||||||
|
$file = "${scheme}_${host}_${port}.ldaphost";
|
||||||
|
}
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function get_file(string $file, ?string $profile=null): string {
|
||||||
|
if (!path::is_qualified($file) && !path::have_ext($file)) {
|
||||||
|
if ($profile !== null) $file .= ".$profile";
|
||||||
|
$file .= ".ldapconf";
|
||||||
|
}
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/ldap/ldap_server.php
Normal file
61
src/ldap/ldap_server.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
use nulib\output\msg;
|
||||||
|
|
||||||
|
abstract class ldap_server {
|
||||||
|
const NAME = null;
|
||||||
|
|
||||||
|
protected static function name(?string $suffix=null): string {
|
||||||
|
$name = static::NAME;
|
||||||
|
if ($suffix !== null) {
|
||||||
|
$name .= "_";
|
||||||
|
$name .= $suffix;
|
||||||
|
}
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function map_profile(string $profile): string {
|
||||||
|
$profile_map = config::k(self::name("profile_map"));
|
||||||
|
return A::get($profile_map, $profile, $profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static $profile;
|
||||||
|
|
||||||
|
/** obtenir le profil LDAP courant */
|
||||||
|
static function get_profile(): string {
|
||||||
|
$profile = self::$profile;
|
||||||
|
if ($profile === null) {
|
||||||
|
if ($profile === null) $profile = config::k(self::name("profile"));
|
||||||
|
if ($profile === null) $profile = self::map_profile(config::get_profile());
|
||||||
|
self::$profile = $profile;
|
||||||
|
}
|
||||||
|
return $profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** spécifier le profil LDAP courant */
|
||||||
|
static function set_profile(?string $profile): void {
|
||||||
|
if ($profile === null) $profile = config::get_profile();
|
||||||
|
self::$profile = self::map_profile($profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** adapter le chemin vers le fichier de configuration */
|
||||||
|
protected static function fix_path(string $config): string {
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function conn(?array $config=null, ?string $profile=null): LdapConn {
|
||||||
|
if ($profile === null) $profile = self::get_profile();
|
||||||
|
$name = self::name();
|
||||||
|
msg::debug("Profil $name: $profile");
|
||||||
|
$configFile = static::fix_path(ldap_config::get_file($name, $profile));
|
||||||
|
if (!file_exists($configFile)) {
|
||||||
|
$configname = path::filename($configFile);
|
||||||
|
throw new ValueException("$name: profil LDAP invalide (fichier '$configname' non trouvé)");
|
||||||
|
}
|
||||||
|
return new LdapConn(array_merge(...SL::filter_n([
|
||||||
|
require $configFile,
|
||||||
|
$config,
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/ldap/names.php
Normal file
89
src/ldap/names.php
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
class names {
|
||||||
|
static function split_dn(string $dn, ?string &$rdn, ?string &$parent_dn): bool {
|
||||||
|
$dparts = ldap_explode_dn($dn, 0);
|
||||||
|
$count = $dparts["count"];
|
||||||
|
if ($count > 0) {
|
||||||
|
$rdn = $dparts[0];
|
||||||
|
$sparts = [];
|
||||||
|
for ($i = 1; $i < $count; $i++) {
|
||||||
|
$sparts[] = $dparts[$i];
|
||||||
|
}
|
||||||
|
$parent_dn = implode(",", $sparts);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function ldap_unescape($string) {
|
||||||
|
$hex2bin = function ($ms) {
|
||||||
|
$m = array_shift($ms);
|
||||||
|
return hex2bin(substr($m, 1));
|
||||||
|
};
|
||||||
|
return preg_replace_callback('/\\\\[0-9a-fA-F]{2}/', $hex2bin, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function split_rdn(string $rdn): array {
|
||||||
|
$attrs = [];
|
||||||
|
$rparts = explode("+", $rdn);
|
||||||
|
foreach ($rparts as $rpart) {
|
||||||
|
if (strpos($rpart, "=") === false) {
|
||||||
|
throw ValueException::invalid_value($rdn, "rdn");
|
||||||
|
}
|
||||||
|
[$name, $value] = explode("=", $rpart, 2);
|
||||||
|
$name = self::ldap_unescape($name);
|
||||||
|
$value = self::ldap_unescape($value);
|
||||||
|
$attrs[$name][] = $value;
|
||||||
|
}
|
||||||
|
return $attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function build_rdn(string $name, string $value): string {
|
||||||
|
$name = ldap_escape($name, 0, LDAP_ESCAPE_DN);
|
||||||
|
$value = ldap_escape($value, 0, LDAP_ESCAPE_DN);
|
||||||
|
return "$name=$value";
|
||||||
|
}
|
||||||
|
|
||||||
|
static function get_dn_names(?string $dn, ?array $lkeys2names=null): ?array {
|
||||||
|
$dn_names = null;
|
||||||
|
if ($dn !== null) {
|
||||||
|
$dn_names = [];
|
||||||
|
if (self::split_dn($dn, $rdn, $parent_dn)) {
|
||||||
|
foreach (array_keys(self::split_rdn($rdn)) as $name) {
|
||||||
|
$dn_names[] = A::get($lkeys2names, strtolower($name), $name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $dn_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function join($rdn, string $parent_dn): string {
|
||||||
|
if (is_array($rdn)) {
|
||||||
|
$rparts = [];
|
||||||
|
foreach ($rdn as $name => $values) {
|
||||||
|
$name = ldap_escape($name, 0, LDAP_ESCAPE_DN);
|
||||||
|
foreach (A::with($values) as $value) {
|
||||||
|
$value = ldap_escape($value, 0, LDAP_ESCAPE_DN);
|
||||||
|
$rparts[] = "$name=$value";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$rdn = implode("+", $rparts);
|
||||||
|
}
|
||||||
|
$dparts = [];
|
||||||
|
if ($rdn) $dparts[] = $rdn;
|
||||||
|
if ($parent_dn) $dparts[] = $parent_dn;
|
||||||
|
return implode(",", $dparts);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** tester si $dn a le suffixe $suffix */
|
||||||
|
static function have_suffix(string $dn, string $suffix): bool {
|
||||||
|
$dparts = ldap_explode_dn($dn, 0);
|
||||||
|
$sparts = ldap_explode_dn($suffix, 0);
|
||||||
|
$count = $sparts["count"];
|
||||||
|
return array_slice($dparts, -$count) === array_slice($sparts, -$count);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/ldap/scheman.php
Normal file
30
src/ldap/scheman.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class scheman: gestionnaire de schéma global partagé
|
||||||
|
*
|
||||||
|
* Cette classe ne peut être utilisée correctement que pour une seule instance
|
||||||
|
* de {@link LdapConn}
|
||||||
|
*/
|
||||||
|
class scheman {
|
||||||
|
/** @var SchemaManager */
|
||||||
|
protected static $scheman;
|
||||||
|
|
||||||
|
static function init(LdapConn $conn, ?array $overrides=null): void {
|
||||||
|
self::$scheman = new SchemaManager($conn, $overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function autogen_schema(array $objectClasses): array {
|
||||||
|
return self::$scheman->autogenSchema($objectClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function autogen_properties(array $schema): array {
|
||||||
|
return self::$scheman->autogenProperties($schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function autogen_methods(array $schema): array {
|
||||||
|
return self::$scheman->autogenMethods($schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
245
src/ldap/schemas/LdapSchemaExtractor.php
Normal file
245
src/ldap/schemas/LdapSchemaExtractor.php
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\schemas;
|
||||||
|
|
||||||
|
|
||||||
|
use nulib\A;
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\ldap\consts;
|
||||||
|
use nulib\ldap\LdapAttr;
|
||||||
|
use nulib\ldap\LdapConn;
|
||||||
|
use nulib\ldap\syntaxes\BinarySyntax;
|
||||||
|
use nulib\ldap\syntaxes\StringSyntax;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LdapSchemaExtractor: extracteur de schéma LDAP, pour utilisation avec
|
||||||
|
* PHP
|
||||||
|
*/
|
||||||
|
class LdapSchemaExtractor {
|
||||||
|
function __construct(?array $schemaInfos=null) {
|
||||||
|
if ($schemaInfos !== null) {
|
||||||
|
[
|
||||||
|
"ldap_syntaxes" => $this->ldapSyntaxes,
|
||||||
|
"attribute_types" => $this->attributeTypes,
|
||||||
|
"object_classes" => $this->objectClasses,
|
||||||
|
] = $schemaInfos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $ldapSyntaxes;
|
||||||
|
|
||||||
|
protected $attributeTypes;
|
||||||
|
|
||||||
|
protected $objectClasses;
|
||||||
|
|
||||||
|
function loadSchema(LdapConn $conn): array {
|
||||||
|
$schema = null;
|
||||||
|
$schemaDn = $conn->getRootDse()->first("subschemaSubentry");
|
||||||
|
if ($schemaDn !== null) {
|
||||||
|
$schema = $conn->empty()->load($schemaDn, $conn->_search($schemaDn, [
|
||||||
|
"suffix" => "",
|
||||||
|
"attrs" => [
|
||||||
|
"ldapSyntaxes",
|
||||||
|
"attributeTypes",
|
||||||
|
"objectClasses",
|
||||||
|
],
|
||||||
|
"scope" => "base",
|
||||||
|
])->first());
|
||||||
|
}
|
||||||
|
if ($schema === null) {
|
||||||
|
throw new IllegalAccessException("unable to find subschemaSubentry attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
$parser = new LseSyntax();
|
||||||
|
$ldapSyntaxes = [];
|
||||||
|
foreach ($schema->get("ldapSyntaxes", []) as $ldapSyntax) {
|
||||||
|
$ldapSyntax = $parser->parse($ldapSyntax);
|
||||||
|
$ldapSyntaxes[$ldapSyntax["oid"]] = $ldapSyntax;
|
||||||
|
}
|
||||||
|
$parser = new LseAttribute();
|
||||||
|
$attributeTypes = [];
|
||||||
|
foreach ($schema->get("attributeTypes", []) as $attributeType) {
|
||||||
|
$attributeType = $parser->parse($attributeType);
|
||||||
|
$attributeTypes[$attributeType["oid"]] = $attributeType;
|
||||||
|
}
|
||||||
|
$parser = new LseObjectClass();
|
||||||
|
$objectClasses = [];
|
||||||
|
foreach ($schema->get("objectClasses", []) as $objectClass) {
|
||||||
|
$objectClass = $parser->parse($objectClass);
|
||||||
|
$objectClasses[$objectClass["oid"]] = $objectClass;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"ldap_syntaxes" => $this->ldapSyntaxes = $ldapSyntaxes,
|
||||||
|
"attribute_types" => $this->attributeTypes = $attributeTypes,
|
||||||
|
"object_classes" => $this->objectClasses = $objectClasses,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $syntaxes;
|
||||||
|
protected $attributes;
|
||||||
|
protected $canonAttrs;
|
||||||
|
protected $classes;
|
||||||
|
protected $canonClasses;
|
||||||
|
|
||||||
|
function init(): array {
|
||||||
|
## calculer la liste des syntaxes, et les classer par OID
|
||||||
|
$ldapSyntaxes = $this->ldapSyntaxes;
|
||||||
|
# rajouter une liste connue de syntaxes
|
||||||
|
A::merge($ldapSyntaxes, consts::KNOWN_SLAPD_SYNTAXES);
|
||||||
|
$syntaxes = [];
|
||||||
|
foreach ($ldapSyntaxes as $syntax) {
|
||||||
|
$oid = $syntax["oid"];
|
||||||
|
# si la syntaxe a déjà été définie, ignorer
|
||||||
|
if (array_key_exists($oid, $syntaxes)) continue;
|
||||||
|
$class = cl::get(consts::KNOWN_SYNTAX_CLASSES, $oid);
|
||||||
|
if ($class === null) {
|
||||||
|
$binary = $syntax["x_not_human_readable"] || $syntax["x_binary_transfer_required"];
|
||||||
|
$class = $binary? BinarySyntax::class: StringSyntax::class;
|
||||||
|
}
|
||||||
|
$syntax["class"] = $class;
|
||||||
|
$syntaxes[$oid] = $syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
## calculer la liste des attributs, et les classer par nom canonique
|
||||||
|
$attributes = [];
|
||||||
|
$canonAttrs = [];
|
||||||
|
foreach ($this->attributeTypes as $attribute) {
|
||||||
|
$names = $attribute["names"];
|
||||||
|
$canonName = $names[0];
|
||||||
|
$attribute["name"] = $canonName;
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$canonAttrs[strtolower($name)] = $canonName;
|
||||||
|
}
|
||||||
|
$attribute["class"] = cl::pget($syntaxes, [$attribute["syntax"], "class"]);
|
||||||
|
$attributes[strtolower($canonName)] = $attribute;
|
||||||
|
}
|
||||||
|
# résoudre l'héritage des attributs
|
||||||
|
foreach ($attributes as &$attribute) {
|
||||||
|
foreach ($attribute["sups"] as $sup) {
|
||||||
|
$sup = strtolower(cl::get($canonAttrs, strtolower($sup), $sup));
|
||||||
|
A::update_n($attribute, $attributes[$sup]);
|
||||||
|
}
|
||||||
|
}; unset($attribute);
|
||||||
|
# puis mettre à false les valeurs booléennes nulles
|
||||||
|
foreach ($attributes as &$attribute) {
|
||||||
|
foreach (LseAttribute::BOOL_ATTRS as $name) {
|
||||||
|
$attribute[$name] = boolval($attribute[$name]);
|
||||||
|
}
|
||||||
|
}; unset($attribute);
|
||||||
|
|
||||||
|
## calculer la liste des classes, et les classer par nom canonique.
|
||||||
|
## les noms des attributs sont aussi canonisés
|
||||||
|
$classes = [];
|
||||||
|
$canonClasses = [];
|
||||||
|
foreach ($this->objectClasses as $class) {
|
||||||
|
$names = $class["names"];
|
||||||
|
$canonName = $names[0];
|
||||||
|
$class["name"] = $canonName;
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$canonClasses[strtolower($name)] = $canonName;
|
||||||
|
}
|
||||||
|
$musts = cl::with($class["musts"]);
|
||||||
|
foreach ($musts as &$name) {
|
||||||
|
$name = cl::get($canonAttrs, strtolower($name), $name);
|
||||||
|
}; unset($name);
|
||||||
|
$class["musts"] = $musts;
|
||||||
|
$mays = cl::with($class["mays"]);
|
||||||
|
foreach ($mays as &$name) {
|
||||||
|
$name = cl::get($canonAttrs, strtolower($name), $name);
|
||||||
|
}; unset($name);
|
||||||
|
$class["mays"] = $mays;
|
||||||
|
$class["attrs"] = array_merge($musts, $mays);
|
||||||
|
$classes[strtolower($canonName)] = $class;
|
||||||
|
}
|
||||||
|
# résoudre l'héritage des classes
|
||||||
|
foreach ($classes as &$class) {
|
||||||
|
foreach ($class["sups"] as $sup) {
|
||||||
|
$sup = strtolower(cl::get($canonAttrs, strtolower($sup), $sup));
|
||||||
|
$sup = $classes[$sup];
|
||||||
|
A::update_n($class, $sup);
|
||||||
|
A::merge($class["musts"], $sup["musts"]);
|
||||||
|
A::merge($class["mays"], $sup["mays"]);
|
||||||
|
}
|
||||||
|
}; unset($class);
|
||||||
|
|
||||||
|
## fin de l'initialisation
|
||||||
|
return [
|
||||||
|
"syntaxes" => $this->syntaxes = $syntaxes,
|
||||||
|
"attributes" => $this->attributes = $attributes,
|
||||||
|
"canon_attrs" => $this->canonAttrs = $canonAttrs,
|
||||||
|
"classes" => $this->classes = $classes,
|
||||||
|
"canon_classes" => $this->canonClasses = $canonClasses,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAttributes_overrides_SCHEMA = [
|
||||||
|
"name" => "string",
|
||||||
|
"class" => "?string",
|
||||||
|
"set" => "?int",
|
||||||
|
"reset" => "?int",
|
||||||
|
];
|
||||||
|
/** @var Metadata */
|
||||||
|
private static $getAttributes_overrides_md;
|
||||||
|
|
||||||
|
function getAttributes(array $objectClasses, ?array $overrides=null): array {
|
||||||
|
if ($overrides !== null) {
|
||||||
|
$tmp = [];
|
||||||
|
foreach ($overrides as $name => $override) {
|
||||||
|
$attribute = ValueException::check_nn(
|
||||||
|
cl::get($this->attributes, strtolower($name))
|
||||||
|
, "$name: attribut non défini");
|
||||||
|
$tmp[$attribute["name"]] = $override;
|
||||||
|
}
|
||||||
|
$overrides = $tmp;
|
||||||
|
$md = md_utils::ensure_md(self::$getAttributes_overrides_md, self::getAttributes_overrides_SCHEMA);
|
||||||
|
$md->eachEnsureSchema($overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
$nameRequired = [];
|
||||||
|
foreach ($objectClasses as $name) {
|
||||||
|
$name = cl::get($this->canonClasses, strtolower($name), $name);
|
||||||
|
$class = ValueException::check_nn(
|
||||||
|
cl::get($this->classes, strtolower($name))
|
||||||
|
, "$name: classe non définie");
|
||||||
|
foreach ($class["musts"] as $must) {
|
||||||
|
$nameRequired[$must] = true;
|
||||||
|
}
|
||||||
|
foreach ($class["mays"] as $may) {
|
||||||
|
A::replace_nx($nameRequired, $may, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$attributes = [
|
||||||
|
"dn" => [
|
||||||
|
"name" => "dn",
|
||||||
|
"class" => StringSyntax::class,
|
||||||
|
"flags" => LdapAttr::MONOVALUED,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
foreach ($nameRequired as $name => $required) {
|
||||||
|
$lname = strtolower($name);
|
||||||
|
$attribute = ValueException::check_nn(
|
||||||
|
cl::get($this->attributes, $lname)
|
||||||
|
, "$name: attribut non défini");
|
||||||
|
$syntax = ValueException::check_nn(
|
||||||
|
cl::get($this->syntaxes, $attribute["syntax"])
|
||||||
|
, "$attribute[syntax]: syntaxe non définie");
|
||||||
|
$class = $attribute["class"];
|
||||||
|
$monovalued = $attribute["single_value"]? LdapAttr::MONOVALUED: 0;
|
||||||
|
$binary = $syntax["x_binary_transfer_required"]? LdapAttr::BINARY: 0;
|
||||||
|
$ordered = $attribute["x_ordered"]? LdapAttr::ORDERED: 0;
|
||||||
|
$notHumanReadable = $syntax["x_not_human_readable"]? LdapAttr::NOT_HUMAN_READABLE: 0;
|
||||||
|
$flags = $monovalued + $binary + $ordered + $notHumanReadable;
|
||||||
|
$override = cl::get($overrides, $name);
|
||||||
|
if ($override !== null) {
|
||||||
|
if ($override["class"] !== null) $class = $override["class"];
|
||||||
|
if ($override["set"] !== null) $flags = $flags | $override["set"];
|
||||||
|
if ($override["reset"] !== null) $flags = $flags & ~$override["reset"];
|
||||||
|
}
|
||||||
|
$attributes[$lname] = [
|
||||||
|
"name" => $name,
|
||||||
|
"class" => $class,
|
||||||
|
"flags" => $flags,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $attributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/ldap/schemas/LseAttribute.php
Normal file
84
src/ldap/schemas/LseAttribute.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\schemas;
|
||||||
|
|
||||||
|
use nulib\output\log;
|
||||||
|
|
||||||
|
class LseAttribute extends LseParser {
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
const BOOL_ATTRS = [
|
||||||
|
"single_value",
|
||||||
|
"no_user_modification",
|
||||||
|
"x_ordered",
|
||||||
|
"obsolete",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function reset(): array {
|
||||||
|
return $this->data = [
|
||||||
|
"oid" => null,
|
||||||
|
"names" => [],
|
||||||
|
"desc" => null,
|
||||||
|
"sups" => [],
|
||||||
|
"equality" => null,
|
||||||
|
"substr" => null,
|
||||||
|
"ordering" => null,
|
||||||
|
"syntax" => null,
|
||||||
|
"single_value" => null,
|
||||||
|
"no_user_modification" => null,
|
||||||
|
"usage" => null,
|
||||||
|
"x_ordered" => null,
|
||||||
|
"x_origin" => null,
|
||||||
|
"obsolete" => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(?string $s=null): array {
|
||||||
|
if ($s !== null) $this->s = $s;
|
||||||
|
$data = $this->reset();
|
||||||
|
$this->skipLiteral('(');
|
||||||
|
$data["oid"] = self::fix_oid($this->parseName());
|
||||||
|
while ($this->isName()) {
|
||||||
|
$okey = $this->parseName();
|
||||||
|
$key = str_replace("-", "_", strtolower($okey));
|
||||||
|
switch ($key) {
|
||||||
|
case "name":
|
||||||
|
$data["${key}s"] = $this->parseStrings();
|
||||||
|
break;
|
||||||
|
case "sup":
|
||||||
|
$data["${key}s"] = $this->parseNames();
|
||||||
|
break;
|
||||||
|
case "desc":
|
||||||
|
case "x_ordered":
|
||||||
|
case "x_origin":
|
||||||
|
$data[$key] = $this->parseString();
|
||||||
|
break;
|
||||||
|
case "equality":
|
||||||
|
case "substr":
|
||||||
|
case "ordering":
|
||||||
|
case "usage":
|
||||||
|
$data[$key] = $this->parseName();
|
||||||
|
break;
|
||||||
|
case "syntax":
|
||||||
|
$data[$key] = self::fix_oid($this->parseName());
|
||||||
|
break;
|
||||||
|
case "single_value":
|
||||||
|
case "no_user_modification":
|
||||||
|
case "obsolete":
|
||||||
|
$data[$key] = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log::warning("unknown key $okey in |$s|");
|
||||||
|
$data["unknown_keys"][] = $okey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->skipLiteral(')');
|
||||||
|
# ne pas mettre de suite les valeurs false: elle sont mises à jour dans
|
||||||
|
# LdapSchemaExtractor
|
||||||
|
## puis mettre à jour les valeurs booléennes
|
||||||
|
#foreach (self::BOOL_ATTRS as $name) {
|
||||||
|
# $data[$name] = boolval($data[$name]);
|
||||||
|
#}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/ldap/schemas/LseObjectClass.php
Normal file
61
src/ldap/schemas/LseObjectClass.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\schemas;
|
||||||
|
|
||||||
|
use nulib\output\log;
|
||||||
|
|
||||||
|
class LseObjectClass extends LseParser {
|
||||||
|
const BOOL_ATTRS = [];
|
||||||
|
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
protected function reset(): array {
|
||||||
|
return $this->data = [
|
||||||
|
"oid" => null,
|
||||||
|
"names" => [],
|
||||||
|
"desc" => null,
|
||||||
|
"sups" => [],
|
||||||
|
"type" => null,
|
||||||
|
"musts" => null,
|
||||||
|
"mays" => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(?string $s=null): array {
|
||||||
|
if ($s !== null) $this->s = $s;
|
||||||
|
$data = $this->reset();
|
||||||
|
$this->skipLiteral('(');
|
||||||
|
$data["oid"] = self::fix_oid($this->parseName());
|
||||||
|
while ($this->isName()) {
|
||||||
|
$okey = $this->parseName();
|
||||||
|
$key = str_replace("-", "_", strtolower($okey));
|
||||||
|
switch ($key) {
|
||||||
|
case "name":
|
||||||
|
$data["${key}s"] = $this->parseStrings();
|
||||||
|
break;
|
||||||
|
case "sup":
|
||||||
|
case "must":
|
||||||
|
case "may":
|
||||||
|
$data["${key}s"] = $this->parseNames();
|
||||||
|
break;
|
||||||
|
case "desc":
|
||||||
|
$data[$key] = $this->parseString();
|
||||||
|
break;
|
||||||
|
case "abstract":
|
||||||
|
case "structural":
|
||||||
|
case "auxiliary":
|
||||||
|
$data["type"] = $key;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log::warning("unknown key $okey in |$s|");
|
||||||
|
$data["unknown_keys"][] = $okey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->skipLiteral(')');
|
||||||
|
# puis mettre à jour les valeurs booléennes
|
||||||
|
foreach (self::BOOL_ATTRS as $name) {
|
||||||
|
$data[$name] = boolval($data[$name]);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/ldap/schemas/LseParser.php
Normal file
120
src/ldap/schemas/LseParser.php
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\schemas;
|
||||||
|
|
||||||
|
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class LseParser {
|
||||||
|
/** supprimer le {size} à la fin d'un OID */
|
||||||
|
protected static function fix_oid(string $oid): string {
|
||||||
|
return preg_replace('/\{\d+}$/', "", $oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct(?string $s=null) {
|
||||||
|
$this->s = $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function expected(string $expected): ValueException {
|
||||||
|
return new ValueException("expected $expected, got $this->s");
|
||||||
|
}
|
||||||
|
protected function unexpected(string $value): ValueException {
|
||||||
|
return new ValueException("unexpected $value");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $s;
|
||||||
|
|
||||||
|
#~~~~
|
||||||
|
|
||||||
|
const SPACES_PATTERN = '/^\s+/';
|
||||||
|
|
||||||
|
protected function skipSpaces(): void {
|
||||||
|
if (preg_match(self::SPACES_PATTERN, $this->s, $ms)) {
|
||||||
|
$this->s = substr($this->s, strlen($ms[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#~~~~
|
||||||
|
protected function isLiteral(string $literal): bool {
|
||||||
|
return substr($this->s, 0, strlen($literal)) === $literal;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function skipLiteral(string $literal): void {
|
||||||
|
$pos = strlen($literal);
|
||||||
|
if (substr($this->s, 0, $pos) === $literal) {
|
||||||
|
$this->s = substr($this->s, $pos);
|
||||||
|
} else {
|
||||||
|
throw $this->expected($literal);
|
||||||
|
}
|
||||||
|
$this->skipSpaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
#~~~~
|
||||||
|
|
||||||
|
const NAME_PATTERN = '/^\S+/';
|
||||||
|
|
||||||
|
protected function isName(): bool {
|
||||||
|
if (!preg_match(self::NAME_PATTERN, $this->s, $ms)) return false;
|
||||||
|
$name = $ms[0];
|
||||||
|
return !in_array($name, ['(', ')', '$']);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseName(): string {
|
||||||
|
if (!preg_match(self::NAME_PATTERN, $this->s, $ms)) {
|
||||||
|
throw $this->expected("<NAME>");
|
||||||
|
}
|
||||||
|
$name = $ms[0];
|
||||||
|
$this->s = substr($this->s, strlen($name));
|
||||||
|
$this->skipSpaces();
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
#~~~~
|
||||||
|
|
||||||
|
const STRING_PATTERN = "/^'([^']*)'/";
|
||||||
|
|
||||||
|
protected function isString(): bool {
|
||||||
|
return preg_match(self::STRING_PATTERN, $this->s, $ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseString(): string {
|
||||||
|
if (!preg_match(self::STRING_PATTERN, $this->s, $ms)) {
|
||||||
|
throw $this->expected("<STRING>");
|
||||||
|
}
|
||||||
|
$this->s = substr($this->s, strlen($ms[0]));
|
||||||
|
$this->skipSpaces();
|
||||||
|
return $ms[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
#~~~~
|
||||||
|
|
||||||
|
protected function parseNames(): array {
|
||||||
|
if ($this->isName()) return [$this->parseName()];
|
||||||
|
$names = [];
|
||||||
|
if ($this->isLiteral('(')) {
|
||||||
|
$this->skipLiteral('(');
|
||||||
|
while ($this->isName()) {
|
||||||
|
$names[] = $this->parseName();
|
||||||
|
if ($this->isLiteral('$')) $this->skipLiteral('$');
|
||||||
|
}
|
||||||
|
$this->skipLiteral(')');
|
||||||
|
} else {
|
||||||
|
$names[] = $this->parseName();
|
||||||
|
}
|
||||||
|
return $names;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseStrings(): array {
|
||||||
|
if ($this->isString()) return [$this->parseString()];
|
||||||
|
$strings = [];
|
||||||
|
if ($this->isLiteral('(')) {
|
||||||
|
$this->skipLiteral('(');
|
||||||
|
while ($this->isString()) {
|
||||||
|
$strings[] = $this->parseString();
|
||||||
|
}
|
||||||
|
$this->skipLiteral(')');
|
||||||
|
} else {
|
||||||
|
$strings[] = $this->parseString();
|
||||||
|
}
|
||||||
|
return $strings;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/ldap/schemas/LseSyntax.php
Normal file
52
src/ldap/schemas/LseSyntax.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\schemas;
|
||||||
|
|
||||||
|
use nulib\output\log;
|
||||||
|
|
||||||
|
class LseSyntax extends LseParser {
|
||||||
|
const BOOL_ATTRS = [
|
||||||
|
"x_not_human_readable",
|
||||||
|
"x_binary_transfer_required",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
protected function reset(): array {
|
||||||
|
return $this->data = [
|
||||||
|
"oid" => null,
|
||||||
|
"desc" => null,
|
||||||
|
"x_not_human_readable" => null,
|
||||||
|
"x_binary_transfer_required" => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(?string $s=null): array {
|
||||||
|
if ($s !== null) $this->s = $s;
|
||||||
|
$data =$this->reset();
|
||||||
|
$this->skipLiteral('(');
|
||||||
|
$data["oid"] = self::fix_oid($this->parseName());
|
||||||
|
while ($this->isName()) {
|
||||||
|
$okey = $this->parseName();
|
||||||
|
$key = str_replace("-", "_", strtolower($okey));
|
||||||
|
switch ($key) {
|
||||||
|
case "desc":
|
||||||
|
$data[$key] = $this->parseString();
|
||||||
|
break;
|
||||||
|
case "x_not_human_readable":
|
||||||
|
case "x_binary_transfer_required":
|
||||||
|
$data[$key] = boolval($this->parseString());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log::warning("unknown key $okey in $s");
|
||||||
|
$data["unknown_keys"][] = $okey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->skipLiteral(')');
|
||||||
|
# puis mettre à jour les valeurs booléennes
|
||||||
|
foreach (self::BOOL_ATTRS as $name) {
|
||||||
|
$data[$name] = boolval($data[$name]);
|
||||||
|
}
|
||||||
|
return $this->data = $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/ldap/schemas/SchemaManager.php
Normal file
79
src/ldap/schemas/SchemaManager.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\schemas;
|
||||||
|
|
||||||
|
|
||||||
|
use nulib\ldap\LdapAttr;
|
||||||
|
use nulib\ldap\LdapConn;
|
||||||
|
use nulib\ldap\syntaxes\AbstractSyntax;
|
||||||
|
use nulib\ldap\syntaxes\CompositeSyntax;
|
||||||
|
|
||||||
|
class SchemaManager {
|
||||||
|
function __construct(LdapConn $conn, ?array $overrides=null) {
|
||||||
|
$lse = new LdapSchemaExtractor($conn->getSchemaInfos());
|
||||||
|
$lse->init();
|
||||||
|
$this->lse = $lse;
|
||||||
|
$this->overrides = $overrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var LdapSchemaExtractor */
|
||||||
|
protected $lse;
|
||||||
|
|
||||||
|
/** @var array|null */
|
||||||
|
protected $overrides;
|
||||||
|
|
||||||
|
function getAttributes(array $objectClasses): array {
|
||||||
|
return $this->lse->getAttributes($objectClasses, $this->overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var AbstractSyntax[] */
|
||||||
|
protected $syntaxes;
|
||||||
|
|
||||||
|
function getSyntax($class): AbstractSyntax {
|
||||||
|
if (is_array($class)) return func::cons(...$class);
|
||||||
|
$syntax = A::get($this->syntaxes, $class);
|
||||||
|
if ($syntax === null) {
|
||||||
|
$syntax = $this->syntaxes[$class] = func::cons($class);
|
||||||
|
}
|
||||||
|
return $syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
function autogenSchema(array $objectClasses): array {
|
||||||
|
return $this->getAttributes($objectClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function fix_type(AbstractSyntax $syntax, bool $monovalued): array {
|
||||||
|
if ($syntax instanceof CompositeSyntax) {
|
||||||
|
if ($monovalued) $phpType = $syntax->getPhpType();
|
||||||
|
else $phpType = $syntax->getAttrClass();
|
||||||
|
} else {
|
||||||
|
$phpType = $syntax->getPhpType();
|
||||||
|
if (!$monovalued) $phpType .= "[]";
|
||||||
|
}
|
||||||
|
return Autogen::fix_type($phpType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function autogenProperties(array $schema): array {
|
||||||
|
$properties = [];
|
||||||
|
foreach ($schema as $attribute) {
|
||||||
|
$name = $attribute["name"];
|
||||||
|
/** @var AbstractSyntax $syntax */
|
||||||
|
$syntax = $this->getSyntax($attribute["class"]);
|
||||||
|
$monovalued = ($attribute["flags"] & LdapAttr::MONOVALUED) != 0;
|
||||||
|
[$phpType, $returnType] = self::fix_type($syntax, $monovalued);
|
||||||
|
$properties[] = "$returnType \$$name";
|
||||||
|
}
|
||||||
|
return $properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
function autogenMethods(array $schema): array {
|
||||||
|
$methods = [];
|
||||||
|
foreach ($schema as $attribute) {
|
||||||
|
$name = $attribute["name"];
|
||||||
|
/** @var AbstractSyntax $syntax */
|
||||||
|
$syntax = $this->getSyntax($attribute["class"]);
|
||||||
|
$returnType = $syntax instanceof CompositeSyntax? $syntax->getAttrClass(): LdapAttr::class;
|
||||||
|
$methods[] = "\\$returnType $name()";
|
||||||
|
}
|
||||||
|
return $methods;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/ldap/syntaxes/AbstractSyntax.php
Normal file
59
src/ldap/syntaxes/AbstractSyntax.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
|
||||||
|
use nulib\A;
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\ldap\LdapAttr;
|
||||||
|
use nulib\ldap\LdapConn;
|
||||||
|
|
||||||
|
abstract class AbstractSyntax {
|
||||||
|
/** @var LdapConn */
|
||||||
|
protected $conn;
|
||||||
|
|
||||||
|
function initConn(LdapConn $conn) {
|
||||||
|
$this->conn = $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newAttr(string $name, ?array &$values, ?int $flags): LdapAttr {
|
||||||
|
return new LdapAttr($name, $values, $this, $flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPhpType(): ?string {
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws SyntaxException si $value est invalide */
|
||||||
|
abstract function php2ldap($value): ?string;
|
||||||
|
|
||||||
|
abstract function ldap2php(string $value);
|
||||||
|
|
||||||
|
/** transformer les valeurs d'un attribut LDAP en PHP */
|
||||||
|
function fromMultivaluedLdap($values): ?array {
|
||||||
|
A::ensure_narray($values);
|
||||||
|
if ($values !== null) {
|
||||||
|
foreach ($values as &$value) {
|
||||||
|
$value = $this->ldap2php($value);
|
||||||
|
}; unset($value);
|
||||||
|
}
|
||||||
|
return cl::filter_n($values)?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** transformer la valeur d'un attribut LDAP en PHP */
|
||||||
|
function fromMonovaluedLdap($value) {
|
||||||
|
if (is_array($value)) $value = cl::first($value);
|
||||||
|
if ($value === null) return null;
|
||||||
|
else return $this->ldap2php($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** transformer une(des) valeur(s) PHP en attribut LDAP */
|
||||||
|
function fromPhp($values): ?array {
|
||||||
|
A::ensure_narray($values);
|
||||||
|
if ($values !== null) {
|
||||||
|
foreach ($values as &$value) {
|
||||||
|
$value = $this->php2ldap($value);
|
||||||
|
}; unset($value);
|
||||||
|
}
|
||||||
|
return cl::filter_n($values)?: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/ldap/syntaxes/BinarySyntax.php
Normal file
13
src/ldap/syntaxes/BinarySyntax.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
|
||||||
|
class BinarySyntax extends AbstractSyntax {
|
||||||
|
function php2ldap($value): ?string {
|
||||||
|
throw IllegalAccessException::not_implemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ldap2php(string $value) {
|
||||||
|
throw IllegalAccessException::not_implemented();
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/ldap/syntaxes/BooleanSyntax.php
Normal file
22
src/ldap/syntaxes/BooleanSyntax.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
class BooleanSyntax extends AbstractSyntax {
|
||||||
|
function getPhpType(): ?string {
|
||||||
|
return "bool";
|
||||||
|
}
|
||||||
|
|
||||||
|
function php2ldap($value): ?string {
|
||||||
|
if ($value === null) return null;
|
||||||
|
else return $value? "TRUE": "FALSE";
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromPhp($values): ?array {
|
||||||
|
if (is_bool($values)) $values = [$values];
|
||||||
|
return parent::fromPhp($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ldap2php(string $value): bool {
|
||||||
|
return $value === "TRUE";
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/ldap/syntaxes/CompositeSyntax.php
Normal file
96
src/ldap/syntaxes/CompositeSyntax.php
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
|
||||||
|
use nulib\A;
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\ldap\CompositeAttr;
|
||||||
|
use nulib\ldap\CompositeValue;
|
||||||
|
|
||||||
|
class CompositeSyntax extends AbstractSyntax {
|
||||||
|
/**
|
||||||
|
* @var string la classe dérivée de {@link CompositeAttr} qui porte l'attribut
|
||||||
|
*/
|
||||||
|
const CACLASS = CompositeAttr::class;
|
||||||
|
|
||||||
|
function getAttrClass(): string {
|
||||||
|
return static::CACLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newAttr(string $name, ?array &$values, ?int $flags): CompositeAttr {
|
||||||
|
$attrClass = $this->getAttrClass();
|
||||||
|
return new $attrClass($name, $values, $this, $flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string la classe dérivée de {@link CompositeValue} qui porte les
|
||||||
|
* valeurs de cette syntaxe
|
||||||
|
*/
|
||||||
|
const CVCLASS = CompositeValue::class;
|
||||||
|
|
||||||
|
/** retourner la classe d'une valeur composite */
|
||||||
|
function getPhpType(): ?string {
|
||||||
|
return static::CVCLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newCompositeValue(): CompositeValue {
|
||||||
|
$class = $this->getPhpType();
|
||||||
|
/** @var CompositeValue $cvalue */
|
||||||
|
$cvalue = new $class;
|
||||||
|
return $cvalue->setup($this->conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureArray($values): ?array {
|
||||||
|
A::ensure_narray($values);
|
||||||
|
if ($values === null) return null;
|
||||||
|
# déterminer si $values est *une* valeur ou une liste de valeurs
|
||||||
|
$list = false;
|
||||||
|
foreach ($values as $value) {
|
||||||
|
if (is_array($value) || $value instanceof CompositeValue) {
|
||||||
|
$list = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$list) $values = [$values];
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureComposite($value): ?CompositeValue {
|
||||||
|
if ($value === null) return null;
|
||||||
|
if (is_array($value)) {
|
||||||
|
$value = $this->newCompositeValue()->reset($value);
|
||||||
|
}
|
||||||
|
ValueException::check_class($value, $this->getPhpType());
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param ?CompositeValue $value */
|
||||||
|
function php2ldap($value): ?string {
|
||||||
|
$cvalue = $this->ensureComposite($value);
|
||||||
|
if ($cvalue === null) return null;
|
||||||
|
else return $cvalue->formatLdap();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ldap2php(string $value): CompositeValue {
|
||||||
|
return $this->newCompositeValue()->parseLdap($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromMultivaluedLdap($values): ?array {
|
||||||
|
A::ensure_narray($values);
|
||||||
|
if ($values !== null) {
|
||||||
|
$tmp = [];
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$value = $this->ldap2php($value);
|
||||||
|
$key = $value->getKey();
|
||||||
|
$tmp[$key] = $value;
|
||||||
|
}
|
||||||
|
$values = $tmp;
|
||||||
|
}
|
||||||
|
return cl::filter_n($values)?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromPhp($values): ?array {
|
||||||
|
$values = $this->ensureArray($values);
|
||||||
|
return parent::fromPhp($values);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/ldap/syntaxes/DateSyntax.php
Normal file
41
src/ldap/syntaxes/DateSyntax.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
class DateSyntax extends AbstractSyntax {
|
||||||
|
function __construct() {
|
||||||
|
$this->type = new SDatetimeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var SDatetimeType */
|
||||||
|
protected $type;
|
||||||
|
|
||||||
|
|
||||||
|
function php2ldap($value): ?string {
|
||||||
|
$value = $this->type->with($value);
|
||||||
|
if ($value === null) return null;
|
||||||
|
$datetime = new Datetime($value);
|
||||||
|
return $datetime->formatRfc4517();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ldap2php(string $value) {
|
||||||
|
$d = DateTimeImmutable::createFromFormat("YmdHisT", $value);
|
||||||
|
$value = $d->format("d/m/Y H:i:s");
|
||||||
|
$value = preg_replace('/ 00:00:00$/', "", $value);
|
||||||
|
return $value;
|
||||||
|
/*
|
||||||
|
[$y, $m, $d, $H, $M, $S] = [
|
||||||
|
substr($value, 0, 4),
|
||||||
|
substr($value, 4, 2),
|
||||||
|
substr($value, 6, 2),
|
||||||
|
substr($value, 8, 2),
|
||||||
|
substr($value, 10, 2),
|
||||||
|
substr($value, 12, 2),
|
||||||
|
];
|
||||||
|
$datetime = new Datetime(gmmktime($H, $M, $S, $m, $d, $y));
|
||||||
|
$value = preg_replace('/ 00:00:00$/', "", $datetime->format());
|
||||||
|
return $value;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/ldap/syntaxes/IntegerSyntax.php
Normal file
17
src/ldap/syntaxes/IntegerSyntax.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
class IntegerSyntax extends AbstractSyntax {
|
||||||
|
function getPhpType(): ?string {
|
||||||
|
return "int";
|
||||||
|
}
|
||||||
|
|
||||||
|
function php2ldap($value): ?string {
|
||||||
|
if ($value === null) return null;
|
||||||
|
else return strval($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ldap2php(string $value): int {
|
||||||
|
return intval($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/ldap/syntaxes/MailSyntax.php
Normal file
5
src/ldap/syntaxes/MailSyntax.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
class MailSyntax extends StringSyntax {
|
||||||
|
}
|
||||||
20
src/ldap/syntaxes/PostalAddressSyntax.php
Normal file
20
src/ldap/syntaxes/PostalAddressSyntax.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
class PostalAddressSyntax extends StringSyntax {
|
||||||
|
function php2ldap($value): ?string {
|
||||||
|
$value = parent::php2ldap($value);
|
||||||
|
if ($value === null) return null;
|
||||||
|
// mettre en échappement tout caractère $
|
||||||
|
$value = str_replace('$', '\$', $value);
|
||||||
|
$value = preg_replace('/\r?\n/', '$', $value);
|
||||||
|
$value = preg_replace('/\s*(?<!\\\\)\$\s*/', ' $ ', $value);
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ldap2php(string $value): string {
|
||||||
|
$value = preg_replace('/\s*(?<!\\\\)\$\s*/', "\n", $value);
|
||||||
|
$value = preg_replace('/\\\\\$/', '$', $value);
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/ldap/syntaxes/PrintableSyntax.php
Normal file
20
src/ldap/syntaxes/PrintableSyntax.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
class PrintableSyntax extends StringSyntax {
|
||||||
|
const DISALLOWED = '/[^a-zA-Z0-9"()+,-.\/:? -]+/';
|
||||||
|
|
||||||
|
/** enlever les caractères interdit de la chaine */
|
||||||
|
function filter(?string $value): ?string {
|
||||||
|
if ($value === null) return null;
|
||||||
|
return preg_replace(self::DISALLOWED, "", $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function php2ldap($value): ?string {
|
||||||
|
$value = parent::php2ldap($value);
|
||||||
|
if (preg_match(self::DISALLOWED, $value)) {
|
||||||
|
throw new SyntaxException("invalid string: $value");
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/ldap/syntaxes/StringSyntax.php
Normal file
13
src/ldap/syntaxes/StringSyntax.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
class StringSyntax extends AbstractSyntax {
|
||||||
|
function php2ldap($value): ?string {
|
||||||
|
if ($value === null) return null;
|
||||||
|
else return trim(strval($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ldap2php(string $value): string {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/ldap/syntaxes/SyntaxException.php
Normal file
11
src/ldap/syntaxes/SyntaxException.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SyntaxException: indique qu'une valeur PHP ne peut être convertie en
|
||||||
|
* valeur LDAP
|
||||||
|
*/
|
||||||
|
class SyntaxException extends ValueException {
|
||||||
|
}
|
||||||
28
src/ldap/syntaxes/TelephoneSyntax.php
Normal file
28
src/ldap/syntaxes/TelephoneSyntax.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
use nulib\ldap\labels;
|
||||||
|
|
||||||
|
class TelephoneSyntax extends StringSyntax {
|
||||||
|
function __construct() {
|
||||||
|
$this->type = new TelephoneType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var TelephoneType */
|
||||||
|
protected $type;
|
||||||
|
|
||||||
|
function php2ldap($value): ?string {
|
||||||
|
$value = parent::php2ldap($value);
|
||||||
|
if ($value === null) return null;
|
||||||
|
labels::strip($value, $label);
|
||||||
|
$type = $this->type;
|
||||||
|
$value = $type->ensureInternational($type->with($value));
|
||||||
|
return labels::add($value, $label);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ldap2php(string $value): string {
|
||||||
|
labels::strip($value, $label);
|
||||||
|
$value = $this->type->ensureLocal($value);
|
||||||
|
return labels::add($value, $label);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/ldap/syntaxes/cvalues.php
Normal file
16
src/ldap/syntaxes/cvalues.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ldap\syntaxes;
|
||||||
|
|
||||||
|
|
||||||
|
class cvalues {
|
||||||
|
static function autogen_properties(array $schema): array {
|
||||||
|
$md = Metadata::with($schema);
|
||||||
|
$properties = [];
|
||||||
|
foreach ($md->getKeys() as $key) {
|
||||||
|
$type = $md->getType($key);
|
||||||
|
[$phpType, $returnType] = Autogen::fix_type($type->getPhpType());
|
||||||
|
$properties[] = "$returnType \$$key";
|
||||||
|
}
|
||||||
|
return $properties;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user