Subversion Repositories eFlore/Applications.del

Rev

Rev 2159 | 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 machin
        public 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 existe
                                        if ($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 mieux
        protected 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(), courriel = VALUES(courriel) '.
                                ' -- '.__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;
        }
}