le CACHE LDAP (table users) est systématiquement utilisé const LDAP_CACHE_ALWAYS_ON = true; // - NON => le CACHE LDAP (table users) n'est utilisé QUE si l'option ldap_cached est activée dans la config //const LDAP_CACHE_ALWAYS_ON = false; // DEBUG temporaire, uniquement pour ce fichier (module level) //const DEBUG = true; const DEBUG = false; class LdapConnectionsTable extends AppTable { private $DEBUG_MODE; // read from config private $authenticationType; // (EP 23/5/19) Optimisation: // Les utilisateurs sont stockés dans un cache (BD) // pour limiter les accès au LDAP private $LDAP_CACHED = FALSE; // Max time for ldap cache validity (in minutes) : 60 = 1h private $LDAP_CACHE_VALIDITY_DURATION = 2; public $useTable = false; private $host; private $port; private $baseDn; private $filter; /*MCM*/ // EP //private $anonymous; private $ldap_authentified; private $bindDn; private $bindPass; /* fin MCM*/ private $LDAP_USED = TRUE; private $fakeLDAPUsers = []; // print only if debug mode ON private function mydebugmsg($arg, $stop = false) { if ($this->DEBUG_MODE) { //Configure::write('debug', true); debug($arg); if ($stop) exit(); } } public function __construct() { parent::__construct(); } // EP /* public function useFakeLdap() { return ! $this->useLdap(); } public function useLdap() { $this->_checkConfiguration(); return $this->LDAP_USED; } */ // LDAP format ==> DB format // @param: array $user (LDAP-like formatted) // @return: object(App\Model\Entity\User) private function _getLDAPuserFormattedAsDB($user_from_LDAP) { /* (INPUT) Voici le format d'un user du LDAP : [ 'sn' => [ (int) 0 => 'Pallier' ], 'mail' => [ (int) 0 => 'Etienne.Pallier@irap.omp.eu' ], 'givenname' => [ (int) 0 => 'Etienne' ], 'uid' => [ (int) 0 => 'epallier' ], 'userpassword' => [ (int) 0 => '' ] ] */ /* (OUTPUT) Voici le format d'un user de la table users (BD) ( object(App\Model\Entity\User) ) : // - Champs de la table : 'id' => (int) 2, 'nom' => 'Pallier Etienne', 'username' => 'epallier', 'email' => 'Etienne.Pallier@irap.omp.eu', 'role' => 'Super Administrateur', 'groupes_metier_id' => (int) 3, 'password' => 'pass crypté...', 'groupes_thematique_id' => null, 'sur_categorie_id' => null, // - Champs ajoutés par CakePhp3: '[new]' => false, '[accessible]' => [ '*' => true, 'id' => false ], '[dirty]' => [], '[original]' => [], '[virtual]' => [], '[hasErrors]' => false, '[errors]' => [], '[invalid]' => [], '[repository]' => 'Users' */ $usersTable = TableRegistry::getTableLocator()->get('Users'); $user = $usersTable->newEntity(); $user->nom = $user_from_LDAP['sn'][0].' '.$user_from_LDAP['givenname'][0]; //$user->username = $user_from_LDAP['uid']; $user->username = $user_from_LDAP[$this->authenticationType][0]; $user->email = isset($user_from_LDAP['mail']) ? $user_from_LDAP['mail'][0] : "NO_MAIL"; // Par defaut, role = UTILISATEUR $user->role = 'Utilisateur'; /* * (EP 5/6/19 on ne fait plus ça car le ldap ne retourne pas les passwords) // C'est la version "cryptée" qui doit etre stockée $user->password = $user_from_LDAP['userpassword'][0]; */ return $user; } private function _getDBusersFormattedAsLDAP($usersfromDB) { $usersFormattedAsLDAP = []; foreach ($usersfromDB as $userfromDB) $usersFormattedAsLDAP[] = $this->_getDBuserFormattedAsLDAP($userfromDB); $usersFormattedAsLDAP['count'] = sizeof($usersFormattedAsLDAP); //$this->mydebugmsg("count : ".$usersFormattedAsLDAP['count']); return $usersFormattedAsLDAP; } // DB format ==> LDAP format private function _getDBuserFormattedAsLDAP($user) { $names = explode(" ", $user['nom']); $givenName = isset($names[1]) ? $names[1] : " "; return [ // Nom 'sn' => [ $names[0] ], // Email 'mail' => [ $user['email'] ], // Pnom 'givenname' => [ $givenName ], // Login ("uid" for IRAP, "samaccountname" for CRAL) $this->authenticationType => [ $user['username'] ], // Pass 'userpassword' => [ $user['password'] ] ]; } private function _getFakeDBuserFormattedAsLDAP($user) { return [ // Nom 'sn' => [ $user['sn'] ], // Email 'mail' => [ $user['mail'] ], // Pnom 'givenname' => [ $user['givenname'] ], // Login ("uid" for IRAP, "samaccountname" for CRAL) //$this->authenticationType => [ /* 'uid' => [ $user['uid'] ], */ DEFAULT_AUTH_TYPE => [ $user[DEFAULT_AUTH_TYPE] ], // Pass 'userpassword' => [ $user['userpassword'] ] ]; } private function _buildFakeLdapUsers() { return $this->_buildFakeLdapUsersFromDB(); } private function _buildFakeLdapUsersFromDB() { //NEW //$users = TableRegistry::getTableLocator()->get('Users')->find(); $users = TableRegistry::getTableLocator()->get('Fakeldapusers')->find(); $ldapUsers = []; //$ldapUsers = $users->toArray(); foreach ($users as $user) { //debug($user); /* Voici le format d'un user de la table users (BD) // - Champs de la table : 'id' => (int) 2, 'nom' => 'Pallier Etienne', 'username' => 'epallier', 'email' => 'Etienne.Pallier@irap.omp.eu', 'role' => 'Super Administrateur', 'groupes_metier_id' => (int) 3, 'password' => 'pass crypté...', 'groupes_thematique_id' => null, 'sur_categorie_id' => null, // - Champs ajoutés par CakePhp3: '[new]' => false, '[accessible]' => [ '*' => true, 'id' => false ], '[dirty]' => [], '[original]' => [], '[virtual]' => [], '[hasErrors]' => false, '[errors]' => [], '[invalid]' => [], '[repository]' => 'Users' */ //NEW //$ldapUsers[] = $this->_getDBuserFormattedAsLDAP($user); $ldapUsers[] = $this->_getFakeDBuserFormattedAsLDAP($user); /* $names = explode(" ", $user['nom']); $givenName = isset($names[1]) ? $names[1] : " "; $ldapUsers[] = [ // Nom 'sn' => [ $names[0] ], // Email 'mail' => [ $user['email'] ], // Pnom 'givenname' => [ $givenName ], // Login ("uid" for IRAP, "samaccountname" for CRAL) $this->authenticationType => [ $user['username'] ], // Pass 'userpassword' => [ $user['password'] ] ]; */ } /* EP (aout 2017) * ATTENTION : Utilisateur IMPORTANT. * Avec cet utilisateur, on simule un utilisateur qui n'est PAS dans la table utilisateurs * Il devrait donc se voir attribuer un role "Utilisateur" sans pour autant que ça soit écrit dans la table !!! * login = '_NouvelUtilisateur_username' * pass = '_NouvelUtilisateur_password' * $prefix = "_NouvelUtilisateur_"; */ /* (EP 20210201) : DÉSORMAIS INUTILE * car TOUS les utilisateurs doivent être dans la table users, sinon ils ne peuvent pas se connecter. * Avant, on avait besoin de ça car en mode LDAP, la table users ne contenait QUE les users privilégiés * et donc un user du ldap pouvait se connecter même s'il n'était pas dans la table users, * et on lui affectait automatiquement un profil "Utilisateur" * $ldapUsers[] = [ 'sn' => [ 'UTILISATEUR' ], 'givenname' => [ 'FAKE_LDAP' ], // 'mail' => [$login.'email'], 'mail' => [ 'fakeldapuser@domain.fr' ], // $this->authenticationType => [$prefix.'username'], //'uid' => [ $this->authenticationType => [ $this->_getTheFakeLdapUser()['login'] ], // $this->authenticationType => ['usere'], 'userpassword' => [ $this->_getTheFakeLdapUser()['pass'] ] // 'userpassword' => ['toto'], ]; $ldapUsers[] = [ 'sn' => [ 'SUPERADMIN' ], 'givenname' => [ 'FAKE_LDAP' ], // 'mail' => [$login.'email'], 'mail' => [ 'fakeldapuser@domain.fr' ], // $this->authenticationType => [$prefix.'username'], //'uid' => [ $this->authenticationType => [ 'superadmin' ], // $this->authenticationType => ['usere'], 'userpassword' => [ '$2y$10$LZzpws3oDidBcqO/Fy1RTedLLk3ENTmplny5J7bZ6R1PqFoGOw3Ma' ] // 'userpassword' => ['toto'], ]; */ return $ldapUsers; } private function _checkAndGetConfiguration() { $this->configurationsTable = TableRegistry::getTableLocator()->get('Configurations'); $this->CONF = $this->configurationsTable ->find() ->where(['id =' => 1]) ->first(); // Activation forcée de l'option ldap_cached si LDAP_CACHE_ALWAYS_ON // (EP) Ca résoud un bug dû au fait que des infos sont cherchées dans la table users alors que le user n'y est pas... if (LDAP_CACHE_ALWAYS_ON) $this->CONF->ldap_cached = true; $config = $this->CONF; $this->usersTable = TableRegistry::getTableLocator()->get('Users'); $this->DEBUG_MODE = $config->mode_debug; $this->LDAP_USED = $config->ldap_used; // (1) Mode "local" (sans ldap, ou FAKE ldap) if (! $this->LDAP_USED) { // Seulement pour tester le mode ldap_cached en mode FAKE LDAP: //$this->CONF->ldap_cached = TRUE; // (EP 202102) bugfix, en mode fakldap, on est toujours en 'uid' //$this->authenticationType = $config->ldap_authenticationType; $this->authenticationType = DEFAULT_AUTH_TYPE; if (empty($this->fakeLDAPUsers)) $this->fakeLDAPUsers = $this->_buildFakeLdapUsers(); return true; } // debug($this->fakeLDAPUsers); // (2) Mode "LDAP" (avec ldap) if (! empty($config->ldap_host) && ! empty($config->ldap_port) && ! empty($config->ldap_baseDn) && ! empty($config->ldap_authenticationType) && ! empty($config->ldap_filter)) { $this->host = $config->ldap_host; $this->port = $config->ldap_port; $this->baseDn = $config->ldap_baseDn; $this->filter = $config->ldap_filter; $this->authenticationType = $config->ldap_authenticationType; $this->ldap_authentified = $config->ldap_authentified; $this->bindDn = $config->ldap_bindDn; $this->bindPass = $config->ldap_bindPass; return true; } // (3) Si on est toujours là, c'est pas normal => EXCEPTION $ldapConfig = $config->toArray(); throw new Exception('The ldap configuration is not valid :
'); } // @return ldap users from DB users table private function _getAllUsersFromDB($do_update=false) { // (EP202102) inutile puisque on fait déjà ça à la connexion (login) if ($do_update) $this->_updateUsersCacheIfNeeded(); return $this->usersTable->find(); } private function _getAllUsersFromLDAP() { return $this->LDAP_USED ? $this->_ldapSearch($this->filter, []) : $this->fakeLDAPUsers; } /** * @return $users_fetched or FALSE */ // REAL or FAKE LDAP public function getAllLdapUsers() { if (! $this->_checkAndGetConfiguration()) return FALSE; // By default, nothing found, ERROR $users_fetched = FALSE; // LDAP optimized (cached) if ($this->CONF->ldap_cached) { //TODO: // Les 3 types de LDAP (cache, real, et fake) doivent etre 3 objets ayant exactement les MEMES methodes // Par defaut, $this-> doit pointer vers le REAL ldap, les 2 autres sont accessibles via $this->cache-> et $this->fake-> // $this->cache->_getAllLdapUsers(); // envoyés directement dans le bon format $users_fetched = $this->_getAllUsersFromDB(); $users_fetched = $this->_getDBusersFormattedAsLDAP($users_fetched); } // LDAP direct (no optimization) else { //try { $users_fetched = $this->_getAllUsersFromLDAP(); // $from = $this->LDAP_USED ? $this : $this->fake // return $from->_getllLdapUsers(); // Noter que $user_fetched peut etre egal a FALSE (si rien trouvé) //return $users_fetched; //} //catch (Exception $e) {} } return $users_fetched; } public function getAuthenticationType() { return $this->authenticationType; } // EP added public function getFakeLdapUser($login) { foreach ($this->fakeLDAPUsers as $user) { /* debug($login); debug($user); */ //if ($login == $user[$this->authenticationType][0]) if ($login == $user[DEFAULT_AUTH_TYPE][0]) return $user; } return FALSE; } /** * Return a list of Users with key = username => value = [login, email] */ public function getUsersLoginAndEmail() { $usersWithNameAndEmail = []; // Get all users (with ALL their attributes) $u = $this->getAllLdapUsers(); // Sort users //sort($u); //debug($u); $this->mydebugmsg("ldap users 0 and 1:"); $this->mydebugmsg($u[0]); //$this->mydebugmsg($u[1]); // (EP) Refactorisation pour éviter code redondant du "bon vieux" temps des stagiaires... // Il suffit souvent de réfléchir un peu pour résumer 10 lignes en 1 seule... if ($this->LDAP_USED) $this->mydebugmsg("total count is : ".$u['count']); // 440 for IRAP 6/6/19 (LDAP direct), 441 (LDAP cached because 1 old user to be deleted = imoro) //$this->mydebugmsg($u); $nb_users = $this->LDAP_USED ? $u['count'] : sizeof($u)-1; for ($i = 0; $i < $nb_users; $i ++) { // $utilisateurs["Pallier Etienne"] = ["email"] ////$usersWithNameAndEmail[ $u[$i]['sn'][0].' '.$u[$i]['givenname'][0] ] = $u[$i]['mail'][0]; $email = isset($u[$i]['mail']) ? $u[$i]['mail'][0] : "NO_MAIL"; $usersWithNameAndEmail[ $u[$i]['sn'][0].' '.$u[$i]['givenname'][0] ] = array( //"login" => $u[$i]['uid'][0] // (IRAP) //"login" => $u[$i]['samaccountname'][0] // CRAL "login" => $u[$i][$this->authenticationType][0], "email" => $email ); } // Sort users (without modifying the keys, don't use sort() but asort() !!!!!!!!!!!!!) ksort($usersWithNameAndEmail); //debug($usersWithNameAndEmail); return $usersWithNameAndEmail; } /** * Return a list of Users with key = username & value = username */ public function getListUsers() { $utilisateurs = []; // Get all users (with ALL their attributes) $u = $this->getAllLdapUsers(); // Sort users //sort($u); //debug($u); // (EP) Refactorisation pour éviter code redondant ci-dessous, c'était pourtant pas compliqué, poil dans la main... $nb_users = $this->LDAP_USED ? $u['count'] : sizeof($u)-1; // $utilisateurs["Pallier Etienne"] = "Pallier Etienne" for ($i = 0; $i < $nb_users; $i ++) $utilisateurs[ $u[$i]['sn'][0].' '.$u[$i]['givenname'][0] ] = $u[$i]['sn'][0].' '.$u[$i]['givenname'][0]; //debug($utilisateurs); // Sort users (without modifying the keys, don't use sort() but asort() !!!!!!!!!!!!!) ksort($utilisateurs); //debug($utilisateurs); return $utilisateurs; } /** * Return a list of login of Users with key = username & value = login */ public function getListLoginUsers() { $u = $this->getAllLdapUsers(); $utilisateurs = []; if ($this->LDAP_USED) { for ($i = 0; $i < $u['count']; $i ++) { $utilisateurs[$u[$i]['sn'][0] . ' ' . $u[$i]['givenname'][0]] = $u[$i][$this->authenticationType][0]; } } else { for ($i = 0; $i < sizeof($u) - 1; $i ++) { $utilisateurs[$u[$i]['sn'][0] . ' ' . $u[$i]['givenname'][0]] = $u[$i][$this->authenticationType][0]; } } return $utilisateurs; } /** * Return a list of mail of Users with key = username & value = mail */ public function getListEmailUsers() { $u = $this->getAllLdapUsers(); $utilisateurs = []; if ($this->LDAP_USED) { for ($i = 0; $i < $u['count']; $i ++) { if (isset($u[$i]['mail'][0])) { $utilisateurs[$u[$i]['sn'][0] . ' ' . $u[$i]['givenname'][0]] = $u[$i]['mail'][0]; } else { $utilisateurs[$u[$i]['sn'][0] . ' ' . $u[$i]['givenname'][0]] = 'N/A'; } } } else { for ($i = 0; $i < sizeof($u) - 1; $i ++) { $utilisateurs[$u[$i]['sn'][0] . ' ' . $u[$i]['givenname'][0]] = $u[$i]['mail'][0]; } } return $utilisateurs; } /** * Return size of list users public function getNbUsers() { $u = $this->getAllLdapUsers(); if ($this->LDAP_USED) { $nbUsers = $u['count']; } else { $nbUsers = sizeof($u) - 1; } return $nbUsers; } */ // Utilisateur du ldap qui n'est pas dans la table utilisateurs // => il a donc le role "Utilisateur" PAR DEFAUT private function _getTheFakeLdapUser() { return [ 'login' => '_fake_ldap_user_', //'pass' => '_fake_ldap_user_pass' 'pass' => '$2y$10$Vx8E8VirQGnoLYpn7qqNAO4UhTJMyrUVCzkcy0Obh3ceABuxMxk.q' ]; } // @return user from DB whith login = $userLogin (or NULL) private function _getUserFromDB($userLogin) { //debug($userLogin); exit; //$ldapUser = TableRegistry::getTableLocator()->get('Users')->find() return $this->usersTable->find() ->where([ 'username' => $userLogin ]) ->first(); } /** * @return boolean * - FALSE if cache is up to date * - TRUE if cache is expired (outdated or NULL) */ private function _ldapCacheIsExpired() { if (is_null($this->CONF->ldap_cache_last_update)) return TRUE; /* About strtotime("") : * - forward slash (/) signifies American M/D/Y formatting => strtotime("11/12/10") * - a dash (-) signifies European D-M-Y => strtotime("11-12-10")) * - a period (.) signifies ISO Y.M.D. => strtotime("11.12.10") */ $date_now = date("Y-m-d H:i:s"); $this->mydebugmsg("now :".$date_now); $date_now = new \DateTime($date_now); $date_cached = $this->CONF->ldap_cache_last_update; // Pourquoi j'ai pas les secondes qui s'affichent ???!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! $this->mydebugmsg("cached :".$date_cached); /* bugfixing $date_cached pour Inventirap (IRAP) (php5 ou vieux mysql ou pb de config ???) : * Inventirap affiche l'année sur 2 chiffres : 'cached :05/06/19 15:36' * Ma version perso affiche l'année sur 4 chiffres : 'cached :05/06/2019 15:36' * => Il faut donc remettre l'année sur 4 chiffres si besoin */ //$date_cached='05/06/19 15:36'; //$this->mydebugmsg("cached :".$date_cached); // '05/06/19 15:36' (if wrong format) if ( strpos($date_cached,'/') !== FALSE ) { $year = substr(strrchr($date_cached, '/'), 0,4); // '/19 ' ou '/201' if (substr($year,-1) == ' ') //$year = '20'.substr($y,1,2); // '2019' $date_cached = substr($date_cached,0,6)."20".substr($year,1,2).substr($date_cached,8); // '05/06/19 15:36'; } $this->mydebugmsg("cached2 :".$date_cached); $date_cached = \DateTime::createFromFormat('d/m/Y H:i',$date_cached); //debug("now :".$date_now->format('Y-m-d H:i:s') ); //debug("cached :".$date_cached->format('Y-m-d H:i:s') ); $this->mydebugmsg("Temps écoulé depuis last save:"); $this->mydebugmsg($date_now->diff($date_cached)->format('%i mn %s sec')); $date_cached->add(new \DateInterval('PT'.$this->CONF->ldap_cache_validity_duration.'M')); $this->mydebugmsg("date_cached (added) :".$date_cached->format('Y-m-d H:i:s') ); /* $date_now = $date_now->getTimestamp(); $date_cached = $date_cached->getTimestamp(); debug("now :".$date_now); debug("cached :".$date_cached); $date_now = strtotime($date_now); $date_cached = strtotime($date_cached); debug("now :". date("Y-m-d H:i:s", $date_now)); debug("cached :". date("Y-m-d H:i:s", $date_cached)); //debug($this->CONF->ldap_cache_last_update ." plus ".$this->CONF->ldap_cache_validity_duration." minutes < $now ?"); debug($date_cached ." plus ".$this->CONF->ldap_cache_validity_duration." minutes < $date_now ?"); $last_save_date_plus_delay = strtotime( "+".$this->CONF->ldap_cache_validity_duration." minutes", strtotime($this->CONF->ldap_cache_last_update) ); */ //debug("decalage ?"); //debug($date_cached < $date_now); // TEST /* $now = new \DateTime("2019-05-29 15:32:00"); debug("now :".$now->format('Y-m-d H:i:s') ); $d = $this->CONF->ldap_cache_last_update; $d = \DateTime::createFromFormat('d/m/Y H:i',$d); debug("d :".$d->format('Y-m-d H:i:s') ); $d->add(new \DateInterval('PT1M')); debug("d :".$d->format('Y-m-d H:i:s') ); debug($d > $now); $d->sub(new \DateInterval('PT2M')); debug("d :".$d->format('Y-m-d H:i:s') ); debug($d < $now); $d->add(new \DateInterval('PT1M')); debug("d :".$d->format('Y-m-d H:i:s') ); debug($d == $now); */ $this->mydebugmsg("expired ?"); $this->mydebugmsg($date_cached < $date_now); return ($date_cached < $date_now); //return TRUE; } private function _updateUserPassword($userId, $newPwd) { $query = $this->usersTable->query(); $query->update() ->set(['password' => $newPwd]) ->where(['id' => $userId]) ->execute(); } private function _isUserAlreadyCached(array $user_logged) { //debug($user_logged); return $this->_getUserFromDB($user_logged[$this->authenticationType][0]) !== null; } /* * UPDATE users CACHE (save all (fake)LDAP users into "users" db table) * ONLY if necessary */ //private function _updateLdapCacheIfNeeded() { private function _updateUsersCacheIfNeeded(array $user_logged = null) { // Si cache pas activé => return //debug("C1"); //if (!LDAP_CACHE_ALWAYS_ON && !$this->CONF->ldap_cached) return; if (! $this->CONF->ldap_cached) return; /* * On ne met à jour le cache des users QUE si : * - le user qui vient de se loguer n'est pas dans le cache * OU * - le cache est périmé * * Sinon => RETURN */ //debug("C2"); if ( ! $this->_ldapCacheIsExpired() && ! ( $user_logged && !$this->_isUserAlreadyCached($user_logged) ) ) return; DEBUG && debug("User pas dans DB users"); //debug("C3"); // Get all users from (fake)LDAP $usersFromLDAP = $this->_getAllUsersFromLDAP(); // LDAP should return at least some users, otherwise ERROR assert(!empty($usersFromLDAP)); // Get all users from DB $usersFromDB = $this->_getAllUsersFromDB(FALSE); // Add new(fake)ldap users (only) and update existing users foreach ($usersFromLDAP as $userFromLDAP) { //$this->mydebugmsg("current user is ".$userFromLDAP['uid'][0]); ////$this->mydebugmsg("current user is ".$userFromLDAP[$this->authenticationType][0]); // Si utilisateur mal formé (pas de nom, pas de login, pas de mail) => ne pas l'enregistrer, passer au suivant if ( ( // pas de champ login !isset($userFromLDAP[$this->authenticationType]) || !isset($userFromLDAP[$this->authenticationType][0]) || $userFromLDAP[$this->authenticationType][0]=='' ) || ( // pas de champ sn !isset($userFromLDAP['sn']) || !isset($userFromLDAP['sn'][0]) || $userFromLDAP['sn'][0]=='' ) ) { $this->mydebugmsg("Utilisateur LDAP mal formé => je ne le stocke pas"); continue; } $userFromLDAPLogin = $userFromLDAP[$this->authenticationType][0]; // (EP 5/6/19 : on n'a pas accès au password stocké dans le ldap, on ne peut donc pas le stocker dans le cache) //$currentLdapUserPwd = $userFromLDAP['userpassword'][0]; //$this->mydebugmsg("current user is ".$currentLdapUserLogin); // Do not save fake ldap user in DB if ($userFromLDAPLogin == '_fake_ldap_user_') continue; $userFromDB = $this->_getUserFromDB($userFromLDAPLogin); // 1) ADD NEW user if (is_null($userFromDB)) { //$this->mydebugmsg("user does not exist => add it to DB"); // Ajout du nouvel utilisateur $this->usersTable->save($this->_getLDAPuserFormattedAsDB($userFromLDAP)); //$this->usersTable->query("FLUSH TABLES"); // Correction de son mot de passe /* (EP 5/6/19 : on n'a pas accès au password stocké dans le ldap, on ne peut donc pas le stocker dans le cache) $userFromDB = $this->_getUserFromDB($currentLdapUserLogin); assert(!is_null($userFromDB)); //$this->mydebugmsg("UPDATE users SET password = '".$currentLdapUserPwd."' WHERE id = ".$userFromDB->id); //$this->usersTable->query("UPDATE users SET password = '".$userFromLDAP['userpassword'][0]."' WHERE id = ".$userFromDB->id); $this->_updateUserPassword($userFromDB->id, $currentLdapUserPwd); */ /* $query = $this->usersTable->query(); $query->update() ->set([ 'password' => $userFromLDAP['userpassword'][0] ]) ->where(['id' => $userFromDB->id]) ->execute(); */ //exit; } // NEW user // 2) UPDATE Existing user => only if email changed else { //TODO: a virer, only for test $BOURRIN=false; //$this->mydebugmsg("user already exists => update it"); // 1) Update email (only if changed) if ( isset($userFromLDAP['mail']) && ( $BOURRIN || ($userFromDB->email != $userFromLDAP['mail'][0]) ) ) { //$this->mydebugmsg("email diff ? "); //$this->mydebugmsg($userFromDB->email !== $userFromLDAP['mail'][0]); $userFromDB->email = $userFromLDAP['mail'][0]; // Tant qu'on y est, on met aussi à jour le nom et prenom $userFromDB->nom = $userFromLDAP['sn'][0].' '.$userFromLDAP['givenname'][0]; $this->usersTable->save($userFromDB); } // 2) Update password (only if changed) /* (EP 5/6/19 : on n'a pas accès au password stocké dans le ldap, on ne peut donc pas le stocker dans le cache) //debug("password = ".$userFromDB->password); //debug("userpassword = ".$userFromLDAP['userpassword'][0]); /STAR (EP 28/5/19) Sauvegarde du mot de passe : ATTENTION ! * Si on utilise la methode save() de cakephp, le mot de passe n'est pas copié tel quel, mais re-crypté !!! * On doit donc le copier "à la hard" avec du code sql direct pour shunter la methode save() * Ceci n'est donc pas possible : $userFromDB->password = $userFromLDAP['userpassword'][0]; $this->usersTable->save($userFromDB); STAR/ // Par contre, ceci fonctionne : //debug("UPDATE users SET password = '".$userFromLDAP['userpassword'][0]."' WHERE id = ".$userFromDB->id); if ( $BOURRIN || ($userFromDB->password != $currentLdapUserPwd) ) { //$this->mydebugmsg("pass diff ? "); //$this->mydebugmsg($userFromDB->password !== $userFromLDAP['userpassword'][0]); //$this->usersTable->query("UPDATE users SET password = '".$userFromLDAP['userpassword'][0]."' WHERE id = ".$userFromDB->id); $this->_updateUserPassword($userFromDB->id, $currentLdapUserPwd); } */ } // existing user => update } // 3) DELETE old users (which are no more in LDAP) // dangereux, et faire attention de ne pas supprimer le user superadmin ldap créé au début du projet... $DO_DELETE = false; // - Select only the login (['uid']) column $usersFromLDAPLogins = array_column($usersFromLDAP, $this->authenticationType); $this->mydebugmsg($usersFromLDAPLogins); /* * Avec LDAP IRAP, on obtient pour $usersFromLDAPLogins, 0 à 439 (440 users) : [ (int) 0 => [ 'count' => (int) 1, (int) 0 => 'chillembrand' ], (int) 1 => [ 'count' => (int) 1, (int) 0 => 'ablanchard' ], (int) 2 => [ 'count' => (int) 1, (int) 0 => 'ahui-bon-hoa' ], (int) 3 => [ 'count' => (int) 1, (int) 0 => 'jatteia' ], ... ] */ // - Select only the [0] column $usersFromLDAPLogins = array_column($usersFromLDAPLogins, 0); $this->mydebugmsg($usersFromLDAPLogins); /* * Avec LDAP IRAP, on obtient pour $usersFromLDAPLogins, 0 à 439 (440 users) : [ [0]=> string(12) "chillembrand" [1]=> string(10) "ablanchard" [2]=> string(12) "ahui-bon-hoa" [3]=> string(7) "jatteia" ... ] */ foreach ($usersFromDB as $userFromDB) { /* debug("DELETE"); debug("look for $userFromDB->username"); debug("in "); */ //debug($usersFromLDAP); //$usersFromLDAPLogins = array_column($usersFromLDAP, 'uid'); if (! in_array($userFromDB->username, $usersFromLDAPLogins)) { // (6/6/19) 3 users found (superadmin, imoro, mimelhaine) $this->mydebugmsg("OLD user should be deleted:"); $this->mydebugmsg($userFromDB->username); $DO_DELETE && $userFromDB->delete(); } } // Update LDAP cache last update time (= NOW) $this->CONF->ldap_cache_last_update = date("Y-m-d H:i:s"); // 2019-05-28 03:25:34 $this->configurationsTable->save($this->CONF); DEBUG && debug("CACHE (table users) mis à jour"); //exit; } // _updateUsersCacheIfNeeded() /* // REAL LDAP only // from DB ==> to LDAP // @return: ldap user if ok (else FALSE) //private function checkAndFetchLDAPUserFromDB($user_login, $user_password) { private function _getLdapUserFromDB($user_login) { // Doit aussi return false si ce user_login est "périmé" (sa date "created" est > 2 mois par exemple), // ce qui obligera à relire ses données dans le LDAP et donc se mettre à jour //if (! $this->LDAP_CACHED) return FALSE; // If LDAP cache (in users table) is expired, update it (save again ldap into DB) //if ($this->_ldapCacheIsExpired()) $this->_updateLdapCache(); $this->_updateUsersCacheIfNeeded(); // 1) Search user in DB $ldapUser = $this->_getUserFromDB($user_login); /S $ldapUser = TableRegistry::getTableLocator()->get('Users')->find() ->where([ 'username' => $user_login ]) ->first(); S/ // User not found => fail if (is_null($ldapUser)) return FALSE; /S // 2) Check password // Bad password => fail if ( ! (new DefaultPasswordHasher())->check($user_password,$ldapUser['userpassword'][0]) ) return FALSE; // User found and password ok => return it S/ // User found ok => return it formatted as ldap return $this->_getDBuserFormattedAsLDAP($ldapUser); } */ /* (EP202102) inutile car il ne faut pas sauver (cacher) uniquement CE user, mais TOUS les users du (fake)ldap // from (fake)LDAP ==> to DB // SAVE new user in DB private function _saveNewUserInDB($user_from_LDAP) { if (! $this->CONF->ldap_cached) return TRUE; // 1) Format LDAP user as for DB $user_from_LDAP_formatted_as_DB = $this->_getLDAPuserFormattedAsDB($user_from_LDAP); // 2) Save DB formatted user into DB $usersTable = TableRegistry::getTableLocator()->get('Users'); if ( ! $usersTable->save($user_from_LDAP_formatted_as_DB, [ //'checkRules' => false, 'checkExisting' => TRUE ])) return FALSE; // user has been saved (cached) ok return TRUE; } */ // REAL LDAP only // SEARCH en 4 étapes //private function _ldapSearch($filter, $just_these, $user_login=NULL, $user_password=NULL) { private function _ldapSearch($filter, $just_these) { // (1) CONNEXION // Pour faire du ldaps : entrer 'ldaps://' devant $this->host $ldapConnection = ldap_connect($this->host, $this->port) or die("Impossible de se connecter au serveur ldap $this->host:$this->port"); // (2) SET OPTIONS ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3) or die("Impossible de positionner l'option LDAP LDAP_OPT_PROTOCOL_VERSION=".LDAP_OPT_PROTOCOL_VERSION.", 3"); // (3) BINDING simple // Signature de ldap_bind : ($link_identifier, $bind_rdn = null, $bind_password = null) // => return bool true on success or false on failure if ($this->ldap_authentified) // - ldap authentifié => binding authentifié $ldapbind = @ldap_bind($ldapConnection, $this->bindDn, $this->bindPass); else // - ldap anonyme => binding anonyme $ldapbind = @ldap_bind($ldapConnection); if (! $ldapbind) { die("Impossible de faire un BIND simple sur le serveur ldap " . $this->ldap_authentified ? "(mode authentifié) !" : "(mode anonyme) !"); } // (4) SEARCH : Recherche d'infos (sur le user qui se connecte, ou bien sur tous les users) // $filter = "(&".$this->filter."(".$this->authenticationType . '=' . $user_login."))"; // ex: (&(compteinfo=Oui)(uid=epallier)) $results = ldap_search($ldapConnection, $this->baseDn, $filter, $just_these) or die("Impossible de faire une recherche sur le serveur LDAP, réponse: " . ldap_error($ldapConnection) ); $search = ldap_get_entries($ldapConnection, $results); //echo $results["count"]." entries returned\n"; if ($search === FALSE) die("Impossible de récupérer les attributs utilisateur(s) du serveur LDAP, réponse: " . ldap_error($ldapConnection) ); //return $search[0]; return $search; } // _ldapSearch() private function _ldapAuth($user, $password) { $ldapConnection = ldap_connect($this->host, $this->port) or die("Impossible de se connecter au serveur ldap $this->host:$this->port"); ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3) or die("Impossible de positionner l'option LDAP LDAP_OPT_PROTOCOL_VERSION=".LDAP_OPT_PROTOCOL_VERSION.", 3"); $ldapbind = @ldap_bind($ldapConnection, $user, $password); if ($ldapbind) return TRUE; return FALSE; } // MAIN ENTRY POINT of this class /* * @param string $user_login * @param string $user_password * @return logged user LDAP attributes (FALSE if user not found in LDAP) */ public function ldapAuthentication($user_login, $user_password) { // Bad configuration => FAIL if (! $this->_checkAndGetConfiguration()) return FALSE; // Par défaut, on retourne FALSE (échec de connexion) //$user_fetched = FALSE; // (EP 5/6/19 : on n'a pas accès au password stocké dans le ldap, on ne peut donc pas le stocker dans le cache) // 1 - REAL LDAP //debug($this->LDAP_USED); exit; if ($this->LDAP_USED) try { // No connexion allowed without password if (strlen(trim($user_password)) == 0) return FALSE; /* // TODO: optimisation possible // 1) Search user in CACHE (DB) $user_fetched = $this->checkAndFetchLDAPUserFromDB($user_login, $user_password); $this->mydebugmsg("(1) user found in DB is:"); $this->mydebugmsg($user_fetched); //TODO: A VIRER !!! //$user_fetched = FALSE; // 2) If not CACHED, search user in LDAP if ($user_fetched === FALSE) { */ //$user_fetched = $this->checkAndFetchUserFromLdap($user_login, $user_password); $just_these = []; // TODO: vérifier si cette ligne est bien utile ou pas... (avant on faisait ça) //if (! $this->ldap_authentified) $just_these = array("cn"); // Construction du filtre avec le filtre de la base de données avec un & sur le login de l'utilisateur // Si aucun filtre n'est défini dans la base de données on aura juste (& ($this->authenticationType=$user_login)) // ex: "(&(objectClass=person)(memberOf:1.2.840.113556.1.4.1941:=cn=ucbl.osu.cral,ou=groups,ou=27,ou=sim,ou=univ-lyon1,dc=univ-lyon1,dc=fr)(sAMAccountName=$user_login))"; $filter = '(&'.$this->filter.'('.$this->authenticationType.'='.$user_login.'))'; //TODO: optimisation, refactoriser si comportement général //$binddn .= ','.$this->baseDn; //$user_fetched = $this->_ldapSearch($filter, $just_these, $user_login, $user_password); $user_fetched = $this->_ldapSearch($filter, $just_these); //$this->mydebugmsg("(1) user found in LDAP is:"); //$this->mydebugmsg($user_fetched); //$this->mydebugmsg($user_fetched[0]); if ($user_fetched['count'] != 0) { $user_fetched = $user_fetched[0]; if ($this->_ldapAuth($user_fetched["dn"], $user_password)) { // CACHE the new user in DB for next time ///$this->_saveNewUserInDB($user_fetched[0]); // CACHE ALL ldap users $this->_updateUsersCacheIfNeeded($user_fetched); // return logged user (authentified) return $user_fetched; } //return null; //return FALSE; } return FALSE; //return $user_fetched; // Noter que $user_fetched peut etre egal a FALSE (si pas trouvé) } catch (Exception $e) { //echo 'Exception LDAP : ', $e->getMessage(), "\n"; debug('Exception LDAP : '); debug($e->getMessage()); exit; } // REAL LDAP // OU BIEN // 2 - FAKE LDAP used //debug($this->baseDn); $user_fetched = $this->getFakeLdapUser($user_login); /* debug($this->fakeLDAPUsers); debug($user_login); debug($user_password); debug($user_fetched); exit; */ $this->mydebugmsg("(1) user found in FAKE LDAP is:"); $this->mydebugmsg($user_fetched); /* Voici un exemple de ce qui est dans $user_fetched (fake ldap) : [ 'sn' => [ (int) 0 => 'Pallier' ], 'mail' => [ (int) 0 => 'Etienne.Pallier@irap.omp.eu' ], 'givenname' => [ (int) 0 => 'Etienne' ], 'uid' => [ (int) 0 => 'epallier' ], 'userpassword' => [ (int) 0 => '' ] ] */ // debug($user); //if ($user === false) return FALSE; if ($user_fetched !== false) { // $this->authenticationType peut valoir "uid" ou "cn"... (par défaut "uid" pour le fake ldap, à confirmer...) // if ($user['uid'][0] == "_NouvelUtilisateur_username" && $user['userpassword'][0] == "_NouvelUtilisateur_password") return $user; // if ($user[$this->authenticationType][0] == "_NouvelUtilisateur_username" && $user['userpassword'][0] == "_NouvelUtilisateur_password") return $user; // Est-ce le user FAKE LDAP par defaut (_fake_ldap_user_) ? si oui, on le laisse passer //if ($user_fetched[$this->authenticationType][0] == $this->_getTheFakeLdapUser()['login'] && $user_fetched['userpassword'][0] == $this->_getTheFakeLdapUser()['pass']) if ($user_fetched[DEFAULT_AUTH_TYPE][0] == $this->_getTheFakeLdapUser()['login'] && $user_fetched['userpassword'][0] == $this->_getTheFakeLdapUser()['pass']) return $user_fetched; /* debug("user_password = ".$user_password); debug("user found in db is "); debug($user_fetched); debug($user_fetched['userpassword'][0]); debug($user_fetched['userpassword'][0]); exit; */ // Sinon, on regarde si c'est un user de la table fakeldapusers if ( (new DefaultPasswordHasher())->check($user_password,$user_fetched['userpassword'][0]) ) { $this->_updateUsersCacheIfNeeded($user_fetched); return $user_fetched; } } // Ce login n'existe pas, ou alors il y a eu un problème sur fake ldap return FALSE; // END FAKE_LDAP // On met à jour le cache des users (si nécessaire) //if ($user_fetched !== FALSE) $this->_updateUsersCacheIfNeeded($user_fetched); // Retourne le user authentifié ou bien FALSE //return $user_fetched; } // ldapAuthentication() } // fin de class LdapConnectionsTable /* Voici un exemple de ce qui est dans $user_fetched[0] (structure LDAP IRAP) : // ce qui est retourné par le fake ldap (imitation bien faite non ?) [ 'sn' => [ (int) 0 => 'Pallier' ], 'mail' => [ (int) 0 => 'Etienne.Pallier@irap.omp.eu' ], 'givenname' => [ (int) 0 => 'Etienne' ], 'uid' => [ (int) 0 => 'epallier' ], 'userpassword' => [ (int) 0 => '' ] ] // VRAI LDAP, juste un extrait utile : [ 'sn' => [ 'count' => (int) 1, (int) 0 => 'Pallier' ], (int) 14 => 'sn', 'givenname' => [ 'count' => (int) 1, (int) 0 => 'Etienne' ], ] // VRAI LDAP, au complet : [ 'cn' => [ 'count' => (int) 1, (int) 0 => 'Etienne Pallier' ], (int) 0 => 'cn', 'homedirectory' => [ 'count' => (int) 1, (int) 0 => '/home/epallier' ], (int) 1 => 'homedirectory', 'uidnumber' => [ 'count' => (int) 1, (int) 0 => '20172' ], (int) 2 => 'uidnumber', 'objectclass' => [ 'count' => (int) 9, (int) 0 => 'top', (int) 1 => 'person', (int) 2 => 'organizationalPerson', (int) 3 => 'inetOrgPerson', (int) 4 => 'posixAccount', (int) 5 => 'shadowAccount', (int) 6 => 'irap', (int) 7 => 'hostObject', (int) 8 => 'sambaSamAccount' ], (int) 3 => 'objectclass', 'sambasid' => [ 'count' => (int) 1, (int) 0 => 'S-1-5-21-3149873848-2002230563-1027543705-41344' ], (int) 4 => 'sambasid', 'mail' => [ 'count' => (int) 1, (int) 0 => 'Etienne.Pallier@irap.omp.eu' ], (int) 5 => 'mail', 'olddn' => [ 'count' => (int) 1, (int) 0 => 'uid=pallier,ou=users,ou=laboratoire,dc=cesr,dc=fr' ], (int) 6 => 'olddn', 'userpassword' => [ 'count' => (int) 1, (int) 0 => '{SASL}epallier@IRAP.OMP.EU' ], (int) 7 => 'userpassword', 'sambantpassword' => [ 'count' => (int) 1, (int) 0 => 'ED9A0ECE0C6C7560A8DDF6A23B2C7C36' ], (int) 8 => 'sambantpassword', 'sambapwdlastset' => [ 'count' => (int) 1, (int) 0 => '1317291687' ], (int) 9 => 'sambapwdlastset', 'loginshell' => [ 'count' => (int) 1, (int) 0 => '/bin/bash' ], (int) 10 => 'loginshell', 'shadowexpire' => [ 'count' => (int) 1, (int) 0 => '-1' ], (int) 11 => 'shadowexpire', 'host' => [ 'count' => (int) 3, (int) 0 => 'gitlab1.irap.omp.eu', (int) 1 => 'gw.irap.omp.eu', (int) 2 => 'version2.irap.omp.eu' ], (int) 12 => 'host', 'uid' => [ 'count' => (int) 1, (int) 0 => 'epallier' ], (int) 13 => 'uid', 'sn' => [ 'count' => (int) 1, (int) 0 => 'Pallier' ], (int) 14 => 'sn', 'givenname' => [ 'count' => (int) 1, (int) 0 => 'Etienne' ], (int) 15 => 'givenname', 'gecos' => [ 'count' => (int) 1, (int) 0 => 'Etienne.Pallier' ], (int) 16 => 'gecos', 'gidnumber' => [ 'count' => (int) 1, (int) 0 => '2001' ], (int) 17 => 'gidnumber', 'tagmail' => [ 'count' => (int) 1, (int) 0 => 'Oui' ], (int) 18 => 'tagmail', 'compteinfo' => [ 'count' => (int) 1, (int) 0 => 'Oui' ], (int) 19 => 'compteinfo', 'arrivaldate' => [ 'count' => (int) 1, (int) 0 => '01/01/1933' ], (int) 20 => 'arrivaldate', 'birthday' => [ 'count' => (int) 1, (int) 0 => '07/08/1968' ], (int) 21 => 'birthday', 'telephonenumber' => [ 'count' => (int) 1, (int) 0 => '0561556648' ], (int) 22 => 'telephonenumber', 'roomnumber' => [ 'count' => (int) 1, (int) 0 => 'J039' ], (int) 23 => 'roomnumber', 'mailperso' => [ 'count' => (int) 1, (int) 0 => 'N/A' ], (int) 24 => 'mailperso', 'title' => [ 'count' => (int) 1, (int) 0 => 'M' ], (int) 25 => 'title', 'site' => [ 'count' => (int) 1, (int) 0 => 'Roche' ], (int) 26 => 'site', 'manager' => [ 'count' => (int) 1, (int) 0 => 'uid=mgiard,ou=users,dc=irap,dc=omp,dc=eu' ], (int) 27 => 'manager', 'statut1' => [ 'count' => (int) 1, (int) 0 => 'ITA' ], (int) 28 => 'statut1', 'o' => [ 'count' => (int) 1, (int) 0 => 'UPS' ], (int) 29 => 'o', 'gt1' => [ 'count' => (int) 1, (int) 0 => 'PEPS' ], (int) 30 => 'gt1', 'gt2' => [ 'count' => (int) 1, (int) 0 => 'GAHEC' ], (int) 31 => 'gt2', 'statut2' => [ 'count' => (int) 1, (int) 0 => 'GT2I' ], (int) 32 => 'statut2', 'affichageannuaire' => [ 'count' => (int) 1, (int) 0 => 'Oui' ], (int) 33 => 'affichageannuaire', 'count' => (int) 34, 'dn' => 'uid=epallier,ou=users,dc=irap,dc=omp,dc=eu' ] */ ?>