123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- <?php
- /**
- * Define a Serveur active directory
- * @author Jean CARRUESCO
- * @category Plugin
- * @license MIT
- */
- class AdServer extends Entity{
- public $id;
- public $ip; //IP (Texte)
- public $port = 389; //Port (Nombre Entier)
- public $sslPort = 636; //Port SSL (Nombre Entier)
- public $protocolVersion = 3; //Version de protocole ldap (Texte)
- public $domain; //Domaine (Texte)
- public $userRoot; //Racine des comptes utilisateurs (Texte Long)
- public $groupRoot; //Racine des groupes utilisateurs (Texte Long)
- public $readonlyLogin; //Identifiant du compte lecture seule (Texte)
- public $readonlyPassword; //Mot de passe du compte lecture seule (Mot de passe)
- public $adminLogin; //Identifiant compte administrateur (Texte)
- public $adminPassword; //Mot de passe compte administrateur (Mot de passe)
- public $mapping; //Plan de liaisons des champs (Texte Long)
- public $authenticationMode = 'samaccountname'; //Authentication mode (Texte)
- private $datasource; //Ressource AD
- protected $TABLE_NAME = 'activedirectory_ad_server';
- public $entityLabel = 'Serveur active directory';
- public $fields = array(
- 'id' => 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 <String> Racine contexte de la recherche
- * @param <String> Filtre contenant les éléments a rechercher
- * @param <Array> Liste des attributs à récupérer
- * @return <Array> 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; $i<count($entry['memberof'])-1; ++$i){
- if(empty($groups[$entry['memberof'][$i]])) continue;
- self::group_recursive_fill($user,$groups,$entry['memberof'][$i],$ranks);
- }
- }
- return $user;
- }
- //ajout les groupes a l'utilisateur de manière récursive (un groupe peut être parent d'un autre)
- static function group_recursive_fill(&$user,$groups,$dn,$ranks){
- if(!isset($groups[$dn])) return;
- $groupItem = $groups[$dn];
- $user->groups[] = $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; $u<count($info['memberof'])-1; ++$u){
- //on ne prend que les groupes parent qui font partie de la racine groupe
- if(strrpos($info['memberof'][$u],$this->groupRoot)===false) continue;
- $group['memberof'][] = $info['memberof'][$u];
- }
- }
- $groups[$group['dn']] = $group;
- }
- uasort($groups,function($a,$b){
- return $a['label'] > $b['label'];
- });
- return $groups;
- }
- }
- ?>
|