Interroger l’ActiveDirectory en PHP

Il y a quelques années de ça, je tentais désespérément de bricoler une appli qui se baserait sur l’AD de mon client préféré pour tout ce qui est authentification et gestion des droits. Ce fût un misérable échec (un parmi tant d’autres), un epic fail même, puisque mon compte perso (je n’en avais pas d’autres) fut bloqué à maintes reprises. Coup de bol, j’étais dans le bureau à côté du support, et je pouvais les harceler pour qu’ils me le réactivent fissa. Je savais que je m’y prenais mal – ce qui du coup bloquait mon compte après plusieurs tentatives de connexion – sans mettre le doigt sur l’origine du problème. Il faut dire que je manquais d’éléments pour faire les choses correctement, les exemples trouvés sur le net étaient des plus catastrophiques, et de guerre lasse, je me plongeais plutôt dans le SAML, vu que Big Client avait mis en place un fort pratique SSO.

Mais aujourd’hui, les choses ont changé, j’ai un compte avec bien plus de droits qu’ils ne m’en faut (aka admin du domaine… le Saint Graal quoi), une bien meilleure connaissance de l’AD chez Big, et un peu de temps pour faire quelques expériences inoffensives. Je me suis donc replongé dans le manuel de PHP, avec la ferme intention d’interroger l’AD et de terminer enfin la série noire des mes échecs passés (et je ne parle que de ceux liés à la programmation. Pour le reste, hmm… c’est pas gagné).

Comme un avion sans L…DAP

Première chose à faire, c’est d’activer l’extension LDAP côté PHP. Pour cela, rien de plus facile, on file éditer le php.ini et on enlève le point-virgule devant la ligne :

;extension=php_ldap.dll

Mais cela ne suffit pas. Sous Windows, il y a une petite astuce : il suffit de copier libasl.dll (présente dans le répertoire où est installé PHP, par exemple C:\PHP) vers le répertoire \Bin d’Apache (par exemple : C:\Apache24\Bin). On redémarre le service Apache24 via services.msc et le tour est joué, on va pouvoir faire de jolies requêtes LDAP.

Sur des environnements intégrés comme XAMP, il faut copier d’autres DLL à d’autres endroits, je vous laisser chercher sur votre moteur de recherche favori.

Quelques rappels sur l’AD

L’ActiveDirectory est un annuaire. Un peu particulier : on y trouve entre autres des comptes utilisateurs, des ordinateurs, des groupes et j’en passe, le tout savamment organisés dans une arborescence mûrement réfléchie (ou pas) par des administrateurs sages et intelligents (ou pas). En gros, une organisation comme dans la plupart des systèmes de fichiers depuis des temps immémoriaux : les fichiers (des objets contenant des données) sont dans des répertoires, eux-même dans d’autres répertoires, etc. L’AD, comme tout bon annuaire, peut être interrogé via le protocole LDAP, qui, je vous le donne en mille, signifie très précisément Lightweight Directory Access Protocol (et c’est le protocole d’accès qui est léger, pas l’annuaire. Vous verriez l’AD mondial de Big avec ses millions d’objets, on ne peut pas dire qu’il est léger).

Il faut donc avoir une certaine connaissance de l’arborescence et savoir ce qu’on cherche, et tant qu’à faire où ça se trouve (au moins la branche qui va bien). Je passe sur la notion de forêt, restons sur un AD assez simple : une racine, un domaine, quelques branches imbriquées, et c’est tout. Sous Windows, l’AD est gérée par un serveur nommé contrôleur de domaine (une notion en réalité remontant AVANT l’existence d’ActiveDirectory, à l’époque de Windows NT). Ce que nous allons faire, c’est justement interroger (poliment) ce contrôleur de domaine pour qu’il nous donne les informations recherchées.

Il faut donc savoir son nom (ou son adresse IP), cela va nous servir pour nous y connecter… De même, il faut connaître le nom du domaine (en toute logique, vous disposez d’un compte dans celui-ci, sinon ce n’est même pas la peine de lire la suite de cet article), et un peu l’arborescence (les fameuses OU – Organizational Units – autrement dit, les répertoires contenant les objets tant recherchés).

LDAP Vinci Code

Oui, bon, désolé. Nous allons commencer joyeusement par définir quelques variables qui vont nous permettre de se connecter au contrôleur de domaine, et donc à l’AD.

Malheureusement, je ne peux pas ici vous donner un contenu qui fonctionne, vous devrez l’adapter à votre AD évidemment.

