array('type'=>'key', 'label' => 'Identifiant'), 'ip' => array('type'=>'text','label' => 'IP'), 'port' => array('type'=>'integer','label' => 'Port'), 'sslPort' => array('type'=>'integer','label' => 'Port SSL'), 'protocolVersion' => array('type'=>'text','label' => 'Version de protocole ldap'), 'domain' => array('type'=>'text','label' => 'Domaine'), 'userRoot' => array('type'=>'textarea','label' => 'Racine des comptes utilisateurs'), 'groupRoot' => array('type'=>'textarea','label' => 'Racine des groupes utilisateurs'), 'readonlyLogin' => array('type'=>'text','label' => 'Identifiant du compte lecture seule'), 'readonlyPassword' => array('type'=>'password','label' => 'Mot de passe du compte lecture seule'), 'adminLogin' => array('type'=>'text','label' => 'Identifiant compte administrateur'), 'adminPassword' => array('type'=>'password','label' => 'Mot de passe compte administrateur'), 'mapping' => array('type'=>'textarea','label' => 'Plan de liaisons des champs'), 'authenticationMode' => array('type'=>'text','label' => 'Authentication mode'), ); //retourne le mapping champ ad -> champ ustilisateur de base function default_mapping(){ $mapping = array( 'sn'=>array('label'=>'Nom','field'=>'name','source'=>'active_directory'), 'givenname'=>array('label'=>'Prénom','field'=>'firstname','source'=>'active_directory'), 'mail'=>array('label'=>'E-mail','field'=>'mail','source'=>'active_directory'), 'telephonenumber'=>array('label'=>'Téléphone fixe','field'=>'phone','source'=>'active_directory'), 'mobile'=>array('label'=>'Téléphone mobile','field'=>'mobile','source'=>'active_directory'), 'title'=>array('label'=>'Fonction','field'=>'function','source'=>'active_directory'), //prend le samaccountname ou le userprincipalname en fct de la conf 'samaccountname'=>array('label'=>'samaccountname','field'=>function(&$user,$infos){ $user->login = str_replace($this->domain,'',mb_strtolower($infos[$this->authenticationMode][0])); },'source'=>'active_directory'), 'origin'=>array('label'=>'samaccountname','field'=>'origin','value'=>'active_directory','source'=>'hardcoded'), 'department'=>array('label'=>'Nom','field'=>'service','source'=>'active_directory'), 'thumbnailphoto'=>array('label'=>'Photo 1','field'=>function(&$user,$infos){ $user->meta['ldap_avatar'] = base64_encode($infos['thumbnailphoto'][0]); },'source'=>'active_directory'), 'jpegphoto'=>array('label'=>'Photo 2','field'=>function(&$user,$infos){ $user->meta['ldap_avatar'] = base64_encode($infos['jpegphoto'][0]); },'source'=>'active_directory'), 'accountexpires'=>array('label'=>'Expiration','field'=>function(&$user,$infos){ $seconds = (float)($infos['accountexpires'][0] / 10000000); //Convertion d'un timestamp AD en timestamp UNIX $timestamp = round($seconds - (((1970-1601) * 365.242190) * 86400)); $user->meta['expiration'] = $timestamp; },'source'=>'active_directory'), //on traite memberof de manière particuliere donc pas de field 'memberof'=>array('label'=>'Membre de','field'=>'','source'=>'active_directory'), 'manager'=>array('label'=>'Manager','field'=>'manager','source'=>'active_directory'), 'userprincipalname'=>array('label'=>'userprincipalname','field'=>'','source'=>'active_directory'), 'whencreated'=>array('label'=>'Date création','field'=>function(&$user,$infos){ if(!isset($infos['whencreated'][0]) || strlen($infos['whencreated'][0])<12 ) return; $created = substr($infos['whencreated'][0],0,8).' '.substr($infos['whencreated'][0],8,2).':'.substr($infos['whencreated'][0],10,2); $user->created = strtotime($created); },'source'=>'active_directory') ); return $mapping; } //retourn les mapping champ ad->champ user par défaut + customs function getMapping(){ $mapping = array(); $jsonmapping = json_decode($this->mapping,true) ; if(is_array($jsonmapping)){ foreach(json_decode($this->mapping,true) as $json){ if(!isset($json['slug'])) continue; $json['source'] = 'active_directory'; $mapping[$json['slug']]= $json; } } $mapping = array_merge(self::default_mapping(),$mapping); return $mapping; } //Gestion des modes d'authentification possibles function authenticationModes($key=null){ $items = array( 'samaccountname' => array('label'=>'SamAccountName','filter'=>function($server,$login){ return "(&(samaccountname=".$login."))(objectClass=user)(objectCategory=person))"; }), 'userprincipalname' => array('label'=>'UserPrincipalName','filter'=>function($server,$login){ return "(&(userprincipalname=".$login.$server->domain.")(objectClass=user)(objectCategory=person))"; }), ); if(!isset($key)) return $items; return isset($items[$key]) ? $items[$key] : array('label'=>'Non définis','filter'=>function($server,$login){}); } //connexion ad function login($options = array()){ $options = array_merge(array( 'mode' => 'readonly', 'ssl' => false ),$options); putenv('LDAPTLS_REQCERT=never'); $port = $options['ssl'] ? $this->sslPort : $this->port ; $ip = $this->ip; $ip = 'ldap'.($options['ssl']? 's' :'').'://'.$ip; $ip .= ':'.$port; if($ip==null || $port==null || $this->userRoot==null) throw new Exception('Paramètres de connexion manquants',400); $this->datasource = ldap_connect($ip); if(!$this->datasource) throw new Exception('Connexion échouée', 404); ldap_set_option($this->datasource,LDAP_OPT_PROTOCOL_VERSION,$this->protocolVersion); ldap_set_option($this->datasource, LDAP_OPT_REFERRALS, 0); switch($options['mode']){ case 'admin': $login = $this->adminLogin; $password = decrypt($this->adminPassword); break; case 'readonly': $login = $this->readonlyLogin; $password = decrypt($this->readonlyPassword); break; case 'login': $login = $options['login']; $password = $options['password']; break; } try{ if(@ldap_bind($this->datasource,$login,$password) == false) throw new Exception(ldap_error($this->datasource), 403); }catch(Exception $e){ if(ldap_errno($this->datasource)==49){ throw new Exception('Identifiant ou mot de passe incorrect', 403); }else{ throw new Exception('Connexion échouée :'.ldap_error($this->datasource), 404); } } } /** * Recherche des valeurs dans la base de données en fonction du filter * @param Racine contexte de la recherche * @param Filtre contenant les éléments a rechercher * @param Liste des attributs à récupérer * @return tableau contenant les objets correspondants a la recherche */ public function search($dns, $filter=null, $attributes=array(),$limit=0){ $searches = ldap_search( array_fill(0, count($dns), $this->datasource) , $dns,$filter,$attributes,0,$limit); $infos = array(); foreach($searches as $search){ if($search === false) continue; $infos = array_merge($infos ,ldap_get_entries($this->datasource, $search)); } return $infos; } //Déconnexion ad function logout(){ if($this->datasource!=null) @ldap_close($this->datasource); } //Récuperation des utilisateurs function users($filters = array(),$options = array()){ require_once(__DIR__.SLASH.'AdFirmRank.class.php'); $mapping = $this->getMapping(); $options = array_merge(array( 'limit' => 0, // limit de users (0= pas de limite) 'select' => array_keys($mapping), //Colonnes a cibler 'group' => true, // charger les groupes ad 'rank' => true, // charger les rank et les firms 'manager' => false, // charger les managers en tant qu'objet user 'activeOnly' => true //n'afficher que les comptes non expirés ),$options); $groups = $options['group'] ? $this->groups() : array(); $ranks = array(); if($options['rank'] ){ foreach(AdFirmRank::loadAll( array(), null, null, array('*'), 1) as $firmRank){ if(!isset($ranks[$firmRank->group])) $ranks[$firmRank->group] = array(); $ranks[$firmRank->group][] = $firmRank; } } $users = array(); $default_filters = array(); $filterChain = ''; $default_filters['objectClass'] = 'user'; $default_filters['objectCategory'] = 'person'; $filters = array_merge($default_filters,$filters); if(count($filters)>1) $filterChain ='(&'; foreach($filters as $key=>$value) $filterChain .= '('.$key.'='.$value.')'; if(count($filters)>1) $filterChain .= ')'; $entries = $this->search(explode(PHP_EOL,$this->userRoot),$filterChain,$options['select'],$options['limit']); if(count($entries)==0 || $entries['count']==0) return $users; if(!isset($entries[0])) return $users; $usersDn = array(); for($i=0; $i<$entries['count']; $i++){ $entry = $entries[$i]; //Vérifie que le compte n'est pas expiré (nb : 0 et 9223372036854775807 sont les deux valeurs possibles pour un n'expire jamais (allez comprendre la logique microsoft...)) if($options['activeOnly'] && isset($entry['accountexpires'][0]) && $entry['accountexpires'][0]!=0 && $entry['accountexpires'][0]!=9223372036854775807){ //Convertion en seconds $seconds = (float)($entry['accountexpires'][0] / 10000000); //Convertion d'un timestamp AD en timestamp UNIX $timestamp = round($seconds - (((1970-1601) * 365.242190) * 86400)); if($timestamp <= time()) continue; } $user = self::user_object($entry,$mapping,$options,$groups,$ranks); //on stocke l'id de l'ad ayant provisionné ce user dans les méta pour réutilisations ulterieure (ex save du mdp) $user->meta['activedirectory_server'] = $this->id; $user->meta = json_encode($user->meta); if($options['manager']) $usersDn[$entry['dn']] = $user; $users[$user->login] = $user; } //Chargement des managers if($options['manager']){ foreach($users as $key=>$user){ if(empty($user->manager)) continue; if(isset($usersDn[$user->manager])){ $user->manager = $usersDn[$user->manager]; }else{ $manager = $this->users(array( 'dn' => $user->manager ),array( 'limit' => 1, // limit de users (0= pas de limite) 'group' => true, // charger les groupes ad 'rank' => true, // charger les rank et les firms 'rights' => false, // charger les rank et les firms 'manager' => true, // charger les managers en tant qu'objet user 'activeOnly' => true //n'afficher que les comptes non expirés )); $user->manager = array_shift($manager); } $users[$key] = $user; } } return $users; } //Remplis et return un objet user en fonction d'une entrée ad static function user_object($entry,$mapping,$options = array(),$groups=array(),$ranks=array()){ $user = new User(); //récuperation des attributs mappés (défaut et custom) foreach($mapping as $ldap=>$attribute){ $field = $attribute['field']; if(empty($field )) continue; //si le champ doit contenir une valeur en dur (et non ldap ex : origin : active_directory) if($attribute['source']=='hardcoded' && !empty($attribute['value'])){ $user->$field = $attribute['value']; continue; } //si aucune correspondance trouvée dans l'entrée ldap on passe au suivant if(empty($entry[$ldap]) || empty($entry[$ldap][0])) continue; //si le field définit est une méthode anonyme if(!is_string($field)){ $field($user,$entry); //si le field définit est un simple attribut }else{ if(property_exists($user,$field)){ $user->$field = $entry[$ldap][0]; }else{ $user->meta[$field] = $entry[$ldap][0]; } } } //Récuperation des groupes direct et indirects $user->groups = array(); if(isset($entry['memberof'])){ for($i=0; $igroups[] = $groupItem['label']; if(isset($ranks[$groupItem['label']])){ foreach($ranks[$groupItem['label']] as $firmrank){ $user->ranks[$firmrank->firm][$firmrank->rank] = $firmrank->join('rank'); $user->firms[$firmrank->firm] = $firmrank->join('firm'); } } foreach($groupItem['memberof'] as $parentDn){ self::group_recursive_fill($user,$groups,$parentDn,$ranks); } } function userChange($login,$changes){ switch($this->authenticationMode){ case 'samaccountname': $filter = "(".$this->authenticationMode."=".$login.")"; break; case 'userprincipalname': default: $filter= "(".$this->authenticationMode."=".$login.$this->domain.")"; break; } $filter = '(&'.$filter.'(objectClass=user)(objectCategory=person))'; $entries= $this->search(array($this->userRoot),$filter); $cn = (isset($entries[0])) ? $entries[0]['dn'] : false; if($cn == false) throw new Exception('Impossible de trouver l\'utilisateur sur ce serveur'); putenv('LDAPTLS_REQCERT=never'); foreach($changes as $adfield=>$value){ if($adfield == 'password') continue; if($value != ''){ $attributes[$adfield][0] = $value; return ldap_modify($this->datasource,$cn,$attributes); }else{ //evite le crash si l'attribut n'existe pas lors d'une supression $attributes[$adfield] ='0'; ldap_modify($this->datasource,$cn,$attributes); $attributes[$adfield] = array(); return ldap_mod_del($this->datasource,$cn,$attributes); } } if(isset($changes['password'])){ if (!ldap_mod_replace($this->datasource, $cn , self::encrypt_password($changes['password']))) throw new Exception("Impossible de modifier le mot de passe : ".ldap_error($this->datasource)); } } public static function encrypt_password($newPassword){ $newPassword = "\"" . $newPassword . "\""; $len = strlen( $newPassword ); $newPassw = ""; for ( $i = 0; $i < $len; $i++ ) $newPassw .= $newPassword[$i]."\000"; return array("unicodePwd" => $newPassw); } //Récuperation des groupes ad function groups($filters = array()){ $groups = array(); $default_filters = array(); $filterChain = ''; $default_filters['objectClass'] = 'group'; $filters = array_merge($default_filters,$filters); if(count($filters)>1) $filterChain ='(&'; foreach($filters as $key=>$value) $filterChain .= '('.$key.'='.$value.')'; if(count($filters)>1) $filterChain .= ')'; $entries = $this->search(explode(PHP_EOL,$this->groupRoot),$filterChain,array('name','memberOf')); for($i=0; $i<$entries['count']; $i++){ $info = $entries[$i]; $group = array( 'label' => $info['name'][0], 'dn' => $info['dn'], 'memberof' => array() ); //on récupere les groupes parents if(isset($info['memberof'])){ for($u=0; $ugroupRoot)===false) continue; $group['memberof'][] = $info['memberof'][$u]; } } $groups[$group['dn']] = $group; } uasort($groups,function($a,$b){ return $a['label'] > $b['label']; }); return $groups; } } ?>