224 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nur\ldap;
 | |
| 
 | |
| use IteratorAggregate;
 | |
| use nulib\output\msg;
 | |
| 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;
 | |
| 
 | |
| 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];
 | |
|   }
 | |
| }
 |