// Le contrôleur de domaine (avec ldap:// devant pour préciser le protocole)
$server = "ldap://monjolidc";
// Le compte d'utilisateur (du domaine) 
$user = "dede@lateur.laserforce.org";
// Le mot de passe (oui c'est mal de stocker ça en clair mais c'est un exemple hein)
$psw = "password"; // un TRES mauvais exemple, certes.
// Le chemin (FQDN) qui servira de base à la recherche, avec OU et DC (domain component)
$dn = "OU=LateurCorp,dc=lateur,dc=laserforce,dc=org";

Notez la syntaxe pour mon compte utilisateur : contrairement à ce que vous entrez habituellement pour ouvrir une session Windows, on n’utilise pas ici la syntaxe à la NetBIOS (domain\user), mais bien la syntaxe LDAP (user@domain), avec le domaine qualifié (la série de DC en l’occurrence).

Il va falloir ensuite préciser ce qu’on veut. On va commencer très simplement, à savoir demander la liste de tous les objets présents dans l’OU LateurCorp (et ses sous-OU, c’est récursif). Le reste du code ne changera guère, tout dépendra des propriétés que vous avez besoin (et donc du type d’objet – utilisateur, ordinateur, etc – à récupérer). Pour cela, nous allons donc définir la requête, ou un filtre, tout dépend de ce qu’on demande, dans une variable :

$search = "CN*";

Oui on ne se foule guère là, mais ça se compliquera vite, vous allez voir. Ici donc, notre critère de recherche est on ne peut plus simple : TOUS les objets, d’où la présence du caractère joker *.

La phase suivante consiste à se connecter au contrôleur de domaine, de nous binder à cette connexion, à envoyer la requête, et recevoir les résultats. Ce est plus long à écrire qu’à coder (ou presque), vu que cela tient en 4 lignes (enfin 6 avec les commentaires, arrêtez un peu de pinailler) !

// On se connecte et on se binde
$ds=ldap_connect($server);
$r=ldap_bind($ds, $user , $psw);
// On fait la recherche et on récupère les résultats
$sr=ldap_search($ds, $dn, $search);
$data = ldap_get_entries($ds, $sr);

J’entends d’ici les hauts cris des puristes : « et si la connexion foire ? Et si le bind foire ? Tu ne testes rien ! ». Effectivement, il faudrait tester tout ça, mais j’explique d’abord le principe, on verra les best practices plus tard.

Revenons un peu à nos commandes et variables. $ds c’est pas sorcier, c’est un sorte de handle (comme pour les fichiers) de connexion au contrôleur de domaine. r$ c’est le résultat du bind, donc il faudra le tester à l’occasion, mais sinon à part ça il ne nous sert à rien d’autre. $sr est le résultat de notre recherche : pareil, il faudrait le tester un de ces quatre, mais à part ça, il n’a pas d’autre fonction. Enfin, $data contient le résultat de notre requête LDAP, et c’est là que ça devient intéressant.

Mais compliqué.

$data s’emballe ?

Désolé. Data peut effectivement être d’un compliqué (un peu comme celui de Star Trek Générations) parfois.. et même très souvent… car c’est un tableau (pour PHP) multidimensionnel (qui contient d’autres tableaux), et non un objet comme sous Powershell. Du coup, il va falloir aussi que vous connaissiez quelques propriétés intéressantes de vos objets AD, sinon vous allez avoir du mal (alors que c’est assez facile).

Vous pouvez tenter de faire un var_dump($data) pour voir ce qu’il contient, mais avant, prenez un ou deux Doliprane, parce que c’est difficilement lisible… Alors plutôt qu’un var_dump, tapez plutôt ceci, vous m’en direz des nouvelles :

highlight_string("<?php\n\$data =\n" . var_export($data, true) . ";\n?>");

Oui c’est déjà un peu plus clair, on voit déjà beaucoup mieux la structure de $data (oui, vous qui ne faites que lire ce blog sans programmer en parallèle, vous ne voyez rien, je sais. Sachez juste que c’est joli et bien plus lisible qu’en faisant un var_dump).

Donc nous avons des données dans $data, c’est bien, mais ce qui est mieux, c’est de savoir combien. Ben oui parce que vu la requête qu’on a faite, vous vous doutez bien que l’AD va nous renvoyer tout un tas d’objets, pas qu’un seul. A ce sujet, il y a une limite du nombre de résultats : 1000. Pas plus. Sous Powershell, c’est 5000. Pourquoi 1000 et pas autre chose, mystère. Comment outrepasser cette limite ? A l’heure actuelle je n’en sais rien, si ce n’est en faisant des requêtes plus intelligentes que « tous les objets dans l’OU de plus bas niveau », comme « certains objets bien précis dans l’OU où ils se trouvent » !

Bref, comptons un peu nos objets présents dans $data :

echo "Nb objets  :".$data["count"];

