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é...', 'groupe_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 = $user_from_LDAP['mail'][0]; // Par defaut, role = UTILISATEUR $user->role = 'Utilisateur'; // 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); 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) //'uid' => [ $this->authenticationType => [ $user['uid'] ], // 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é...', 'groupe_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_"; */ $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'], ]; return $ldapUsers; } private function checkConfiguration() { $this->configurationsTable = TableRegistry::getTableLocator()->get('Configurations'); $this->CONF = $this->configurationsTable ->find() ->where(['id =' => 1]) ->first(); $config = $this->CONF; $this->usersTable = TableRegistry::getTableLocator()->get('Users'); $this->DEBUG_MODE = $config->mode_debug; $this->LDAP_USED = $config->ldap_used; if (! $this->LDAP_USED) { // Seulement pour tester le mode ldap_cached en mode FAKE LDAP: //$this->CONF->ldap_cached = TRUE; $this->authenticationType = $config->ldap_authenticationType; if (empty($this->fakeLDAPUsers)) $this->fakeLDAPUsers = $this->buildFakeLdapUsers(); return true; } // debug($this->fakeLDAPUsers); $ldapConfig = $config->toArray(); 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; } throw new Exception('The ldap configuration is not valid :
'); } // REAL LDAP only /** * @return array or boolean : all users from DB (CACHE of the LDAP) or FALSE (if table emtpy or expired data) */ private function fetchAllUsersFromDB() { // On remet à jour tous les 7 jours $PEREMPTION_NB_DAYS = 7; // Doit aussi retourner FALSE si la ligne FAKE de la table users // (celle qui contient le user_name "FAKE_USER") // a une date "updated" périmée (now - updated > $PEREMPTION_NB_DAYS) // (update automatique de tous les users, chaque semaine, pour rester synced avec le LDAP) // By default, no user in CACHE return FALSE; } // REAL LDAP only // Sauvegarde de tous les users du LDAP en BD (avec un rythme de mise à jour hebdo) // Seulement les champs: nom, pnom, login, pass, email, create, updated, profile private function saveAllUsersInDB($users_fetched) { // START TRANSACTION // 1) Update (ou création) de la ligne FAKE (contient le user_name "FAKE_USER") => avec une date "updated" // 2) Update (ou création) de chaque user contenu dans $users_fetched // Attention à ne pas perdre l'attribut "profile", surtout pour les users privilégiés!!! (les autres ont un profile = "Utilisateur") // END TRANSACTION (COMMIT) // SAVE s'est bien passé return TRUE; } // @return ldap users from DB users table private function _getAllLdapUsersFromDB($do_update=TRUE) { if ($do_update) $this->_updateLdapCacheIfNeeded(); return $this->usersTable->find(); } private function _getAllLdapUsersFromLDAP() { return $this->LDAP_USED ? $this->searchLdap($this->filter, []) : $this->fakeLDAPUsers; } /** * @return $users_fetched or FALSE */ // REAL or FAKE LDAP public function getAllLdapUsers() { if (! $this->checkConfiguration()) return FALSE; // By default, nothing found, ERROR $users_fetched = FALSE; // LDAP optimized (cached) if ($this->CONF->ldap_cached) { $users_fetched = $this->_getAllLdapUsersFromDB(); $users_fetched = $this->_getDBusersFormattedAsLDAP($users_fetched); } // LDAP direct (no optimization) else { try { //if ($this->checkConfiguration()) { // REAL LDAP //if ($this->LDAP_USED) { /* // 1) Search users in CACHE (DB) $users_fetched = $this->fetchAllUsersFromDB(); // 2) Not found in CACHE, so search users in LDAP if ($users_fetched === FALSE) { */ $users_fetched = $this->_getAllLdapUsersFromLDAP(); //$users_fetched = $this->searchLdap($this->filter, []); // CACHE the new user in DB for next time //if ($users_fetched !== FALSE) $this->saveAllUsersInDB($users_fetched); //} //} /* // FAKE LDAP else { $users_fetched = $this->fakeLDAPUsers; } */ // 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['uid'][0]) if ($login == $user[$this->authenticationType][0]) return $user; } return FALSE; } /** * Return a list of Users with key = username & value = 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... $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]; $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" => $u[$i]['mail'][0] ); // 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 ofUsers 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 _getUser($userLogin) { //debug($userLogin); return $this->usersTable->find() //$ldapUser = TableRegistry::getTableLocator()->get('Users')->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) : * 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(); } // REAL LDAP only // Save all LDAP users into "users" db table private function _updateLdapCacheIfNeeded() { // return if cache is not expired if (! $this->_ldapCacheIsExpired()) return; //$this->mydebugmsg("expiré"); // Get all users from LDAP $ldapUsersFromLDAP = $this->_getAllLdapUsersFromLDAP(); // LDAP should return at least some users, otherwise ERROR assert(!empty($ldapUsersFromLDAP)); // Get all users from DB $ldapUsersFromDB = $this->_getAllLdapUsersFromDB(FALSE); // Add new users (only) and update existing users foreach ($ldapUsersFromLDAP as $ldapUserFromLDAP) { //$this->mydebugmsg("current user is ".$ldapUserFromLDAP['uid'][0]); // Si utilisateur mal formé (pas de nom, pas de login, pas de mail) => ne pas l'enregistrer, passer au suivant if ( ( !isset($ldapUserFromLDAP[$this->authenticationType]) || !isset($ldapUserFromLDAP[$this->authenticationType][0]) || $ldapUserFromLDAP[$this->authenticationType][0]=='' ) || ( !isset($ldapUserFromLDAP['sn']) || !isset($ldapUserFromLDAP['sn'][0]) || $ldapUserFromLDAP['sn'][0]=='' ) ) { $this->mydebugmsg("Utilisateur LDAP mal formé => je ne le stocke pas"); continue; } $currentLdapUserLogin = $ldapUserFromLDAP[$this->authenticationType][0]; $currentLdapUserPwd = $ldapUserFromLDAP['userpassword'][0]; //$this->mydebugmsg("current user is ".$currentLdapUserLogin); // Do not save fake ldap user in DB if ($currentLdapUserLogin == '_fake_ldap_user_') continue; $ldapUserFromDB = $this->_getUser($currentLdapUserLogin); // New user => add it to DB if (is_null($ldapUserFromDB)) { //$this->mydebugmsg("user does not exist => add it to DB"); // Ajout du nouvel utilisateur $this->usersTable->save($this->_getLDAPuserFormattedAsDB($ldapUserFromLDAP)); //$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) $ldapUserFromDB = $this->_getUser($currentLdapUserLogin); assert(!is_null($ldapUserFromDB)); //$this->mydebugmsg("UPDATE users SET password = '".$currentLdapUserPwd."' WHERE id = ".$ldapUserFromDB->id); //$this->usersTable->query("UPDATE users SET password = '".$ldapUserFromLDAP['userpassword'][0]."' WHERE id = ".$ldapUserFromDB->id); $this->_updateUserPassword($ldapUserFromDB->id, $currentLdapUserPwd); */ /* $query = $this->usersTable->query(); $query->update() ->set([ 'password' => $ldapUserFromLDAP['userpassword'][0] ]) ->where(['id' => $ldapUserFromDB->id]) ->execute(); */ //exit; } // Existing user => update it if changed (only email and password) else { //TODO: a virer, only for test $BOURRIN=TRUE; //$this->mydebugmsg("user already exists => update it"); // 1) Update email (only if changed) if ( $BOURRIN || ($ldapUserFromDB->email != $ldapUserFromLDAP['mail'][0]) ) { //$this->mydebugmsg("email diff ? "); //$this->mydebugmsg($ldapUserFromDB->email !== $ldapUserFromLDAP['mail'][0]); $ldapUserFromDB->email = $ldapUserFromLDAP['mail'][0]; $this->usersTable->save($ldapUserFromDB); } // 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 = ".$ldapUserFromDB->password); //debug("userpassword = ".$ldapUserFromLDAP['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 : $ldapUserFromDB->password = $ldapUserFromLDAP['userpassword'][0]; $this->usersTable->save($ldapUserFromDB); STAR/ // Par contre, ceci fonctionne : //debug("UPDATE users SET password = '".$ldapUserFromLDAP['userpassword'][0]."' WHERE id = ".$ldapUserFromDB->id); if ( $BOURRIN || ($ldapUserFromDB->password != $currentLdapUserPwd) ) { //$this->mydebugmsg("pass diff ? "); //$this->mydebugmsg($ldapUserFromDB->password !== $ldapUserFromLDAP['userpassword'][0]); //$this->usersTable->query("UPDATE users SET password = '".$ldapUserFromLDAP['userpassword'][0]."' WHERE id = ".$ldapUserFromDB->id); $this->_updateUserPassword($ldapUserFromDB->id, $currentLdapUserPwd); } */ } } // Delete old users (which are no more in LDAP) foreach ($ldapUsersFromDB as $ldapUserFromDB) { /* debug("DELETE"); debug("look for $ldapUserFromDB->username"); debug("in "); */ //debug($ldapUsersFromLDAP); //$ldapUsersLoginFromLDAP = array_column($ldapUsersFromLDAP, 'uid'); $ldapUsersLoginFromLDAP = array_column($ldapUsersFromLDAP, $this->authenticationType); $ldapUsersLoginFromLDAP = array_column($ldapUsersLoginFromLDAP, '0'); //debug($ldapUsersLoginFromLDAP); if (! in_array($ldapUserFromDB->username, $ldapUsersLoginFromLDAP)) { $this->mydebugmsg("OLD user should be deleted:"); $this->mydebugmsg($ldapUserFromDB->username); //$ldapUserFromDB->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); } // 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->_updateLdapCacheIfNeeded(); // 1) Search user in DB $ldapUser = $this->_getUser($user_login); /* $ldapUser = TableRegistry::getTableLocator()->get('Users')->find() ->where([ 'username' => $user_login ]) ->first(); */ // User not found => fail if (is_null($ldapUser)) return FALSE; /* // 2) Check password // Bad password => fail if ( ! (new DefaultPasswordHasher())->check($user_password,$ldapUser['userpassword'][0]) ) return FALSE; // User found and password ok => return it */ // User found ok => return it formatted as ldap return $this->_getDBuserFormattedAsLDAP($ldapUser); } // TODO: implement // REAL LDAP only // from 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 searchLdap($filter, $just_these, $user_login=NULL, $user_password=NULL) { // (1) CONNEXION $ldapConnection = ldap_connect($this->host, $this->port) or die("Could not connect to $this->host (port $this->port)"); if ($ldapConnection) { // (2) SET OPTIONS ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); // (3) BINDING OPTIONNEL (true by default if not done) $ldapbind = TRUE; // - Authentified LDAP // (EP) ATTENTION: Ne pas faire die() ici car ça stopperait net la mauvaise connexion d'un utilisateur, avec ce message d'erreur ! // Il vaut mieux retourner FALSE et afficher un joli message de refus sur la page d'accueil if ($this->ldap_authentified) //$ldapbind = @ldap_bind($ldapConnection, $this->bindDn, $this->bindPass); $ldapbind = @ldap_bind($ldapConnection, $this->bindDn, $this->bindPass); //or die("Could not bind to LDAP server.". ldap_error($ldapConnection) ); // - Anonymous LDAP // (EP) En cas de LDAP anonyme, binding uniquement si login session (pour vérifier le mot de passe de l'utilisateur). // Car sans cette ligne, on passe avec n'importe quel password !!! // NB: pas de die() ici, voir remarque juste au-dessus else if ($user_login && $user_password) //$ldapbind = @ldap_bind($ldapConnection, $this->authenticationType . '=' . $user_login . ',' . $this->baseDn, $user_password); $ldapbind = @ldap_bind($ldapConnection, $this->authenticationType . '=' . $user_login . ',' . $this->baseDn, $user_password); // or die("Could not bind to LDAP server: ". ldap_error($ldapConnection) ); // (4) SEARCH if ($ldapbind) { // $filter = "(&".$this->filter."(".$this->authenticationType . '=' . $user_login."))"; // ex: (&(compteinfo=Oui)(uid=epallier)) $results = ldap_search($ldapConnection, $this->baseDn, $filter, $just_these) or die("Could not search to LDAP server response was: " . ldap_error($ldapConnection) ); $search = ldap_get_entries($ldapConnection, $results); //echo $results["count"]." entries returned\n"; if ($search === FALSE) die("Could not get user attributes from LDAP server, response was: " . ldap_error($ldapConnection) ); //return $search[0]; return $search; } } // Il y a eu un pb, utilisateur non reconnu return FALSE; } // searchLdap() public function ldapAuthenticationOLD($user_login, $user_password) { try { if ($this->checkConfiguration()) { // REAL LDAP if ($this->LDAP_USED) { // 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); // 2) If not CACHED, search user in LDAP $user_fetched = FALSE; 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->searchLdap($filter, $just_these, $user_login, $user_password); // CACHE the new user in DB for next time if ($user_fetched != FALSE) { //$this->saveNewUserInDB($user_fetched[0]); return $user_fetched[0]; } } else { debug("user found in DB"); debug($user_fetched); } //return $user_fetched; // Noter que $user_fetched peut etre egal a FALSE (si pas trouvé) } // FAKE LDAP else { //debug($this->USE_LDAP); //debug($this->baseDn); $user = $this->getFakeLdapUser($user_login); // debug($user); //if ($user === false) return FALSE; if ($user !== 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; //if ($user['uid'][0] == $this->getTheFakeLdapUser()['login'] && $user['userpassword'][0] == $this->getTheFakeLdapUser()['pass']) if ($user[$this->authenticationType][0] == $this->getTheFakeLdapUser()['login'] && $user['userpassword'][0] == $this->getTheFakeLdapUser()['pass']) return $user; if ( (new DefaultPasswordHasher())->check($user_password,$user['userpassword'][0]) ) return $user; // if ($user != false && $user['userpassword'][0] == $password) { } } } } catch (Exception $e) { //echo 'Exception LDAP : ', $e->getMessage(), "\n"; } // Il y a eu un problème, l'utilisateur n'est pas reconnu return FALSE; } // end ldapAuthentication() // 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->checkConfiguration()) return 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) // LDAP optimized //$this->CONF->ldap_cached = FALSE; if ($this->CONF->ldap_cached) { $ldap_user = $this->_getLdapUserFromDB($user_login); // login FAIL because user not found if ($ldap_user === FALSE) return FALSE; /STAR debug("user_password = ".$user_password); debug("user found in db is "); debug($ldap_user); debug($ldap_user['userpassword'][0]); STAR/ // check password and return user if ok or FALSE if fail // check($user_password EN CLAIR, $ldap_user['userpassword'][0]) CRYPTED) { if ( (new DefaultPasswordHasher())->check($user_password,$ldap_user['userpassword'][0]) ) { //debug("YES"); return $ldap_user; } } else { */ // normal LDAP (no optimization) try { //if ($this->checkConfiguration()) { // REAL LDAP if ($this->LDAP_USED) { // 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->searchLdap($filter, $just_these, $user_login, $user_password); //$this->mydebugmsg("(1) user found in LDAP is:"); //$this->mydebugmsg($user_fetched); //$this->mydebugmsg($user_fetched[0]); // CACHE the new user in DB for next time if ($user_fetched !== FALSE) { //$this->saveNewUserInDB($user_fetched[0]); return $user_fetched[0]; } /* } // user from LDAP // user from LDAP-cache (DB) else { $this->mydebugmsg("(2) user found in DB is:"); $this->mydebugmsg($user_fetched); } */ //return $user_fetched; // Noter que $user_fetched peut etre egal a FALSE (si pas trouvé) } // FAKE LDAP used else { //debug($this->USE_LDAP); //debug($this->baseDn); $user_fetched = $this->getFakeLdapUser($user_login); /* 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; if ($user_fetched[$this->authenticationType][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]); exit; */ if ( (new DefaultPasswordHasher())->check($user_password,$user_fetched['userpassword'][0]) ) return $user_fetched; // if ($user != false && $user['userpassword'][0] == $password) { } } //} // check config } catch (Exception $e) { //echo 'Exception LDAP : ', $e->getMessage(), "\n"; } //} // if LDAP_CACHED // Il y a eu un problème, l'utilisateur n'est pas reconnu return FALSE; } // end ldapAuthentication() } /* 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' ] */ ?>