222 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			5.8 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\log;
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
  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,
 | 
						|
    ]);
 | 
						|
    log::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");
 | 
						|
 | 
						|
    // pas trouvé
 | 
						|
    if ($rr === false && ldap_errno($conn) == 32) return;
 | 
						|
    $rr = LdapException::check("search", $conn, $rr);
 | 
						|
 | 
						|
    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
 | 
						|
   */
 | 
						|
  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];
 | 
						|
  }
 | 
						|
}
 |