["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); } /** @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"); } } }