nur-sery/nur_src/ldap/LdapSearch.php

224 lines
5.9 KiB
PHP

<?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;
use nur\sery\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];
}
}