LSsession : Add useUserCredentials parameter
[ldapsaisie.git] / public_html / includes / class / class.LSldap.php
1 <?php
2 /*******************************************************************************
3  * Copyright (C) 2007 Easter-eggs
4  * http://ldapsaisie.labs.libre-entreprise.org
5  *
6  * Author: See AUTHORS file in top-level directory.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License version 2
10  * as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20
21 ******************************************************************************/
22
23 /**
24  * Gestion de l'accès à l'annaire Ldap
25  *
26  * Cette classe gère l'accès à l'annuaire ldap en s'appuyant sur PEAR :: Net_LDAP2
27  *
28  * @author Benjamin Renard <brenard@easter-eggs.com>
29  */
30 class LSldap {
31
32   private static $config;
33   private static $cnx = NULL;
34   
35   /**
36    * Défini la configuration
37    *
38    * Cette methode définis la configuration de l'accès à l'annuaire
39    *
40    * @author Benjamin Renard <brenard@easter-eggs.com>
41    *
42    * @param[in] $config array Tableau de configuration au format Net_LDAP2
43    *
44    * @retval void
45    */
46   function setConfig ($config) {
47     self :: $config = $config;
48   }
49   
50   /**
51    * Connect to LDAP server
52    *
53    * This method  establish connection to LDAP server
54    *
55    * @author Benjamin Renard <brenard@easter-eggs.com>
56    * 
57    * @param[in] $config array LDAP configuration array in format of Net_LDAP2
58    *
59    * @retval boolean true if connected, false instead
60    */
61   public static function connect($config = null) {
62     if ($config) {
63       self :: setConfig($config);
64     }
65     self :: $cnx = Net_LDAP2::connect(self :: $config);
66     if (Net_LDAP2::isError(self :: $cnx)) {
67       LSerror :: addErrorCode('LSldap_01',self :: $cnx -> getMessage());
68       self :: $cnx = NULL;
69       return;
70     }
71     return true;
72   }
73   
74   /**
75    * Reconnect (or connect) with other credentials
76    *
77    * @author Benjamin Renard <brenard@easter-eggs.com>
78    *
79    * @param[in] $dn string Bind DN
80    * @param[in] $pwd array Bind password
81    * @param[in] $config array LDAP configuration array in format of Net_LDAP2
82    *
83    * @retval boolean true if connected, false instead
84    */
85   public static function reconnectAs($dn,$pwd,$config) {
86     if ($config) {
87       self :: setConfig($config);
88     }
89         if (self :: $cnx) {
90           self :: $cnx -> done();
91         }
92         $config=self :: $config;
93         $config['binddn']=$dn;
94         $config['bindpw']=$pwd;
95         self :: $cnx = Net_LDAP2::connect($config);
96         if (Net_LDAP2::isError(self :: $cnx)) {
97           LSerror :: addErrorCode('LSldap_01',self :: $cnx -> getMessage());
98           self :: $cnx = NULL;
99           return;
100         }
101         return true;
102   }
103
104   /**
105    * Déconnection
106    *
107    * Cette methode clos la connexion à l'annuaire Ldap
108    *
109    * @author Benjamin Renard <brenard@easter-eggs.com>
110    *
111    * @retval void
112    */
113   public static function close() {
114     self :: $cnx -> done();
115   }
116   
117   /**
118    * Recherche dans l'annuaire
119    *
120    * Cette methode effectue une recherche dans l'annuaire et retourne le resultat
121    * de celle-ci sous la forme d'un tableau.
122    *
123    * @author Benjamin Renard <brenard@easter-eggs.com>
124    *
125    * @param[in] $filter [<b>required</b>] string Filtre de recherche Ldap
126    * @param[in] $basedn string DN de base pour la recherche
127    * @param[in] $params array Paramètres de recherche au format Net_LDAP2::search()
128    *
129    * @see Net_LDAP2::search()
130    *
131    * @retval array Retourne un tableau associatif contenant :
132    *               - ['dn'] : le DN de l'entré
133    *               - ['attrs'] : tableau associatif contenant les attributs (clé)
134    *                             et leur valeur (valeur).
135    */
136   public static function search ($filter,$basedn=NULL,$params = array()) {
137     $ret = self :: $cnx -> search($basedn,$filter,$params);
138     if (Net_LDAP2::isError($ret)) {
139       LSerror :: addErrorCode('LSldap_02',$ret -> getMessage());
140       return;
141     }
142     LSdebug("LSldap::search() : return ".$ret->count()." objet(s)");
143     $retInfos=array();
144     foreach($ret as $dn => $entry) {
145       if (!$entry instanceof Net_LDAP2_Entry) {
146         LSerror :: addErrorCode('LSldap_02',"LDAP search return an ".get_class($entry).". object");
147         continue;
148       }
149       $retInfos[]=array('dn' => $dn, 'attrs' => $entry -> getValues());
150     }
151     return $retInfos;
152   }
153   
154   /**
155    * Compte le nombre de retour d'une recherche dans l'annuaire
156    *
157    * Cette methode effectue une recherche dans l'annuaire et retourne le nombre
158    * d'entrés trouvées.
159    *
160    * @author Benjamin Renard <brenard@easter-eggs.com>
161    *
162    * @param[in] $filter [<b>required</b>] string Filtre de recherche Ldap
163    * @param[in] $basedn string DN de base pour la recherche
164    * @param[in] $params array Paramètres de recherche au format Net_LDAP2::search()
165    *
166    * @see Net_LDAP2::search()
167    *
168    * @retval numeric Le nombre d'entré trouvées
169    */
170   public static function getNumberResult ($filter,$basedn=NULL,$params = array() ) {
171     if (empty($filter))
172       $filter=NULL;
173     $ret = self :: $cnx -> search($basedn,$filter,$params);
174     if (Net_LDAP2::isError($ret)) {
175       LSerror :: addErrorCode('LSldap_02',$ret -> getMessage());
176       return;
177     }
178     return $ret -> count();
179   }
180   
181   /**
182    * Charge les valeurs des attributs d'une entré de l'annuaire
183    *
184    * Cette methode recupère les valeurs des attributs d'une entrée de l'annaire
185    * et les retournes sous la forme d'un tableau.
186    *
187    * @author Benjamin Renard <brenard@easter-eggs.com>
188    *
189    * @param[in] $dn string DN de l'entré Ldap
190    *
191    * @retval array Tableau associatif des valeurs des attributs avec en clef, le nom de l'attribut.
192    */
193   public static function getAttrs($dn) {
194     $infos = ldap_explode_dn($dn,0);
195     if((!$infos)||($infos['count']==0))
196       return;
197     $basedn='';
198     for ($i=1;$i<$infos['count'];$i++) {
199       $sep=($basedn=='')?'':',';
200       $basedn.=$sep.$infos[$i];
201     }
202     $return=self :: search($infos[0],$basedn);
203     return $return[0]['attrs'];
204   }
205   
206   /**
207    * Retourne une entrée existante ou nouvelle
208    *
209    * @author Benjamin Renard <brenard@easter-eggs.com>
210    *
211    * @param[in] $object_type string Type de l'objet Ldap
212    * @param[in] $dn string DN de l'entré Ldap
213    *
214    * @retval ldapentry|array Un objet ldapentry (PEAR::Net_LDAP2)
215    *                         ou un tableau (si c'est une nouvelle entrée):
216    *                          Array (
217    *                            'entry' => ldapentry,
218    *                            'new' => true
219    *                          )
220    */
221   public static function getEntry($object_type,$dn) {
222     $obj_conf=LSconfig :: get('LSobjects.'.$object_type);
223     if(is_array($obj_conf)){
224       $entry = self :: getLdapEntry($dn);
225       if ($entry === false) {
226         $newentry = self :: getNewEntry($dn,$obj_conf['objectclass'],array());
227         
228         if (!$newentry) {
229           return;
230         }
231         return array('entry' => $newentry,'new' => true);
232       }
233       else {
234         return $entry;
235       }
236     }
237     else {
238       LSerror :: addErrorCode('LSldap_03');
239       return;
240     }
241   }
242   
243   /**
244    * Retourne un object NetLDAP d'une entree existante
245    *
246    * @author Benjamin Renard <brenard@easter-eggs.com>
247    *
248    * @param[in] $dn string DN de l'entré Ldap
249    *
250    * @retval ldapentry|boolean  Un objet ldapentry (PEAR::Net_LDAP2) ou false en
251    *                            cas de problème
252    */
253   public static function getLdapEntry($dn) {
254     $entry = self :: $cnx -> getEntry($dn);
255     if (Net_LDAP2::isError($entry)) {
256       return false;
257     }
258     else {
259       return $entry;
260     }
261   }
262   
263  /**
264   * Retourne une nouvelle entrée
265   * 
266   * @param[in] $dn string Le DN de l'objet
267   * @param[in] $objectClass array Un tableau contenant les objectClass de l'objet
268   * @param[in] $attrs array Un tabeau du type array('attr_name' => attr_value, ...)
269   * 
270   * @retval mixed Le nouvelle objet en cas de succès, false sinon
271   */
272   public static function getNewEntry($dn,$objectClass,$attrs,$add=false) {
273     $newentry = Net_LDAP2_Entry::createFresh($dn,array_merge(array('objectclass' =>$objectClass),(array)$attrs));
274     if(Net_LDAP2::isError($newentry)) {
275       return false;
276     }
277     if($add) {
278       if(!self :: $cnx -> add($newentry)) {
279         return;
280       }
281     }
282     return $newentry;
283   }
284   
285   /**
286    * Met à jour une entrée dans l'annuaire
287    * 
288    * Remarque : Supprime les valeurs vides de attributs et les attributs sans valeur.
289    *
290    * @author Benjamin Renard <brenard@easter-eggs.com>
291    *
292    * @param[in] $object_type string Type de l'objet Ldap
293    * @param[in] $dn string DN de l'entré Ldap
294    * @param[in] $change array Tableau des modifications à apporter
295    *
296    * @retval boolean true si l'objet a bien été mis à jour, false sinon
297    */
298   public static function update($object_type,$dn,$change) {
299     LSdebug($change);
300     $dropAttr=array();
301     $entry=self :: getEntry($object_type,$dn);
302     if (is_array($entry)) {
303       $new = $entry['new'];
304       $entry = $entry['entry'];
305     }
306     else {
307       $new = false;
308     }
309
310     if($entry) {
311       foreach($change as $attrName => $attrVal) {
312         $drop = true;
313         if (is_array($attrVal)) {
314           foreach($attrVal as $val) {
315             if (!empty($val)||(is_string($val)&&($val=="0"))) {
316               $drop = false;
317               $changeData[$attrName][]=$val;
318             }
319           }
320         }
321         else {
322           if (!empty($attrVal)||(is_string($attrVal)&&($attrVal=="0"))) {
323             $drop = false;
324             $changeData[$attrName][]=$attrVal;
325           }
326         }
327         if($drop) {
328           $dropAttr[] = $attrName;
329         }
330       }
331       if (isset($changeData)) {
332         $entry -> replace($changeData);
333         LSdebug('change : <pre>'.print_r($changeData,true).'</pre>');
334         LSdebug('drop : <pre>'.print_r($dropAttr,true).'</pre>');
335       }
336       else {
337         LSdebug('No change');
338       }
339
340       if ($new) {
341         LSdebug('LSldap :: add()');
342         $ret = self :: $cnx -> add($entry);
343       }
344       else {
345         LSdebug('LSldap :: update()');
346         $ret = $entry -> update();
347       }
348       
349       if (Net_LDAP2::isError($ret)) {
350         LSerror :: addErrorCode('LSldap_05',$dn);
351         LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
352       }
353       else {
354         if (!empty($dropAttr) && !$new) {
355           foreach($dropAttr as $attr) {
356             $value = $entry -> getValue($attr);
357             if(Net_LDAP2::isError($value) || empty($value)) {
358               // Attribut n'existe pas dans l'annuaire
359               continue;
360             }
361             // Méthode buggé : suppression impossible de certain attribut
362             // exemple : jpegPhoto
363             // $entry -> delete($attr);
364             $entry -> replace(array($attr =>array()));
365           }
366           $ret = $entry -> update();
367           if (Net_LDAP2::isError($ret)) {
368             LSerror :: addErrorCode('LSldap_06');
369             LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
370           }
371         }
372         return true;
373       }
374     }
375     else {
376       LSerror :: addErrorCode('LSldap_04');
377       return;
378     }
379   }
380
381   /**
382    * Test de bind
383    *
384    * Cette methode établie une connexion à l'annuaire Ldap et test un bind
385    * avec un login et un mot de passe passé en paramètre
386    *
387    * @author Benjamin Renard <brenard@easter-eggs.com>
388    *
389    * @retval boolean true si la connection à réussi, false sinon
390    */
391   public static function checkBind($dn,$pwd) {
392     $config = self :: $config;
393     $config['binddn'] = $dn;
394     $config['bindpw'] = $pwd;
395     $cnx = Net_LDAP2::connect($config);
396     if (Net_LDAP2::isError($cnx)) {
397       return;
398     }
399     return true;
400   }
401
402   /**
403    * Retourne l'état de la connexion Ldap
404    *
405    * @retval boolean True si le serveur est connecté, false sinon.
406    */
407   public static function isConnected() {
408     return (self :: $cnx == NULL)?false:true;
409   }
410   
411   /**
412    * Supprime un objet de l'annuaire
413    *
414    * @param[in] string DN de l'objet à supprimer
415    * 
416    * @retval boolean True si l'objet à été supprimé, false sinon
417    */
418   public static function remove($dn) {
419     $ret = self :: $cnx -> delete($dn,array('recursive' => true));
420     if (Net_LDAP2::isError($ret)) {
421       LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
422       return;
423     }
424     return true;
425   }
426
427   /**
428    * Déplace un objet LDAP dans l'annuaire
429    * 
430    * @param[in] $old string Le DN actuel
431    * @param[in] $new string Le futur DN
432    * 
433    * @retval boolean True si l'objet a été déplacé, false sinon
434    */
435   public static function move($old,$new) {
436     $ret = self :: $cnx -> move($old,$new);
437     if (Net_LDAP2::isError($ret)) {
438       LSerror :: addErrorCode('LSldap_07');
439       LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
440       return;
441     }
442     return true;
443   }
444   
445   /**
446    * Combine LDAP Filters
447    * 
448    * @params array Array of LDAP filters
449    * 
450    * @retval Net_LDAP2_Filter | False The combined filter or False
451    **/
452   public static function combineFilters($op,$filters,$asStr=false) {
453     if (is_array($filters) && !empty($filters)) {
454       if (count($filters)==1) {
455         return $filters[0];
456       }
457       $filter=Net_LDAP2_Filter::combine($op,$filters);
458       if (!Net_LDAP2::isError($filter)) {
459         if ($asStr) {
460           return $filter->asString();
461         }
462         else {
463           return $filter;
464         }
465       }
466       else {
467         LSerror :: addErrorCode(0,$filter -> getMessage());
468       }
469     }
470     return;
471   }
472   
473   /**
474    * Check LDAP Filters String
475    * 
476    * @params string A LDAP filter as string
477    * 
478    * @retval boolean True only if the filter could be parsed
479    **/
480   public static function isValidFilter($filter) {
481     if (is_string($filter) && !empty($filter)) {
482       $filter=Net_LDAP2_Filter::parse($filter);
483       if (!Net_LDAP2::isError($filter)) {
484         return true;
485       }
486       else {
487         LSerror :: addErrorCode(0,$filter -> getMessage());
488       }
489     }
490     return;
491   }
492 }
493
494 /*
495  * Error Codes
496  */
497 LSerror :: defineError('LSldap_01',
498   _("LSldap : Error during the LDAP server connection (%{msg}).")
499 );
500 LSerror :: defineError('LSldap_02',
501   _("LSldap : Error during the LDAP search (%{msg}).")
502 );
503 LSerror :: defineError('LSldap_03',
504   _("LSldap : Object type unknown.")
505 );
506 LSerror :: defineError('LSldap_04',
507   _("LSldap : Error while fetching the LDAP entry.")
508 );
509 LSerror :: defineError('LSldap_05',
510   _("LSldap : Error while changing the LDAP entry (DN : %{dn}).")
511 );
512 LSerror :: defineError('LSldap_06',
513   _("LSldap : Error while deleting empty attributes.")
514 );
515 LSerror :: defineError('LSldap_07',
516   _("LSldap : Error while changing the DN of the object.")
517 );
518 ?>