Rev 2157 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
<?php// declare(encoding='UTF-8');/*** Contient les méthodes permettant d'identifier l'utilisateur de l'application DEL.** @category DEL* @package Services* @package Bibliotheque* @version 0.1* @author Mathias CHOUET <mathias@tela-botanica.org>* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>* @author Aurelien PERONNET <aurelien@tela-botanica.org>* @license GPL v3 <http://www.gnu.org/licenses/gpl.txt>* @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>* @copyright 1999-2014 Tela Botanica <accueil@tela-botanica.org>*/class GestionUtilisateur {protected $conteneur;protected $contexte;protected $bdd;/** contient le jeton SSO décodé, si une authentification a eu lieu avec succès */protected $jetonDecode;protected $utilisateur = array();public function __construct(Conteneur $conteneur) {$this->conteneur = $conteneur;$this->bdd = $this->conteneur->getBdd();$this->contexte = $this->conteneur->getContexte();$this->chargerUtilisateur();}/*** Charge des données utilisateur : un vrai profil si l'utilisateur est identifié,* un profil "anonyme" sinon*/private function chargerUtilisateur() {$this->demarrerSession();$infos = $this->getUtilisateurIdentifie();$this->utilisateur = ($infos == null) ? $this->getUtilisateurAnonyme() : $infos;}private function demarrerSession() {if (session_id() == '') {// TODO : modifier ce test lors du passage en php 5.4// TODO : expliquer pourquoi SVP :)session_start();}}// rétrocompat avec un vieux machinpublic function getIdAnonymeTemporaire() {return $this->utilisateur['session_id'];}/*** Retourne l'utilisateur en cours, chargé dans le constructeur* par getutilisateurIdentifié()*/public function getUtilisateur() {return $this->utilisateur;}/*** Recherche un jeton SSO dans l'entête HTTP "Authorization", vérifie ce* jeton auprès de l'annuaire et en cas de succès charge les informations* de l'utilisateur associé; si c'est la première fois que l'utilisateur* utilise DeL, crée un profil local dans del_utilisateur_infos; si le* profil a changé depuis la dernière connexion, le met à jour ainsi que* les coordonnées dans les commentaires** @return Array un profil utilisateur ou null*/public function getUtilisateurIdentifie() {$utilisateur = null;// lecture du jeton$jeton = $this->lireJetonEntete();if ($jeton != null) {// validation par l'annuaire$valide = $this->verifierJeton($jeton);if ($valide === true) {// décodage du courriel utilisateur depuis le jeton$this->jetonDecode = $this->decoderJeton($jeton);//var_dump($this->jetonDecode);if ($this->jetonDecode != null && $this->jetonDecode["sub"] != "") {// récupération de l'utilisateur$courriel = $this->jetonDecode["sub"];$utilisateur = $this->recupererUtilisateurEnBdd($courriel);// Si l'utilisateur existeif ($utilisateur != null) {// profil changé ?if ($this->profilAChange($utilisateur)) {// mettre à jour les coordonnées dans le profil local$this->mettreAJourProfilLocal();// mettre à jour auteur commentaires$this->mettreAJourCoordonneesDansCommentaires();// relire infos$utilisateur = $this->recupererUtilisateurEnBdd($courriel);}} else {// première connexion à DeL// initialiser infos$this->initialiserInfosUtilisateur($this->jetonDecode['id']); // rétrocompat; le paramètre devrait être implicite// relire infos$utilisateur = $this->recupererUtilisateurEnBdd($courriel);}$utilisateur = $this->completerInfosUtilisateur($utilisateur);}}}return $utilisateur;}/*** Retourne true si le profil local stocké dans del_utilisateur_infos* n'est plus à jour par rapport aux informations du jeton SSO; si le* jeton est vide, retourne false pour éviter de tout casser*/protected function profilAChange($infosDUI) {$aChange = false;if ($this->jetonDecode != null) {$aChange = ($this->jetonDecode['nom'] != $infosDUI['nom'])|| ($this->jetonDecode['intitule'] != $infosDUI['intitule'])|| ($this->jetonDecode['prenom'] != $infosDUI['prenom']);}//var_dump($aChange);return $aChange;}/*** Met à jour del_utilisateur_infos en fonction des informations* contenues par le jeton SSO; si ce dernier est vide, ne fait* rien (boulette-proof)*/protected function mettreAJourProfilLocal() {//echo "Mise à jour profil local !!";if ($this->jetonDecode != null && $this->jetonDecode['id'] != '') {$requete = 'UPDATE del_utilisateur_infos SET'. ' nom = ' . $this->bdd->proteger($this->jetonDecode['nom']) . ', '. ' intitule = ' . $this->bdd->proteger($this->jetonDecode['intitule']) . ', '. ' prenom = ' . $this->bdd->proteger($this->jetonDecode['prenom']). ' WHERE id_utilisateur = ' . $this->bdd->proteger($this->jetonDecode['id']). ' -- '.__FILE__.':'.__LINE__;//var_dump($requete);$this->bdd->executer($requete);}}/*** Répercute le nom et le prénom contenus dans le jeton SSO (si au* moins un des deux n'est pas vide) dans tous les commentaires de* l'auteur; si le jeton SSO est vide, ne fait rien (boulette-proof)** @TODO gérer l'intitulé un jour*/protected function mettreAJourCoordonneesDansCommentaires() {//echo "Mise à jour obs et images !!";if ($this->jetonDecode != null && $this->jetonDecode['id'] != '' && ($this->jetonDecode['nom'] != '' || $this->jetonDecode['prenom'] != '')) {$requete = 'UPDATE del_commentaire SET'. ' utilisateur_nom = ' . $this->bdd->proteger($this->jetonDecode['nom']) . ', '. ' utilisateur_prenom = ' . $this->bdd->proteger($this->jetonDecode['prenom']). ' WHERE ce_utilisateur = ' . $this->bdd->proteger($this->jetonDecode['id']) // s'assurer qu'il y a des ' autour de l'ID sans quoi les hash MD5 matcheront !. ' -- '.__FILE__.':'.__LINE__;//var_dump($requete);$this->bdd->executer($requete);}}/*** Essaye de trouver un jeton JWT non vide dans l'entête HTTP "Authorization"** @return String un jeton JWT ou null*/protected function lireJetonEntete() {$jwt = null;$headers = apache_request_headers();if (isset($headers["Authorization"]) && ($headers["Authorization"] != "")) {$jwt = $headers["Authorization"];}return $jwt;}/*** Vérifie un jeton auprès de l'annuaire** @param String $jeton un jeton JWT* @return true si le jeton est vérifié, false sinon*/protected function verifierJeton($jeton) {$urlServiceVerification = $this->conteneur->getParametre("urlServiceBaseAuth") . "verifierjeton";$urlServiceVerification .= "?token=" . $jeton;// file_get_contents râle si le certificat HTTPS est auto-signé//$retour = file_get_contents($urlServiceVerification);// curl avec les options suivantes ignore le pb de certificat (pour tester en local)$ch = curl_init();$timeout = 5;curl_setopt($ch, CURLOPT_URL, $urlServiceVerification);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);// équivalent de "-k"curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);$data = curl_exec($ch);curl_close($ch);$retour = $data;$retour = json_decode($retour, true);return ($retour === true);}/*** Décode un jeton JWT (SSO) précédemment validé et retourne les infos* qu'il contient (payload / claims)* @param String $jeton un jeton JWT précédemment validé*/protected function decoderJeton($jeton) {$parts = explode('.', $jeton);$payload = $parts[1];$payload = $this->urlsafeB64Decode($payload);$payload = json_decode($payload, true);return $payload;}/*** Decode a string with URL-safe Base64.* copié depuis firebase/jwt** @param string $input A Base64 encoded string* @return string A decoded string*/protected function urlsafeB64Decode($input) {$remainder = strlen($input) % 4;if ($remainder) {$padlen = 4 - $remainder;$input .= str_repeat('=', $padlen);}return base64_decode(strtr($input, '-_', '+/'));}/*** Retourne un profil d'utilisateur non connecté (anonyme), avec un identifiant de session* PHP qui permet de suivre son activité*/public function getUtilisateurAnonyme() {return array('connecte' => false,'id_utilisateur' => session_id(),'courriel' => '','mot_de_passe' => '','nom' => '','prenom' => '','admin' => '0','session_id' => session_id());}/*** Récupère les données d'un utilisateur dans la BDD depuis son login; considère que* l'utilisateur est légitime car il fournit un jeton SSO vérifié par l'annuaire** @param String $login le courriel de l'utilisateur* @return array les infos de l'utilisateur*/private function recupererUtilisateurEnBdd($login) {$loginP = $this->bdd->proteger($login);$requete = 'SELECT id_utilisateur, nom, prenom, intitule, courriel, admin'. " FROM del_utilisateur_infos". " WHERE courriel = $loginP". ' -- ' . __FILE__ . ' : ' . __LINE__;return $this->bdd->recuperer($requete);}protected function completerInfosUtilisateur($utilisateur) {$utilisateur['session_id'] = session_id();$utilisateur['connecte'] = true;return $utilisateur;}// @WARNING décompte des derniers événements désactivé pour raisons de perfs// Mathias - 2016-10-05 @TODO faire mieuxprotected function getEvenements($id_utilisateur) {$sql = $this->conteneur->getSql();$date = $this->getDerniereDateConsultationEvenements($id_utilisateur);//$requete_activite = $sql->getRequeteNbEvenementsDepuisDate($id_utilisateur, $date);//$resultats = $this->bdd->recupererTous($requete_activite);$evenements = array();//$nb_evenements = $resultats[0]['nb_evenements'];//$evenements['nb_evenements'] = $nb_evenements;$evenements['date_derniere_consultation_evenements'] = $date;return $evenements;}public function getDerniereDateConsultationEvenements($id_utilisateur) {$requete = "SELECT date_derniere_consultation_evenements FROM del_utilisateur_infos "."WHERE id_utilisateur = ".$this->bdd->proteger($id_utilisateur);$date = $this->bdd->recuperer($requete);$date = !empty($date['date_derniere_consultation_evenements']) ? $date['date_derniere_consultation_evenements'] : "0";return $date;}public function setDerniereDateConsultationEvenements($id_utilisateur, $date) {// Vérification que la ligne correspondant à l'utilisateur dans la table// infos existe bien (sinon on la crée)$infos_utilisateur = $this->obtenirInfosUtilisateur($id_utilisateur);if (empty($infos_utilisateur)) {$this->initialiserInfosUtilisateur($id_utilisateur);}$requete = "UPDATE del_utilisateur_infos SET date_derniere_consultation_evenements = ".$this->bdd->proteger($date)." "."WHERE id_utilisateur = ".$this->bdd->proteger($id_utilisateur);$this->bdd->executer($requete);}protected function ajouterEvenements(&$utilisateur) {$evenements = $this->getEvenements($utilisateur['id_utilisateur']);$utilisateur = array_merge($utilisateur, $evenements);}public function obtenirPreferencesUtilisateur($id_utilisateur) {$prefs_utilisateur = $this->obtenirInfosUtilisateur($id_utilisateur);if (empty($prefs_utilisateur)) {$this->initialiserInfosUtilisateur($id_utilisateur);$prefs_utilisateur = $this->renvoyerInfosUtilisateurDefaut($id_utilisateur);} else {if (empty($prefs_utilisateur['preferences'])) {$prefs_utilisateur['preferences'] = $this->obtenirTableauPreferenceDefaut();} else {$prefs_utilisateur['preferences'] = json_decode($prefs_utilisateur['preferences']);}$prefs_utilisateur['admin'] = $prefs_utilisateur['admin'];}return $prefs_utilisateur;}private function obtenirTableauPreferenceDefaut() {return array('mail_notification_mes_obs' => '1', 'mail_notification_toutes_obs' => '0');}private function renvoyerInfosUtilisateurDefaut($id_utilisateur) {return array('id_utilisateur' => $id_utilisateur,'admin' => '0','preferences' => $this->obtenirTableauPreferenceDefaut(),'date_premiere_utilisation' => date('Y-m-d H:i:s'),'date_derniere_consultation_evenements' => '0000-00-00 00:00:00');}public function obtenirInfosUtilisateur($id_utilisateur) {$requete = 'SELECT * '.'FROM del_utilisateur_infos '.'WHERE id_utilisateur = '.$this->bdd->proteger($id_utilisateur).' '.' -- '.__FILE__.' : '.__LINE__;$prefs_utilisateur = $this->bdd->recuperer($requete);return $prefs_utilisateur;}/*** Ajoute un utilisateur à la table des profils locaux del_utilisateur_infos;* suppose que l'utilisateur est correctement identifié (jeton décodé disponible)*/public function initialiserInfosUtilisateur($id_utilisateur) {//var_dump("Initialisation infos utilisateur !!");$preferences_defaut = $this->obtenirTableauPreferenceDefaut();$prefsEncodeesP = $this->bdd->proteger(json_encode($preferences_defaut));$idUtilisateurP = $this->bdd->proteger($id_utilisateur);$nomUtilisateurP = $this->bdd->proteger($this->jetonDecode['nom']);$prenomUtilisateurP = $this->bdd->proteger($this->jetonDecode['prenom']);$courrielUtilisateurP = $this->bdd->proteger($this->jetonDecode['sub']);$intituleUtilisateurP = $this->bdd->proteger($this->jetonDecode['intitule']);$requete = 'INSERT INTO del_utilisateur_infos '.'(id_utilisateur, intitule, prenom, nom, courriel, admin, preferences, date_premiere_utilisation, date_derniere_consultation_evenements )'."VALUES ($idUtilisateurP, $intituleUtilisateurP, $prenomUtilisateurP, $nomUtilisateurP, $courrielUtilisateurP, 0, $prefsEncodeesP, NOW(), NOW()) ".'ON DUPLICATE KEY UPDATE date_premiere_utilisation = NOW() '.' -- '.__FILE__.' : '.__LINE__;return $this->bdd->executer($requete);}/*** Vérifie qu'un utilisateur connu est identifié (mode non anonyme) et* que son identifiant numérique est égal à $id_utilisateur; si non,* retourne une erreur HTTP 401 et quitte le programme** @param integer $id_utilisateur l'utilisateur attendu*/public function controleUtilisateurIdentifie($id_utilisateur) {$ok = ($this->utilisateur['connecte'] === true && $this->utilisateur['id_utilisateur'] == $id_utilisateur);if (! $ok) {$message = "";if ($this->utilisateur['connecte'] === true) {$message = "Vous n'êtes pas propriétaire de la ressource demandée";} else {$message = "Vous n'êtes pas identifié";}$code = RestServeur::HTTP_CODE_ACCES_NON_AUTORISE;throw new Exception($message, $code);}}// rétrocompat'public function etreAdmin() {return ($this->utilisateur['admin'] >= 1);}// rétrocompat'public function etreUtilisateurAvecDroitAdmin() {$etreAdmin = $this->etreAdmin();if (! $etreAdmin) {$message = "Vous ne pouvez pas accéder à ce service car vous n'avez pas les droits d'administrateur !\n";$code = RestServeur::HTTP_CODE_ACCES_NON_AUTORISE;throw new Exception($message, $code);}return $etreAdmin;}}/*** Compatibilité avec nginx - merci http://php.net/manual/fr/function.getallheaders.php*/if (! function_exists('apache_request_headers')) {function apache_request_headers() {$headers = '';foreach ($_SERVER as $name => $value) {if (substr($name, 0, 5) == 'HTTP_') {$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;}}return $headers;}}