activedirectory.plugin.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. <?php
  2. /*
  3. @name Connexion AD (LDAP)
  4. @author Valentin CARRUESCO <valentin.carruesco@sys1.fr>
  5. @link http://www.sys1.fr
  6. @licence Copyright Sys1
  7. @version 1.0.0
  8. @description Plugin pour l'identification sur Active Directory (LDAP)
  9. */
  10. //Recuperation d'un instance ldap avec les configuraiton serveur
  11. function ldap_instance($ssl = false){
  12. require_once(__DIR__.SLASH.'ActiveDirectory.class.php');
  13. global $conf;
  14. $ldap = new ActiveDirectory();
  15. $ldap->server = ($ssl ? 'ldaps://':'' ).$conf->get('activedirectory_server');
  16. $ldap->port = $ssl ? $conf->get('activedirectory_ssl_port'): $conf->get('activedirectory_port');
  17. $ldap->userRoot = $conf->get('activedirectory_users_root');
  18. $ldap->groupRoot = $conf->get('activedirectory_groups_root');
  19. $ldap->domain = $conf->get('activedirectory_domain');
  20. $ldap->protocolVersion = 3;
  21. return $ldap;
  22. }
  23. //Récuperation de l'ensemble des utilisateurs en LDAP (appellé par User::getAll)
  24. function ldap_plugin_all_users(&$users, $loadRights=false){
  25. require_once(__DIR__.SLASH.'ActiveDirectory.class.php');
  26. global $conf;
  27. try{
  28. $ldap = ldap_instance();
  29. $ldap->connect($conf->get('activedirectory_reader_login'),$conf->get('activedirectory_reader_password'));
  30. $infos = $ldap->populate($conf->get('activedirectory_users_root'));
  31. if($infos["count"] == 0) return $ldap->disconnect();
  32. $allUsers = array();
  33. foreach($infos as $info){
  34. if( isset($info['userprincipalname'][0]) && trim($info['userprincipalname'][0])!=''){
  35. $newUser = new User();
  36. ldap_user_fill($ldap,$newUser,$info,true,false);
  37. if($loadRights) user_rank_firm_by_group($newUser);
  38. $manager = new User();
  39. if(isset($info['manager'][0])){
  40. foreach($infos as $info2){
  41. if(!isset($info2['distinguishedname'][0]) || $info2['distinguishedname'][0] != $info['manager'][0]) continue;
  42. ldap_user_fill($ldap,$manager,$info2,false,false);
  43. }
  44. }
  45. $newUser->manager = $manager;
  46. $allUsers[] = $newUser;
  47. }
  48. }
  49. $users = $allUsers;
  50. }catch(Exception $e){
  51. $ldap->disconnect();
  52. //Décommenter la ligne qui suit pour avoir un message d'erreur si pb de connexion à l'AD
  53. //throw new Exception("Une erreur est survenue lors de la connexion à l'AD");
  54. }
  55. }
  56. //Récuperation d'un utilisateur précis en LDAP (appellé par User::check)
  57. function ldap_plugin_identification(&$user,$login,$password,$loadRight,$loadManager=true,$noPassword=false){
  58. global $_,$conf;
  59. require_once(__DIR__.SLASH.'ActiveDirectory.class.php');
  60. if($user != false) return;
  61. $ldap = ldap_instance();
  62. try{
  63. if($noPassword){
  64. $ldap->connect($conf->get('activedirectory_reader_login'), $conf->get('activedirectory_reader_password'));
  65. }else{
  66. $ldap->connect($login.$ldap->domain, $password);
  67. }
  68. $infos = $ldap->search($conf->get('activedirectory_users_root'),"(&(userprincipalname=".$login.$ldap->domain.")(objectClass=user))");
  69. if($infos["count"]>0){
  70. $user = new User();
  71. ldap_user_fill($ldap,$user,$infos[0],$loadRight,$loadManager);
  72. user_rank_firm_by_group($user);
  73. }
  74. $avatarPath = __ROOT__.FILE_PATH.AVATAR_PATH.$user->login.'.jpg';
  75. if(isset($user->meta['ldap_avatar'])){
  76. if(!file_exists(__ROOT__.FILE_PATH.AVATAR_PATH)) mkdir(__ROOT__.FILE_PATH.AVATAR_PATH,0755,true);
  77. file_put_contents($avatarPath,base64_decode($user->meta['ldap_avatar']));
  78. }
  79. }catch(Exception $e){
  80. //nothing to do
  81. }
  82. $ldap->disconnect();
  83. }
  84. //Remplissage d'une classe User en fonction des atttributs LDAPS
  85. function ldap_user_fill($ldap,&$user,$infos,$loadRight=false,$loadManager = false){
  86. require_once(__DIR__.SLASH.'ActiveDirectory.class.php');
  87. //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...))
  88. if(isset($infos['accountexpires'][0]) && $infos['accountexpires'][0]!=0 && $infos['accountexpires'][0]!=9223372036854775807){
  89. //Convertion en seconds
  90. $seconds = (float)($infos['accountexpires'][0] / 10000000);
  91. //Convertion d'un timestamp AD en timestamp UNIX
  92. $timestamp = round($seconds - (((1970-1601) * 365.242190) * 86400));
  93. if($timestamp <= time()) return;
  94. }
  95. if(isset($infos['sn'][0])) $user->setName($infos['sn'][0]);
  96. if(isset($infos['givenname'][0])) $user->setFirstName($infos['givenname'][0]);
  97. if(isset($infos['mail'][0])) $user->setMail($infos['mail'][0]);
  98. if(isset($infos['telephonenumber'][0])) $user->setPhone($infos['telephonenumber'][0]);
  99. if(isset($infos['mobile'][0])) $user->setMobile($infos['mobile'][0]);
  100. if(isset($infos['title'][0])) $user->setFunction($infos['title'][0]);
  101. if(isset($infos['samaccountname'][0])) $user->login = mb_strtolower($infos['samaccountname'][0]);
  102. if(isset($infos['department'][0])) $user->service = $infos['department'][0];
  103. if(isset($infos['thumbnailphoto'][0])) $user->meta['ldap_avatar'] = base64_encode($infos['thumbnailphoto'][0]);
  104. if(isset($infos['jpegphoto'][0])) $user->meta['ldap_avatar'] = base64_encode($infos['jpegphoto'][0]);
  105. global $conf;
  106. $metafields = explode(PHP_EOL,$conf->get('activedirectory_metafields'));
  107. foreach ($metafields as $line) {
  108. $metaInfos = explode(':',$line);
  109. if(count($metaInfos)<4) continue;
  110. list($label,$type,$adslug,$slug) = $metaInfos;
  111. if(isset($infos[$adslug][0])) $user->meta[$slug] = $infos[$adslug][0];
  112. }
  113. if(isset($infos['whencreated'][0]) && strlen($infos['whencreated'][0])>=12 ){
  114. $created = substr($infos['whencreated'][0],0,8).' '.substr($infos['whencreated'][0],8,2).':'.substr($infos['whencreated'][0],10,2);
  115. $user->created = strtotime($created);
  116. }
  117. if(isset($infos['manager'][0])){
  118. $user->manager = $infos['manager'][0];
  119. if($loadManager){
  120. $managerEntry = $ldap->userFromCn($infos['manager'][0]);
  121. if($managerEntry['count'] > 0 ){
  122. $manager = new User();
  123. ldap_user_fill($ldap,$manager,$managerEntry[0],$loadRight,false);
  124. if(isset($managerEntry['sn'][0])) $manager->setName($managerEntry[0]['sn'][0]);
  125. if(isset($managerEntry['givenname'][0])) $manager->setFirstName($managerEntry[0]['givenname'][0]);
  126. if(isset($managerEntry['mail'][0])) $manager->setMail($managerEntry[0]['mail'][0]);
  127. if(isset($managerEntry['telephonenumber'][0])) $manager->setPhone($managerEntry[0]['telephonenumber'][0]);
  128. if(isset($managerEntry['mobile'][0])) $manager->setMobile($managerEntry[0]['mobile'][0]);
  129. if(isset($managerEntry['title'][0])) $manager->function = $managerEntry[0]['title'][0];
  130. if(isset($managerEntry['samaccountname'][0])) $manager->login = mb_strtolower($managerEntry[0]['samaccountname'][0]);
  131. $user->manager = $manager;
  132. }
  133. }
  134. }
  135. $user->origin = 'active_directory';
  136. if($loadRight){
  137. $groups = array();
  138. if(isset($infos['memberof'])){
  139. for($i=0; $i<count($infos['memberof'])-1; ++$i){
  140. $groupCN = $infos['memberof'][$i];
  141. list($group,$root) = explode(',',$groupCN);
  142. list($entity,$group) = explode('=',$group);
  143. //TODO decommenter une fois les pb de perf résolus
  144. //$ldap->recursiveGroups($groups,$groupCN);
  145. $groups[] = $group;
  146. }
  147. }
  148. $user->groups = $groups;
  149. }
  150. }
  151. function activedirectory_user_save(&$user,$userForm,&$response){
  152. if($user->origin != 'active_directory') return;
  153. if($user->login != $userForm->login) throw new Exception("L'identifiant n'est pas modifiable");
  154. global $_,$conf;
  155. require_once(__DIR__.SLASH.'ActiveDirectory.class.php');
  156. //Régles de définition de mot de passe
  157. if(!empty($userForm->password)){
  158. if(strlen($userForm->password)<7) throw new Exception("Le mot de passe doit être supérieur à 7 caractères");
  159. if(!preg_match('|[0-9]|i', $userForm->password)) throw new Exception("Le mot de passe doit contenir au moins un chiffre");
  160. if(!preg_match('|[a-z]|i', $userForm->password)) throw new Exception("Le mot de passe doit contenir au moins une lettre");
  161. if(!preg_match('|[a-z]|', $userForm->password)) throw new Exception("Le mot de passe doit contenir au moins une lettre Minuscule");
  162. if(!preg_match('|[A-Z]|', $userForm->password)) throw new Exception("Le mot de passe doit contenir au moins une lettre Majuscule");
  163. }
  164. $ldap = ldap_instance(true);
  165. if($conf->get('activedirectory_admin_login')=='') throw new Exception("Le compte AD admin n'est pas configuré, veuillez contacter un administrateur");
  166. $ldap->connect($conf->get('activedirectory_admin_login'),$conf->get('activedirectory_admin_password'));
  167. $cn = $ldap->cnFromLogin($user->login);
  168. if(!$cn) throw new Exception("Impossible de trouver l'utilisateur dans la base active directory");
  169. $user->phone = $userForm->phone;
  170. $user->mobile = $userForm->mobile;
  171. $infos = $ldap->search($conf->get('activedirectory_users_root'),"(&(userprincipalname=".$user->login.$ldap->domain.")(objectClass=user))");
  172. if(in_array('telephonenumber', $infos[0]))
  173. $ldap->set($cn,'telephonenumber',$userForm->phone);
  174. if(in_array('mobile', $infos[0]))
  175. $ldap->set($cn,'mobile',$userForm->mobile);
  176. if(in_array('jpegphoto', $infos[0])){
  177. $avatarPath = __ROOT__.FILE_PATH.AVATAR_PATH.$user->login.'.jpg';
  178. if(file_exists($avatarPath))
  179. $ldap->set($cn,'jpegphoto',file_get_contents($avatarPath));
  180. }
  181. if(!empty($userForm->password)){
  182. $ldap->change_password($cn,$userForm->password);
  183. }
  184. $response['warning'] = 'Vous êtes sur un compte de société, seules les informations suivantes ont été modifiées :<br/>
  185. - Téléphone<br/>
  186. - Mobile<br/>
  187. - Mot de passe (7 caracteres minimum : Majuscules, minucules et chiffres)<br/>
  188. - Avatar (JPG uniquement)<br/>';
  189. $ldap->disconnect();
  190. }
  191. function user_rank_firm_by_group(&$user){
  192. require_once(__DIR__.SLASH.'ActiveDirectoryGroup.class.php');
  193. global $conf, $myFirm;
  194. $firms = array();
  195. $ranks = array();
  196. if(!isset($user->groups)) $user->groups = array();
  197. foreach(ActiveDirectoryGroup::loadAll(array(), null, null, array('*'),1) as $group){
  198. if(!in_array($group->adgroup,$user->groups)) continue;
  199. $firm = $group->join('firm');
  200. $rank = $group->join('rank');
  201. $firms[$firm->id] = $firm;
  202. if(!isset($ranks[$firm->id])) $ranks[$firm->id] = array();
  203. $ranks[$firm->id][$rank->id] = $rank;
  204. }
  205. //Récuperation du rang par défaut
  206. if(empty($ranks) && $conf->get('activedirectory_default_rank')!=''){
  207. $firstFirm = Firm::load(array());
  208. $firms[$firstFirm->id] = $firstFirm;
  209. $ranks[$firstFirm->id][$conf->get('activedirectory_default_rank')] = Rank::getById($conf->get('activedirectory_default_rank'));
  210. }
  211. if(!empty($ranks)) {
  212. $user->setFirms($firms);
  213. $defaultFirm = !empty($user->preference('default_firm')) ? $user->preferences['default_firm'] : key($firms);
  214. $myFirm = $firms[$defaultFirm];
  215. }
  216. $user->ranks = $ranks;
  217. $user->loadRights();
  218. }
  219. function activedirectory_action(){
  220. global $_;
  221. require_once(__DIR__.SLASH.'action.php');
  222. }
  223. function activedirectory_plugin_menu(&$settingMenu){
  224. global $_, $myUser;
  225. if($myUser->can('activedirectory','configure'))
  226. $settingMenu[]= array(
  227. 'sort' =>1,
  228. 'url' => 'setting.php?section=activedirectory',
  229. 'icon' => 'fas fa-angle-right',
  230. 'label' => 'Active Directory'
  231. );
  232. }
  233. function activedirectory_plugin_page(){
  234. global $_;
  235. if(in_array($_['section'],array('activedirectory')) && file_exists(__DIR__.SLASH.'setting.'.$_['section'].'.php'))
  236. require_once(__DIR__.SLASH.'setting.'.$_['section'].'.php');
  237. }
  238. function activedirectory_plugin_section(&$sections){
  239. $sections['activedirectory'] = 'Gestion des droits sur les échanges avec l\'AD';
  240. }
  241. function activedirectory_plugin_install($id){
  242. if($id != 'fr.idleman.activedirectory') return;
  243. Entity::install(__DIR__);
  244. }
  245. function activedirectory_plugin_uninstall($id){
  246. if($id != 'fr.idleman.activedirectory') return;
  247. Entity::uninstall(__DIR__);
  248. }
  249. function activedirectory_directory_list(&$usermapping){
  250. foreach ($usermapping as $login => $infos) {
  251. $user = $infos['object'];
  252. //todo à dynamiser en fct de plugin_activedirectory_metafields
  253. if(isset($user->meta['personalPhone'])) $usermapping[$login]['values']['Portable (perso)'] = '<a href="tel: '.$user->meta['personalPhone'].'">'.$user->meta['personalPhone'].'</a>';
  254. // if(isset($user->meta['jobstart'])) $usermapping[$login]['values']['Date début contrat'] = $user->meta['jobstart'];
  255. }
  256. }
  257. function activedirectory_account_global(){
  258. global $myUser,$conf;
  259. $metafields = explode(PHP_EOL,$conf->get('activedirectory_metafields')); ?>
  260. <div class="row">
  261. <?php foreach ($metafields as $line):
  262. $metaInfos = explode(':',$line);
  263. if(count($metaInfos)<4) continue;
  264. list($label,$type,$adslug,$slug) = $metaInfos;
  265. ?>
  266. <div class="col-md-6">
  267. <label for="<?php echo $slug; ?>"><?php echo $label ?> :</label>
  268. <input id="<?php echo $slug; ?>" name="<?php echo $slug; ?>" class="form-control-plaintext" readonly="readonly" type="text" value="<?php echo isset($myUser->meta[$slug])?$myUser->meta[$slug]:''; ?>">
  269. </div>
  270. <?php endforeach; ?>
  271. </div>
  272. <?php
  273. }
  274. //Déclaration des settings de base
  275. //Types possibles : text,select ( + "values"=> array('1'=>'Val 1'),password,checkbox. Un simple string définit une catégorie.
  276. Configuration::setting('activedirectory',array(
  277. "Configuration de l'AD",
  278. 'activedirectory_server' => array("label"=>"Serveur","type"=>"text","legend"=>"L'adresse IP du serveur AD","placeholder"=>"192.168.XXX.XXX"),
  279. 'activedirectory_port' => array("label"=>"Port","type"=>"number","legend"=>"Le port sur lequel attaquer le serveur AD","placeholder"=>"389"),
  280. 'activedirectory_ssl_port' => array("label"=>"Port SSL","type"=>"number","legend"=>"Le port SSL sur lequel attaquer le serveur AD","placeholder"=>"636"),
  281. 'activedirectory_domain' => array("label"=>"Domaine","type"=>"text","legend"=>"Le domaine sur lequel se base l'AD","placeholder"=>"@EXAMPLE.LOCAL"),
  282. 'activedirectory_users_root' => array("label"=>'Racine des utilisateurs <small title="Cliquez pour ajouter une racine utilisateur supplémentaire" class="text-primary no-select right pointer" onclick="activedirectory_activedirectory_add_roots(this);"><i class="fas fa-plus"></i> Ajouter une racine supplémentaire</small>',"type"=>"text","legend"=>"La racine où chercher les users","placeholder"=>"OU=SYS1,OU=UTILISATEURS,OU=sys1.fr,DC=SYS1,DC=LOCAL","parameters"=>array("data-root"=>"users")),
  283. 'activedirectory_groups_root' => array("label"=>'Racine des groupes <small title="Cliquez pour ajouter une racine groupe supplémentaire" class="text-primary no-select right pointer" onclick="activedirectory_activedirectory_add_roots(this);"><i class="fas fa-plus"></i> Ajouter une racine supplémentaire</small>',"type"=>"text","legend"=>"La racine où chercher les groupes","placeholder"=>"OU=SYS1,OU=UTILISATEURS,OU=sys1.fr,DC=SYS1,DC=LOCAL","parameters"=>array("data-root"=>"groups")),
  284. "Compte Lecture seule",
  285. 'activedirectory_reader_login' => array("label"=>"CN","type"=>"text","legend"=>"Le Common Name du compte de lecture seule","placeholder"=>"CN=reader_account,OU=EXAMPLE,OU=APPLICATIONS,OU=example.fr,..."),
  286. 'activedirectory_reader_password' => array("label"=>"Mot de passe","type"=>"password","legend"=>"Le mot de passe du compte de lecture seule","placeholder"=>""),
  287. "Compte Administrateur",
  288. 'activedirectory_admin_login' => array("label"=>"CN","type"=>"text","legend"=>"Le Common Name du compte administrateur","placeholder"=>"CN=administrator_account,OU=EXAMPLE,OU=APPLICATIONS,OU=example.fr,..."),
  289. 'activedirectory_admin_password' => array("label"=>"Mot de passe","type"=>"password","legend"=>"Le mot de passe du compte administrateur","placeholder"=>""),
  290. "Champs de méta informations",
  291. 'activedirectory_metafields' => array("label"=>"Méta informations","type"=>"textarea","legend"=>"Vous pouvez remplir des méta champs pour les utilisateurs (un champ par ligne).<br>
  292. Ces métas champs sont requis par certains plugins et peuvent être renseignés depuis l'AD via la syntaxe : <code>Libellé:Type:nom-champ-ad:nom-meta</code>","placeholder"=>"Date début contrat:date:description:jobstart","parameters"=>array("cols"=>"100")),
  293. "Utilisateurs de l'AD",
  294. 'activedirectory_default_rank' => array("label"=>"Rang par défaut","legend"=>"Utilisé si aucun groupe AD n'a été défini pour le rang \"Utilisateur\" standard","type"=>"rank")
  295. ));
  296. Plugin::addJs('/js/main.js');
  297. Plugin::addCss('/css/main.css');
  298. Plugin::addHook('directory_list',"activedirectory_directory_list");
  299. Plugin::addHook("account_global", "activedirectory_account_global");
  300. Plugin::addHook("install", "activedirectory_plugin_install");
  301. Plugin::addHook("uninstall", "activedirectory_plugin_uninstall");
  302. Plugin::addHook("user_login", "ldap_plugin_identification");
  303. Plugin::addHook("user_load", "ldap_plugin_all_users");
  304. Plugin::addHook("user_save","activedirectory_user_save");
  305. Plugin::addHook("user_rank_firm", "user_rank_firm_by_group");
  306. Plugin::addHook("section", "activedirectory_plugin_section");
  307. Plugin::addHook("action", "activedirectory_action");
  308. Plugin::addHook("menu_setting", "activedirectory_plugin_menu");
  309. Plugin::addHook("content_setting", "activedirectory_plugin_page");
  310. ?>