On prend l’index « count » de notre tableau $data et voilà, on a le nombre d’objets. Finito. Évidemment, si on a plus d’un objet, on va se servir de cette valeur pour faire une jolie boucle, histoire d’afficher les propriétés de tout ce petit monde. Admettons que je veuille le nom, le prénom et l’adresse email de mes utilisateurs (de l’OU LateurCorp et ses sous-OU, rappelez-vous, c’est récursif). Cela va nous donner quelque chose comme ceci :

for ($i=0; $i<$data["count"]; $i++) {
	echo "CN de l'objet : ".$data[$i]["cn"][0]."<br />";
	echo "Distinguished Name : ".$data[$i]["dn"]."<br />"; 
	echo "Nom : ".$data[$i]["sn"][0]."<br>";
	echo "Prénom : ".$data[$i]["givenname"][0]."<br>";
	echo "Email : " . $data[$i]["mail"][0] . "<br /><br />";
}

Les hurlements de puristes se rapprochent, je vois la lueur de leurs torches enflammées…

Comme je vous l’ai dit, $data est un tableau multidimensionnel, d’où la syntaxe un peu particulière que je vais décortiquer, en prenant l’exemple de la première propriété (le CN ou Common Name).

$data[$i]["cn"][0]
  1. $data : le tableau lui-même
  2. [$i] : l’index de l’objet dans ce tableau (on commence à 0, et on va jusqu’au dernier vu que $i est incrémentée par la boucle for)
  3. [« cn »] : le nom de la propriété (donc ici, « cn ». En gros, vous prenez le nom de la propriété tel qu’il est écrit dans l’AD, en le mettant en minuscules)
  4. [0] : l’index du sous-tableau de la propriété (et oui, il pourrait y avoir PLUSIEURS valeurs pour cette propriété : ce n’est pas le cas ici, mais c’est possible)

Voilà. Ce qui est important, c’est surtout [« cn »] vu que cela désigne la propriété dont on veut obtenir la valeur (d’index 0, c’est-à-dire la première. Vous suivez toujours ?).

Le problème dans le bout de code ci-dessus, et qui justifie en partie les hurlements des puristes, c’est qu’il fait fi de l’existence (ou non) de la propriété qu’il affiche. Et oui, un objet PEUT AVOIR ou PEUT NE PAS AVOIR une certaine propriété. C’est comme ça. Donc mieux vaut tester son existence avant d’afficher sa valeur (qui risque donc de ne pas exister non plus, et provoquer un joli warning de la part de PHP). Pour le CN vous êtes tranquilles, cette propriété existe TOUJOURS ! Donc on va plutôt tester les autres :

	if (isset($data[$i]["sn"][0]))
	{
		echo "Nom : ".$data[$i]["sn"][0]."<br />";
	}

(et pareil pour les deux autres). Là c’est déjà plus propre, les hurlements ne sont plus que murmures, on teste l’existence de la donnée dans notre tableau (une manière plus exacte que de parler de la propriété de l’objet : on n’est plus dans l’AD mais bien dans un tableau PHP).

Juste pour être bien explicite, voilà la correspondance entre nom de propriété AD et ce qu’affiche le code :

  1. distinguishedname : login (unique) de l’utilisateur
  2. sn : nom de famille de l’utilisateur
  3. givenname : prénom de l’utilisateur
  4. mail : adresse email de l’utilisateur (s’il en a une.. ou plusieurs… mais on prend la première, là)

Vous pouvez tester avec d’autres propriétés, comme par exemple description, sid… Il peut y avoir des choses. Ou pas. Ça dépend de ce que contient votre AD.

Pour finir, et avant que les hurlements reprennent et que les torches soient rallumées, terminons la connexion au contrôleur de domaine proprement :

ldap_close($ds);

Hop. C’est comme pour les bases de données, les relations personnelles ou les portes, quand on en ouvre une, on la ferme après.

J’ai faim

Donc je vais aller manger et vous laisser méditer et expérimenter. Pas de souci à vous faire, on ne fait que lire l’AD, on ne touche à rien dedans, donc aucun risque de faire des dégâts. La prochaine fois, nous allons modifier $search pour faire des trucs plus utiles, mais vous verrez que globalement le code est identique dans le principe, et qu’il ne s’agit ensuite que de construire la bonne requête et d’afficher les bonnes propriétés.

Sur ces bonnes paroles et avant que mon estomac ne dissolve le reste de mon enveloppe charnelle, je vous quitte ! Temporairement du moins pour ici, mais pour un autre site bien connu avec un oiseau bleu, hmm… disons que c’est beaucoup moins sûr ! Tchüss !

Montre ton amour pour Dédé en partageant cet article !

Laisser un commentaire