a251e4123cc9d3e31ae0a9755e97dd847c3714af
[lhc/web/www.git] / www / ecrire / auth / ldap.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2017 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
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 \***************************************************************************/
12
13 /**
14 * Gestion de l'authentification par LDAP
15 *
16 * @package SPIP\Core\Authentification\Ldap
17 **/
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22
23 // Authentifie via LDAP et retourne la ligne SQL decrivant l'utilisateur si ok
24
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'),
30 'nom' => "cn",
31 'email' => "mail",
32 'bio' => "description"
33 );
34 }
35
36 /**
37 * Fonction principale d'authentification du module auth/ldap
38 *
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.
42 *
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.
50 *
51 * @param string $login
52 * @param string $pass
53 * @param string $serveur
54 * @param bool $phpauth
55 * @return string
56 */
57 function auth_ldap_dist($login, $pass, $serveur = '', $phpauth = false) {
58
59 #spip_log("ldap $login " . ($pass ? "mdp fourni" : "mdp absent"));
60
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))) {
66 return array();
67 }
68 $credentials_ldap = array('ldap_dn' => $dn, 'ldap_password' => $pass);
69
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);
72
73 if ($r) {
74 return array_merge($r, $credentials_ldap);
75 }
76
77 // sinon importer les infos depuis LDAP,
78
79 if ($GLOBALS['meta']["ldap_statut_import"]
80 and $desc = auth_ldap_retrouver($dn, array(), $serveur)
81 ) {
82 // rajouter le statut indique a l'install
83 $desc['statut'] = $GLOBALS['meta']["ldap_statut_import"];
84 $desc['login'] = $login;
85 $desc['source'] = 'ldap';
86 $desc['pass'] = '';
87
88 $r = sql_insertq('spip_auteurs', $desc, '', $serveur);
89 }
90
91 if ($r) {
92 return array_merge(
93 $credentials_ldap,
94 sql_fetsel("*", "spip_auteurs", "id_auteur=" . intval($r), '', '', '', '', $serveur)
95 );
96 }
97
98 // sinon echec
99 spip_log("Creation de l'auteur '$login' impossible");
100
101 return array();
102 }
103
104 /**
105 * Connexion à l'annuaire LDAP
106 *
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
110 *
111 * @param string $serveur
112 * @return array
113 */
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];
119 }
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)) {
126 include_once($f);
127 };
128 if (isset($GLOBALS['ldap_link'])) {
129 $connexion['ldap'] = array(
130 'link' => $GLOBALS['ldap_link'],
131 'base' => $GLOBALS['ldap_base']
132 );
133 } else {
134 spip_log("connection LDAP $serveur mal definie dans $f");
135 }
136 if (isset($GLOBALS['ldap_champs'])) {
137 $connexion['ldap']['attributes'] = $GLOBALS['ldap_champs'];
138 }
139 } else {
140 spip_log("connection LDAP $serveur inconnue");
141 }
142 }
143
144 return $connexions_ldap[$serveur] = $connexion['ldap'];
145 }
146
147 /**
148 * Retrouver un login, et vérifier son pass si demandé par `$checkpass`
149 *
150 * @param string $login
151 * @param string $pass
152 * @param bool $checkpass
153 * @param string $serveur
154 * @return string
155 * Le login trouvé ou chaine vide si non trouvé
156 */
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))) {
161 return '';
162 }
163
164 // verifier la connexion
165 if (!$ldap = auth_ldap_connect($serveur)) {
166 return '';
167 }
168
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'] ;
172
173 $logins = is_array($desc['login']) ? $desc['login'] : array($desc['login']);
174
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)
181
182 if (is_array($info) and $info['count'] == 1) {
183 $dn = $info[0]['dn'];
184 if (!$checkpass) {
185 return $dn;
186 }
187 if (@ldap_bind($ldap_link, $dn, $pass)) {
188 return $dn;
189 }
190 }
191 }
192
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";
199 }
200 }
201 }
202
203 return '';
204 }
205
206 /**
207 * Retrouver un DN depuis LDAP
208 *
209 * @param string $dn
210 * @param array $desc
211 * @param string $serveur
212 * @return array
213 */
214 function auth_ldap_retrouver($dn, $desc = array(), $serveur = '') {
215 // Lire les infos sur l'utilisateur a partir de son DN depuis LDAP
216
217 if (!$ldap = spip_connect_ldap($serveur)) {
218 spip_log("ldap $serveur injoignable");
219
220 return array();
221 }
222
223 $ldap_link = $ldap['link'];
224 if (!$desc) {
225 $desc = $ldap['attributes'] ? $ldap['attributes'] : $GLOBALS['ldap_attributes'];
226 unset($desc['login']);
227 }
228 $result = @ldap_read($ldap_link, $dn, "objectClass=*", array_values($desc));
229
230 if (!$result) {
231 return array();
232 }
233
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])) {
237 return array();
238 }
239 $val = $val[0];
240
241 // Convertir depuis UTF-8 (jeu de caracteres par defaut)
242 include_spip('inc/charsets');
243
244 foreach ($desc as $k => $v) {
245 $desc[$k] = importer_charset($val[strtolower($v)][0], 'utf-8');
246 }
247
248 return $desc;
249 }
250
251
252 /**
253 * Retrouver le login de quelqu'un qui cherche à se loger
254 *
255 * @param string $login
256 * @param string $serveur
257 * @return string
258 */
259 function auth_ldap_retrouver_login($login, $serveur = '') {
260 return auth_ldap_search($login, '', false, $serveur) ? $login : '';
261 }
262
263 /**
264 * Vérification de la validité d'un mot de passe pour le mode d'auth concerné
265 *
266 * C'est ici que se font éventuellement les vérifications de longueur mini/maxi
267 * ou de force.
268 *
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
276 * @return string
277 * Message d'erreur si login non valide, chaîne vide sinon
278 */
279 function auth_ldap_verifier_pass($login, $new_pass, $id_auteur = 0, $serveur = '') {
280 include_spip('auth/spip');
281
282 return auth_spip_verifier_pass($login, $new_pass, $id_auteur, $serveur);
283 }
284
285 /**
286 * Informer du droit de modifier ou non le pass
287 *
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
290 * faire.
291 *
292 * @param string $serveur
293 * @return bool
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:
296 * ```
297 * access to attr=userPassword
298 * by self write
299 * ...
300 * ```
301 */
302 function auth_ldap_autoriser_modifier_pass($serveur = '') {
303 return true;
304 }
305
306 /**
307 * Fonction de modification du mot de passe
308 *
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.
312 *
313 * @param $login
314 * @param $new_pass
315 * @param $id_auteur
316 * @param string $serveur
317 * @return bool
318 * Informe du succès ou de l'echec du changement du mot de passe
319 */
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) != '') {
322 return false;
323 }
324 if (!$ldap = auth_ldap_connect($serveur)) {
325 return '';
326 }
327 $link = $ldap['link'];
328 include_spip("inc/session");
329 $dn = session_get('ldap_dn');
330 if ('' == $dn) {
331 return false;
332 }
333 if (!ldap_bind($link, $dn, session_get('ldap_password'))) {
334 return false;
335 }
336 $encoded_pass = "{MD5}" . base64_encode(pack("H*", md5($new_pass)));
337 $success = ldap_mod_replace($link, $dn, array('userPassword' => $encoded_pass));
338
339 return $success;
340 }