3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2017 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
14 * Gestion de l'authentification par LDAP
16 * @package SPIP\Core\Authentification\Ldap
19 if (!defined('_ECRIRE_INC_VERSION')) {
23 // Authentifie via LDAP et retourne la ligne SQL decrivant l'utilisateur si ok
25 // Attributs LDAP correspondants a ceux de SPIP, notamment pour le login
26 // ne pas ecraser une definition perso dans mes_options
27 if (!isset($GLOBALS['ldap_attributes']) or !is_array($GLOBALS['ldap_attributes'])) {
28 $GLOBALS['ldap_attributes'] = array(
29 'login' => array('sAMAccountName', 'uid', 'login', 'userid', 'cn', 'sn'),
32 'bio' => "description"
37 * Fonction principale d'authentification du module auth/ldap
39 * - On se bind avec le compte generique defini dans config/ldap.php,
40 * - On determine le DN de l'utilisateur candidat a l'authentification,
41 * - On se re-bind avec ce DN et le mot de passe propose.
43 * Si la connexion est autorisee, on renvoie pour enregistrement en session,
44 * en plus des champs SQL habituels, les informations de connexion de
45 * l'utilisateur (DN et password). Cela permettra de se binder en cours de
46 * session sous son identite specifique pour les operations necessitant des
47 * privileges particuliers.
48 * TODO: Gerer une constante de conf qui permette de choisir entre ce
49 * comportement et tout faire avec le compte generique.
51 * @param string $login
53 * @param string $serveur
54 * @param bool $phpauth
57 function auth_ldap_dist($login, $pass, $serveur = '', $phpauth = false) {
59 #spip_log("ldap $login " . ($pass ? "mdp fourni" : "mdp absent"));
61 // Utilisateur connu ?
62 // si http auth, inutile de reauthentifier: cela
63 // ne marchera pas avec auth http autre que basic.
64 $checkpass = isset($_SERVER["REMOTE_USER"]) ?
false : true;
65 if (!($dn = auth_ldap_search($login, $pass, $checkpass, $serveur))) {
68 $credentials_ldap = array('ldap_dn' => $dn, 'ldap_password' => $pass);
70 // Si l'utilisateur figure deja dans la base, y recuperer les infos
71 $r = sql_fetsel("*", "spip_auteurs", "login=" . sql_quote($login) . " AND source='ldap'", '', '', '', '', $serveur);
74 return array_merge($r, $credentials_ldap);
77 // sinon importer les infos depuis LDAP,
79 if ($GLOBALS['meta']["ldap_statut_import"]
80 and $desc = auth_ldap_retrouver($dn, array(), $serveur)
82 // rajouter le statut indique a l'install
83 $desc['statut'] = $GLOBALS['meta']["ldap_statut_import"];
84 $desc['login'] = $login;
85 $desc['source'] = 'ldap';
88 $r = sql_insertq('spip_auteurs', $desc, '', $serveur);
94 sql_fetsel("*", "spip_auteurs", "id_auteur=" . intval($r), '', '', '', '', $serveur)
99 spip_log("Creation de l'auteur '$login' impossible");
105 * Connexion à l'annuaire LDAP
107 * Il faut passer par `spip_connect()` pour avoir les info
108 * donc potentiellement indiquer un serveur
109 * meme si dans les fait cet argument est toujours vide
111 * @param string $serveur
114 function auth_ldap_connect($serveur = '') {
115 include_spip('base/connect_sql');
116 static $connexions_ldap = array();
117 if (isset($connexions_ldap[$serveur])) {
118 return $connexions_ldap[$serveur];
120 $connexion = spip_connect($serveur);
121 if (!is_array($connexion['ldap'])) {
122 if ($connexion['authentification']['ldap']) {
123 $f = _DIR_CONNECT
. $connexion['authentification']['ldap'];
124 unset($GLOBALS['ldap_link']);
125 if (is_readable($f)) {
128 if (isset($GLOBALS['ldap_link'])) {
129 $connexion['ldap'] = array(
130 'link' => $GLOBALS['ldap_link'],
131 'base' => $GLOBALS['ldap_base']
134 spip_log("connection LDAP $serveur mal definie dans $f");
136 if (isset($GLOBALS['ldap_champs'])) {
137 $connexion['ldap']['attributes'] = $GLOBALS['ldap_champs'];
140 spip_log("connection LDAP $serveur inconnue");
144 return $connexions_ldap[$serveur] = $connexion['ldap'];
148 * Retrouver un login, et vérifier son pass si demandé par `$checkpass`
150 * @param string $login
151 * @param string $pass
152 * @param bool $checkpass
153 * @param string $serveur
155 * Le login trouvé ou chaine vide si non trouvé
157 function auth_ldap_search($login, $pass, $checkpass = true, $serveur = '') {
158 // Securite anti-injection et contre un serveur LDAP laxiste
159 $login_search = preg_replace("/[^-@._\s\d\w]/", "", $login);
160 if (!strlen($login_search) or ($checkpass and !strlen($pass))) {
164 // verifier la connexion
165 if (!$ldap = auth_ldap_connect($serveur)) {
169 $ldap_link = isset($ldap['link']) ?
$ldap['link'] : null;
170 $ldap_base = isset($ldap['base']) ?
$ldap['base'] : null;
171 $desc = isset($ldap['attributes']) && $ldap['attributes'] ?
$ldap['attributes'] : $GLOBALS['ldap_attributes'] ;
173 $logins = is_array($desc['login']) ?
$desc['login'] : array($desc['login']);
175 // Tenter une recherche pour essayer de retrouver le DN
176 foreach ($logins as $att) {
177 $result = @ldap_search
($ldap_link, $ldap_base, "$att=$login_search", array("dn"));
178 $info = @ldap_get_entries
($ldap_link, $result);
179 // Ne pas accepter les resultats si plus d'une entree
180 // (on veut un attribut unique)
182 if (is_array($info) and $info['count'] == 1) {
183 $dn = $info[0]['dn'];
187 if (@ldap_bind
($ldap_link, $dn, $pass)) {
193 if ($checkpass and !isset($dn)) {
194 // Si echec, essayer de deviner le DN
195 foreach ($logins as $att) {
196 $dn = "$att=$login_search, $ldap_base";
197 if (@ldap_bind
($ldap_link, $dn, $pass)) {
198 return "$att=$login_search, $ldap_base";
207 * Retrouver un DN depuis LDAP
211 * @param string $serveur
214 function auth_ldap_retrouver($dn, $desc = array(), $serveur = '') {
215 // Lire les infos sur l'utilisateur a partir de son DN depuis LDAP
217 if (!$ldap = spip_connect_ldap($serveur)) {
218 spip_log("ldap $serveur injoignable");
223 $ldap_link = $ldap['link'];
225 $desc = $ldap['attributes'] ?
$ldap['attributes'] : $GLOBALS['ldap_attributes'];
226 unset($desc['login']);
228 $result = @ldap_read
($ldap_link, $dn, "objectClass=*", array_values($desc));
234 // Recuperer les donnees du premier (unique?) compte de l'auteur
235 $val = @ldap_get_entries
($ldap_link, $result);
236 if (!is_array($val) or !is_array($val[0])) {
241 // Convertir depuis UTF-8 (jeu de caracteres par defaut)
242 include_spip('inc/charsets');
244 foreach ($desc as $k => $v) {
245 $desc[$k] = importer_charset($val[strtolower($v)][0], 'utf-8');
253 * Retrouver le login de quelqu'un qui cherche à se loger
255 * @param string $login
256 * @param string $serveur
259 function auth_ldap_retrouver_login($login, $serveur = '') {
260 return auth_ldap_search($login, '', false, $serveur) ?
$login : '';
264 * Vérification de la validité d'un mot de passe pour le mode d'auth concerné
266 * C'est ici que se font éventuellement les vérifications de longueur mini/maxi
269 * @param string $login
270 * Le login de l'auteur : permet de vérifier que pass et login sont différents
271 * même à la creation lorsque l'auteur n'existe pas encore
272 * @param string $new_pass
273 * @param int $id_auteur
274 * Si auteur existant déjà
275 * @param string $serveur
277 * Message d'erreur si login non valide, chaîne vide sinon
279 function auth_ldap_verifier_pass($login, $new_pass, $id_auteur = 0, $serveur = '') {
280 include_spip('auth/spip');
282 return auth_spip_verifier_pass($login, $new_pass, $id_auteur, $serveur);
286 * Informer du droit de modifier ou non le pass
288 * On ne peut pas détecter à l'avance si l'autorisation sera donnée, il
289 * faudra informer l'utilisateur a posteriori si la modif n'a pas pu se
292 * @param string $serveur
294 * Pour un auteur LDAP, a priori toujours true, à conditiion que le serveur
295 * l'autorise: par exemple, pour OpenLDAP il faut avoir dans slapd.conf:
297 * access to attr=userPassword
302 function auth_ldap_autoriser_modifier_pass($serveur = '') {
307 * Fonction de modification du mot de passe
309 * On se bind au LDAP cette fois sous l'identité de l'utilisateur, car le
310 * compte générique defini dans config/ldap.php n'a généralement pas (et
311 * ne devrait pas avoir) les droits suffisants pour faire la modification.
316 * @param string $serveur
318 * Informe du succès ou de l'echec du changement du mot de passe
320 function auth_ldap_modifier_pass($login, $new_pass, $id_auteur, $serveur = '') {
321 if (is_null($new_pass) or auth_ldap_verifier_pass($login, $new_pass, $id_auteur, $serveur) != '') {
324 if (!$ldap = auth_ldap_connect($serveur)) {
327 $link = $ldap['link'];
328 include_spip("inc/session");
329 $dn = session_get('ldap_dn');
333 if (!ldap_bind($link, $dn, session_get('ldap_password'))) {
336 $encoded_pass = "{MD5}" . base64_encode(pack("H*", md5($new_pass)));
337 $success = ldap_mod_replace($link, $dn, array('userPassword' => $encoded_pass));