2024-04-04 22:21:20 +04:00
|
|
|
<?php
|
|
|
|
namespace nur\ldap;
|
|
|
|
|
|
|
|
use IteratorAggregate;
|
|
|
|
use nur\A;
|
|
|
|
use nur\b\params\Parametrable;
|
|
|
|
use nur\b\params\Tparametrable;
|
|
|
|
use nur\b\StopException;
|
|
|
|
use nur\b\ValueException;
|
|
|
|
use nur\data\types\md_utils;
|
|
|
|
use nur\data\types\Metadata;
|
|
|
|
use nur\iter;
|
2024-05-03 10:17:23 +04:00
|
|
|
use nur\sery\output\msg;
|
2024-04-04 22:21:20 +04:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2024-05-16 11:46:37 +04:00
|
|
|
/** @throws LdapException */
|
2024-04-04 22:21:20 +04:00
|
|
|
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,
|
|
|
|
]);
|
2024-05-03 10:17:23 +04:00
|
|
|
msg::debug("Searching searchbase=$args[1] filter=$args[2]");
|
2024-04-04 22:21:20 +04:00
|
|
|
|
|
|
|
$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");
|
|
|
|
|
|
|
|
// pas trouvé
|
|
|
|
if ($rr === false && ldap_errno($conn) == 32) return;
|
|
|
|
$rr = LdapException::check("search", $conn, $rr);
|
|
|
|
|
|
|
|
try {
|
2024-05-16 11:46:37 +04:00
|
|
|
$er = LdapException::check("first_entry", $conn, ldap_first_entry($conn, $rr));
|
2024-04-04 22:21:20 +04:00
|
|
|
while ($er !== false) {
|
|
|
|
$dn = ldap_get_dn($conn, $er);
|
|
|
|
$entry = ldap_get_attributes($conn, $er);
|
|
|
|
yield $dn => $entry;
|
2024-05-16 11:46:37 +04:00
|
|
|
$er = LdapException::check("next_entry", $conn, ldap_next_entry($conn, $er));
|
2024-04-04 22:21:20 +04:00
|
|
|
}
|
|
|
|
} 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
|
2024-05-16 11:46:37 +04:00
|
|
|
*
|
|
|
|
* @throws LdapException
|
2024-04-04 22:21:20 +04:00
|
|
|
*/
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
}
|