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