dbe571bdee80a6bf70bcd86d004fcf82eb9a6a65
[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 formar Net_LDAP2
43    *
44    * @retval void
45    */
46   function setConfig ($config) {
47     self :: $config = $config;
48   }
49   
50   /**
51    * Connection
52    *
53    * Cette methode établie la connexion à l'annuaire Ldap
54    *
55    * @author Benjamin Renard <brenard@easter-eggs.com>
56    * 
57    * @param[in] $config array Tableau de configuration au formar Net_LDAP2
58    *
59    * @retval boolean true si la connection est établie, false sinon
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    * Déconnection
76    *
77    * Cette methode clos la connexion à l'annuaire Ldap
78    *
79    * @author Benjamin Renard <brenard@easter-eggs.com>
80    *
81    * @retval void
82    */
83   public static function close() {
84     self :: $cnx -> done();
85   }
86   
87   /**
88    * Recherche dans l'annuaire
89    *
90    * Cette methode effectue une recherche dans l'annuaire et retourne le resultat
91    * de celle-ci sous la forme d'un tableau.
92    *
93    * @author Benjamin Renard <brenard@easter-eggs.com>
94    *
95    * @param[in] $filter [<b>required</b>] string Filtre de recherche Ldap
96    * @param[in] $basedn string DN de base pour la recherche
97    * @param[in] $params array Paramètres de recherche au format Net_LDAP2::search()
98    *
99    * @see Net_LDAP2::search()
100    *
101    * @retval array Retourne un tableau associatif contenant :
102    *               - ['dn'] : le DN de l'entré
103    *               - ['attrs'] : tableau associatif contenant les attributs (clé)
104    *                             et leur valeur (valeur).
105    */
106   public static function search ($filter,$basedn=NULL,$params = array()) {
107     $ret = self :: $cnx -> search($basedn,$filter,$params);
108     if (Net_LDAP2::isError($ret)) {
109       LSerror :: addErrorCode('LSldap_02',$ret -> getMessage());
110       return;
111     }
112     LSdebug("LSldap::search() : return ".$ret->count()." objet(s)");
113     $retInfos=array();
114     foreach($ret as $dn => $entry) {
115       if (!$entry instanceof Net_LDAP2_Entry) {
116         LSerror :: addErrorCode('LSldap_02',"LDAP search return an ".get_class($entry).". object");
117         continue;
118       }
119       $retInfos[]=array('dn' => $dn, 'attrs' => $entry -> getValues());
120     }
121     return $retInfos;
122   }
123   
124   /**
125    * Compte le nombre de retour d'une recherche dans l'annuaire
126    *
127    * Cette methode effectue une recherche dans l'annuaire et retourne le nombre
128    * d'entrés trouvées.
129    *
130    * @author Benjamin Renard <brenard@easter-eggs.com>
131    *
132    * @param[in] $filter [<b>required</b>] string Filtre de recherche Ldap
133    * @param[in] $basedn string DN de base pour la recherche
134    * @param[in] $params array Paramètres de recherche au format Net_LDAP2::search()
135    *
136    * @see Net_LDAP2::search()
137    *
138    * @retval numeric Le nombre d'entré trouvées
139    */
140   public static function getNumberResult ($filter,$basedn=NULL,$params = array() ) {
141     if (empty($filter))
142       $filter=NULL;
143     $ret = self :: $cnx -> search($basedn,$filter,$params);
144     if (Net_LDAP2::isError($ret)) {
145       LSerror :: addErrorCode('LSldap_02',$ret -> getMessage());
146       return;
147     }
148     return $ret -> count();
149   }
150   
151   /**
152    * Charge les valeurs des attributs d'une entré de l'annuaire
153    *
154    * Cette methode recupère les valeurs des attributs d'une entrée de l'annaire
155    * et les retournes sous la forme d'un tableau.
156    *
157    * @author Benjamin Renard <brenard@easter-eggs.com>
158    *
159    * @param[in] $dn string DN de l'entré Ldap
160    *
161    * @retval array Tableau associatif des valeurs des attributs avec en clef, le nom de l'attribut.
162    */
163   public static function getAttrs($dn) {
164     $infos = ldap_explode_dn($dn,0);
165     if((!$infos)||($infos['count']==0))
166       return;
167     $basedn='';
168     for ($i=1;$i<$infos['count'];$i++) {
169       $sep=($basedn=='')?'':',';
170       $basedn.=$sep.$infos[$i];
171     }
172     $return=self :: search($infos[0],$basedn);
173     return $return[0]['attrs'];
174   }
175   
176   /**
177    * Retourne une entrée existante ou nouvelle
178    *
179    * @author Benjamin Renard <brenard@easter-eggs.com>
180    *
181    * @param[in] $object_type string Type de l'objet Ldap
182    * @param[in] $dn string DN de l'entré Ldap
183    *
184    * @retval ldapentry|array Un objet ldapentry (PEAR::Net_LDAP2)
185    *                         ou un tableau (si c'est une nouvelle entrée):
186    *                          Array (
187    *                            'entry' => ldapentry,
188    *                            'new' => true
189    *                          )
190    */
191   public static function getEntry($object_type,$dn) {
192     $obj_conf=LSconfig :: get('LSobjects.'.$object_type);
193     if(is_array($obj_conf)){
194       $entry = self :: getLdapEntry($dn);
195       if ($entry === false) {
196         $newentry = self :: getNewEntry($dn,$obj_conf['objectclass'],array());
197         
198         if (!$newentry) {
199           return;
200         }
201         return array('entry' => $newentry,'new' => true);
202       }
203       else {
204         return $entry;
205       }
206     }
207     else {
208       LSerror :: addErrorCode('LSldap_03');
209       return;
210     }
211   }
212   
213   /**
214    * Retourne un object NetLDAP d'une entree existante
215    *
216    * @author Benjamin Renard <brenard@easter-eggs.com>
217    *
218    * @param[in] $dn string DN de l'entré Ldap
219    *
220    * @retval ldapentry|boolean  Un objet ldapentry (PEAR::Net_LDAP2) ou false en
221    *                            cas de problème
222    */
223   public static function getLdapEntry($dn) {
224     $entry = self :: $cnx -> getEntry($dn);
225     if (Net_LDAP2::isError($entry)) {
226       return false;
227     }
228     else {
229       return $entry;
230     }
231   }
232   
233  /**
234   * Retourne une nouvelle entrée
235   * 
236   * @param[in] $dn string Le DN de l'objet
237   * @param[in] $objectClass array Un tableau contenant les objectClass de l'objet
238   * @param[in] $attrs array Un tabeau du type array('attr_name' => attr_value, ...)
239   * 
240   * @retval mixed Le nouvelle objet en cas de succès, false sinon
241   */
242   public static function getNewEntry($dn,$objectClass,$attrs,$add=false) {
243     $newentry = Net_LDAP2_Entry::createFresh($dn,array_merge(array('objectclass' =>$objectClass),(array)$attrs));
244     if(Net_LDAP2::isError($newentry)) {
245       return false;
246     }
247     if($add) {
248       if(!self :: $cnx -> add($newentry)) {
249         return;
250       }
251     }
252     return $newentry;
253   }
254   
255   /**
256    * Met à jour une entrée dans l'annuaire
257    * 
258    * Remarque : Supprime les valeurs vides de attributs et les attributs sans valeur.
259    *
260    * @author Benjamin Renard <brenard@easter-eggs.com>
261    *
262    * @param[in] $object_type string Type de l'objet Ldap
263    * @param[in] $dn string DN de l'entré Ldap
264    * @param[in] $change array Tableau des modifications à apporter
265    *
266    * @retval boolean true si l'objet a bien été mis à jour, false sinon
267    */
268   public static function update($object_type,$dn,$change) {
269     LSdebug($change);
270     $dropAttr=array();
271     $entry=self :: getEntry($object_type,$dn);
272     if (is_array($entry)) {
273       $new = $entry['new'];
274       $entry = $entry['entry'];
275     }
276     else {
277       $new = false;
278     }
279
280     if($entry) {
281       foreach($change as $attrName => $attrVal) {
282         $drop = true;
283         if (is_array($attrVal)) {
284           foreach($attrVal as $val) {
285             if (!empty($val)) {
286               $drop = false;
287               $changeData[$attrName][]=$val;
288             }
289           }
290         }
291         else {
292           if (!empty($attrVal)) {
293             $drop = false;
294             $changeData[$attrName][]=$attrVal;
295           }
296         }
297         if($drop) {
298           $dropAttr[] = $attrName;
299         }
300       }
301       if (isset($changeData)) {
302         $entry -> replace($changeData);
303         LSdebug('change : <pre>'.print_r($changeData,true).'</pre>');
304         LSdebug('drop : <pre>'.print_r($dropAttr,true).'</pre>');
305       }
306       else {
307         LSdebug('No change');
308       }
309
310       if ($new) {
311         LSdebug('LSldap :: add()');
312         $ret = self :: $cnx -> add($entry);
313       }
314       else {
315         LSdebug('LSldap :: update()');
316         $ret = $entry -> update();
317       }
318       
319       if (Net_LDAP2::isError($ret)) {
320         LSerror :: addErrorCode('LSldap_05',$dn);
321         LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
322       }
323       else {
324         if (!empty($dropAttr)) {
325           foreach($dropAttr as $attr) {
326             $value = $entry -> getValue($attr);
327             if(Net_LDAP2::isError($value) || empty($value)) {
328               // Attribut n'existe pas dans l'annuaire
329               continue;
330             }
331             // Méthode buggé : suppression impossible de certain attribut
332             // exemple : jpegPhoto
333             // $entry -> delete($attr);
334             $entry -> replace(array($attr =>array()));
335           }
336           $ret = $entry -> update();
337           if (Net_LDAP2::isError($ret)) {
338             LSerror :: addErrorCode('LSldap_06');
339             LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
340           }
341         }
342         return true;
343       }
344     }
345     else {
346       LSerror :: addErrorCode('LSldap_04');
347       return;
348     }
349   }
350
351   /**
352    * Test de bind
353    *
354    * Cette methode établie une connexion à l'annuaire Ldap et test un bind
355    * avec un login et un mot de passe passé en paramètre
356    *
357    * @author Benjamin Renard <brenard@easter-eggs.com>
358    *
359    * @retval boolean true si la connection à réussi, false sinon
360    */
361   public static function checkBind($dn,$pwd) {
362     $config = self :: $config;
363     $config['binddn'] = $dn;
364     $config['bindpw'] = $pwd;
365     $cnx = Net_LDAP2::connect($config);
366     if (Net_LDAP2::isError($cnx)) {
367       return;
368     }
369     return true;
370   }
371
372   /**
373    * Retourne l'état de la connexion Ldap
374    *
375    * @retval boolean True si le serveur est connecté, false sinon.
376    */
377   public static function isConnected() {
378     return (self :: $cnx == NULL)?false:true;
379   }
380   
381   /**
382    * Supprime un objet de l'annuaire
383    *
384    * @param[in] string DN de l'objet à supprimer
385    * 
386    * @retval boolean True si l'objet à été supprimé, false sinon
387    */
388   public static function remove($dn) {
389     $ret = self :: $cnx -> delete($dn,array('recursive' => true));
390     if (Net_LDAP2::isError($ret)) {
391       LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
392       return;
393     }
394     return true;
395   }
396
397   /**
398    * Déplace un objet LDAP dans l'annuaire
399    * 
400    * @param[in] $old string Le DN actuel
401    * @param[in] $new string Le futur DN
402    * 
403    * @retval boolean True si l'objet a été déplacé, false sinon
404    */
405   public static function move($old,$new) {
406     $ret = self :: $cnx -> move($old,$new);
407     if (Net_LDAP2::isError($ret)) {
408       LSerror :: addErrorCode('LSldap_07');
409       LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
410       return;
411     }
412     return true;
413   }
414   
415   /**
416    * Combine LDAP Filters
417    * 
418    * @params array Array of LDAP filters
419    * 
420    * @retval Net_LDAP2_Filter | False The combined filter or False
421    **/
422   public static function combineFilters($op,$filters,$asStr=false) {
423     if (is_array($filters) && !empty($filters)) {
424       if (count($filters)==1) {
425         return $filters[0];
426       }
427       $filter=Net_LDAP2_Filter::combine($op,$filters);
428       if (!Net_LDAP2::isError($filter)) {
429         if ($asStr) {
430           return $filter->asString();
431         }
432         else {
433           return $filter;
434         }
435       }
436       else {
437         LSerror :: addErrorCode(0,$filter -> getMessage());
438       }
439     }
440     return;
441   }
442   
443   /**
444    * Check LDAP Filters String
445    * 
446    * @params string A LDAP filter as string
447    * 
448    * @retval boolean True only if the filter could be parsed
449    **/
450   public static function isValidFilter($filter) {
451     if (is_string($filter) && !empty($filter)) {
452       $filter=Net_LDAP2_Filter::parse($filter);
453       if (!Net_LDAP2::isError($filter)) {
454         return true;
455       }
456       else {
457         LSerror :: addErrorCode(0,$filter -> getMessage());
458       }
459     }
460     return;
461   }
462 }
463
464 /*
465  * Error Codes
466  */
467 LSerror :: defineError('LSldap_01',
468   _("LSldap : Error during the LDAP server connection (%{msg}).")
469 );
470 LSerror :: defineError('LSldap_02',
471   _("LSldap : Error during the LDAP search (%{msg}).")
472 );
473 LSerror :: defineError('LSldap_03',
474   _("LSldap : Object type unknown.")
475 );
476 LSerror :: defineError('LSldap_04',
477   _("LSldap : Error while fetching the LDAP entry.")
478 );
479 LSerror :: defineError('LSldap_05',
480   _("LSldap : Error while changing the LDAP entry (DN : %{dn}).")
481 );
482 LSerror :: defineError('LSldap_06',
483   _("LSldap : Error while deleting empty attributes.")
484 );
485 LSerror :: defineError('LSldap_07',
486   _("LSldap : Error while changing the DN of the object.")
487 );
488 ?>