Subversion Repositories eFlore/Applications.coel

Rev

Rev 1952 | Blame | Compare with Previous | Last modification | View Log | RSS feed

<?php
/**
 * Service d'itentification d'un utilisateur.
 * Modifié pour fonctionner avec le SSO.
 * 
 * Encodage en entrée : utf8
 * Encodage en sortie : utf8
 * 
 * Cas d'utilisation :
 * Le service reçoit un jeton SSO (ou pas) dans le header "Authorization", le fait
 * vérifier par l'annuaire; s'il est valide, le décode puis retourne le profil utilisateur
 * associé; sinon retourne un profil anonyme.
 * 
 * 1: Aucun jeton ou jeton invalide transmis
 *      1: L'application retourne l'identifiant de session en cours (mode anonyme)
 * 
 * 2: Jeton valide transmis
 *      1 : Passe ou reste dans l'état "connecté"; le profil actif est retourné
 * 
 *  En résumé, utilisation des URLs :
 *  /CoelUtilisateur/identite : retour identifiant de session si jamais connecté, sinon retour de l'id (+ login et mot de passe)
 *
 * @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
 * @author David DELON <david.delon@clapas.net>
 * @author Mathias CHOUET <mathias@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>
 * @version $Id$
 * @copyright 2009
 */
class CoelUtilisateur extends Coel {

        protected $licence_acceptee = false;

        /**
         * Point d'entrée GET sans paramètres
         */
        public function getRessource() {
                // par défaut, mode anonyme
                $infos = $this->getUtilisateurAnonyme();
                // recherche d'un utilisateur identifié en fonction du jeton SSO
                $infosIdentifie = $this->getUtilisateurSSO();
                if ($infosIdentifie != null) {
                        $infos = $infosIdentifie;
                }
                // et vlan, passe-moi l'éponge
                $this->envoyer($infos);
        }

        /**
         * Point d'entrée GET avec paramètres
         */
        public function getElement($ressources) {
                switch($ressources[0]) {
                        case "setLicence" :
                                $this->setLicence($ressources[1]);
                                break;
                        default:
                                echo "action [" . $ressources[0] . "] inconnue";
                }
        }

        /**
         * Renvoie un profil utilisateur CoeL anonyme
         * (licence vide, id de session, non connecté)
         */
        protected function getUtilisateurAnonyme() {
                $info = array("", session_id(), false);
                return $info;
        }

        /**
         * 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 besoin, copie l'utilisateur dans la table
         * Personnes de CoeL
         *
         * @return Array un profil utilisateur ou null
         */
        protected function getUtilisateurSSO() {
                $utilisateur = null;
                // lecture du jeton
                $jeton = $this->lireJetonEntete();
                //echo "Jeton : "; var_dump($jeton);
                if ($jeton != null) {
                        // validation par l'annuaire
                        $valide = $this->verifierJeton($jeton);
                        if ($valide === true) {
                                // décodage du courriel utilisateur depuis le jeton
                                $donneesJeton = $this->decoderJeton($jeton);
                                if ($donneesJeton != null && $donneesJeton["sub"] != "") {
                                        // récupération de l'utilisateur
                                        $courriel = $donneesJeton["sub"];

                                        // lecture des infos dans l'annuaire (relou mais nécessaire pour la copie dans la table Personnes de CoeL)
                                        $infosAnnuaire = $this->obtenirInfosAnnuaire($courriel);
                                        $infosAnnuaire['courriel'] = $courriel;
                                        $this->setInfosAnnuaire($infosAnnuaire);
                                        //echo "Infos Annu: "; var_dump($infosAnnuaire);

                                        // lecture de l'utilisateur connu (ou pas) par CoeL (table Personnes)
                                        $utilisateur_existant = $this->chargerUtilisateur($courriel);
                                        //echo "UTIL EXIST: "; var_dump($utilisateur_existant);
                                        
                                        // Vérification de la nécessité de mettre à jour l'utilisateur COEL vis à vis de l'annuaire de Tela Botanica
                                        if (!is_null($infosAnnuaire) &&  $this->avoirBesoinMiseAJour($utilisateur_existant)) {
                                                $this->debug[] = "Besoin d'une mise à jour";

                                                $presence_dans_coel = ($utilisateur_existant != false); // si on l'a trouvé juste au dessus
                                                $this->debug[] = "Presence: $presence_dans_coel";
                                                //$mot_de_passe_sha1 = $infosAnnuaire['pass'];
                                                //echo "MDP: " .$mot_de_passe_sha1;

                                                if ($presence_dans_coel) {
                                                        // Nécessite de faire une mise à jour
                                                        $this->debug[] = "Mise à jour de l'utilisateur {$infosAnnuaire['id']}";
                                                        $this->mettreAJourUtilisateur($courriel, $infosAnnuaire);
                                                        // ci-dessous : ne pas caster en int car GWT attend une String (le con !)
                                                        $utilisateur_existant['licence'] = $this->recupererLicenceUtilisateur($infosAnnuaire['id']);
                                                } else {
                                                        // Nécessite d'ajouter le nouvel utilisateur
                                                        $this->debug[] = "Ajout d'une nouvel utilisateur";
                                                        $this->ajouterUtilisateurACoel($infosAnnuaire);
                                                        // rechargement après l'avoir ajouté (un peu nul)
                                                        $utilisateur_existant = $this->chargerUtilisateur($courriel);
                                                }
                                        }

                                        // stockage de l'utilisateur en session
                                        $this->setUtilisateur($utilisateur_existant);

                                        // renvoi des infos
                                        $utilisateur = array($utilisateur_existant['licence'], $infosAnnuaire['id'], true, $this->getUtilisateurNomComplet(), $this->getUtilisateurPrenom(), $this->getUtilisateurNom(), $this->getParametre());
                                }
                        }
                }

                return $utilisateur;
        }

        protected function deconnecterUtilisateur() {
                $_SESSION['coel_utilisateur'] = '';
                $_SESSION['coel_infosAnnuaire'] = '';
        }

        /**
         * Retourne true si le compte utilisateur a été modifié dans l'annuaire, et que les
         * modifications ont besoin d'être répercutées dans la table Personnes de Coel
         */
        protected function avoirBesoinMiseAJour($info_annuaire_coel) {
                $necessite_maj = false;
                if ($info_annuaire_coel == false) {
                        // Le login et/ou le mot de passe a pu changer
                        $necessite_maj = true;
                } else {
                        $info_annuaire_distant = $this->getInfosAnnuaire();
                        if ($this->comparerInfosAnnuairesDistantEtCoel($info_annuaire_distant, $info_annuaire_coel) == false) {
                                $necessite_maj = true;
                        }
                }
                return $necessite_maj;
        }
        
        protected function comparerInfosAnnuairesDistantEtCoel($annuaire_distant, $annuaire_coel) {
                $identique = true;
                $tableau_annuaire_distant = array('nom' => $annuaire_distant['nom'], 
                        'prenom' => $annuaire_distant['prenom'], 
                        'ville' => $annuaire_distant['ville'], 
                        'code_postal' => $annuaire_distant['code_postal']);
                $tableau_annuaire_coel = array('nom' => $annuaire_coel['nom'], 
                        'prenom' => $annuaire_coel['prenom'], 
                        'ville' => $annuaire_coel['ville'], 
                        'code_postal' => $annuaire_coel['code_postal']);
                foreach ($tableau_annuaire_distant as $cle => $valeur) {
                        if ($tableau_annuaire_coel[$cle] != $valeur) {
                                $identique = false;
                                break;
                        }
                }
                return $identique;
        }

        /**
         * Vérifie si un utilisateur est présent dans la table Personnes de CoeL
         */
        protected function verifierPresenceUtilisateur($id) {
                $present = false;
                $requete =      'SELECT COUNT(cp_id_personne) AS nbre '.
                                        'FROM coel_personne '.
                                        "WHERE cp_ce_annuaire = {$this->bdd->quote($id)} ".
                                        "       AND cp_ce_annuaire = cp_id_personne ";
                try {
                        $nbre = $this->bdd->query($requete)->fetchColumn();
                        if (0 == $nbre) {
                                $this->debug[] = "Utilisateur NON présent dans l'annuaire de COEL.";
                        } else if (1 == $nbre) {
                                $this->debug[] = "Utilisateur présent dans l'annuaire de COEL.";
                                $present = true;
                        } else if (false === $nbre) {
                                $this->debug[] = "Erreur dans la requête de vérification de présence dans l'annuaire de COEL.";
                        }
                } catch (PDOException $e) {
                        $this->messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage());
                }
                
                return $present;
        }

        /**
         * Renvoie l'état d'acceptation de la licence pour un utilisateur donné :
         * 0 (non acceptée) ou 1 (acceptée)
         */
        protected function recupererLicenceUtilisateur($id) {
                $requete =      'SELECT cp_mark_licence '.
                                        'FROM coel_personne '.
                                        "WHERE cp_ce_annuaire = {$this->bdd->quote($id)} ".
                                        "       AND cp_ce_annuaire = cp_id_personne ";
                try {
                        $licence = $this->bdd->query($requete)->fetchColumn();
                        if ($licence === false) {
                                $this->debug[] = "La licence n'a pas pu être récupérée.";
                                return 0;
                        } else {
                                return $licence;
                        }
                } catch (PDOException $e) {
                        $this->messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage());
                }
        }

        /**
         * Met à jour les données de l'utilisateur dans la table CoeL Personnes
         */
        protected function mettreAJourUtilisateur($login, $infos) {
                try {
                        $cp_fmt_nom_complet = $infos['prenom'].' '.$infos['nom'];
                        $requete =  ''
                                . 'UPDATE coel_personne'
                                . " SET cp_id_personne = '{$infos['id']}', "
                                . "     cp_fmt_nom_complet = '$cp_fmt_nom_complet', cp_prenom = '{$infos['prenom']}', cp_nom = '{$infos['nom']}', "
                                . "     cp_code_postal = '{$infos['code_postal']}', cp_ville = '{$infos['ville']}', cp_truk_courriel = '{$infos['courriel']}', "
                                . "     cp_login = '{$infos['courriel']}', cp_ce_annuaire = '{$infos['id']}' "
                                . " WHERE cp_login = '$login' "
                        ;

                        // Ajout des données
                        $resultat = $this->bdd->exec($requete);
                        if ($resultat === false) {
                                $this->messages[] = "L'utilisateur n'a pas été mis à jour car la requête a échouée.";
                        }
                } catch (PDOException $e) {
                        $messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage(), $requete);
                }
        }

        /**
         * Met à jour l'utilisateur d'id $idUtilisateur, et passe son acceptation de
         * la licence à "1"
         */
        protected function setLicence($idUtilisateur = null) {
                if ($idUtilisateur == null) {
                        $messages[] = "Id utilisateur null";
                        $this->envoyer();
                        exit;
                }
                // Mise à jour de l'utilisateur
                try {
                        $requete =      'UPDATE coel_personne '.
                                "SET cp_mark_licence = 1 ".
                                "WHERE cp_ce_annuaire = '$idUtilisateur' ";
                        // Ajout des données
                        $resultat = $this->bdd->exec($requete);
                        if ($resultat === false) {
                                $this->messages[] = "L'utilisateur n'a pas été mis à jour car la requête a échouée.";
                        } else {
                                // Confirmation attendue par l'interface
                                $this->envoyer(array("1"));
                                exit;
                        }
                } catch (PDOException $e) {
                        $messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage(), $requete);
                        $this->envoyer();
                }
        }

        /**
         * Ajoute une copie de l'utilisateur dans la table CoeL Personnes
         */
        protected function ajouterUtilisateurACoel($infos) {
                try {
                        // Construction de la requête d'ajout
                        // Notes : pour rester compatibles avec l'annuaire de Tela, les utilisateurs sont ajoutés directement avec l'id
                        // de l'annuaire Tela. Dans CoelPersonne, les personnes qui ne sont pas utilisateur sont ajoutés avec un id supérieur à 100 000
                        // Notes 2017 : on ne devrait plus stocker les mots de passe dans la table coel_personne, et on ne devrait plus
                        // considérer que la base de données de l'annuaire se trouve sur le même serveur.
                        $cp_fmt_nom_complet = $infos['prenom'].' '.$infos['nom'];
                        $cp_mark_licence = '0';
                        // si un utilisateur a changé son email dans un compte TB existant, une ligne avec son ID existera déjà et /i
                        // => on fait un REPLACE (attention à la compatibilité !)
                        $requete =      'REPLACE INTO coel_personne '.
                                                '       (cp_id_personne, cp_fmt_nom_complet, cp_prenom, cp_nom, cp_code_postal, '. 
                                                '       cp_ville, cp_truk_courriel, cp_login, cp_ce_annuaire, cp_mark_licence) '.
                                                "VALUES ('{$infos['id']}', '$cp_fmt_nom_complet', '{$infos['prenom']}', '{$infos['nom']}', ". 
                                                "'{$infos['code_postal']}', '{$infos['ville']}', '{$infos['courriel']}', '{$infos['courriel']}', ". 
                                                "'{$infos['id']}', $cp_mark_licence) ";
                        //$this->debug[] = $requete;
                        // Ajout des données
                        //var_dump($requete);
                        $resultat = $this->bdd->exec($requete);
                        if ($resultat === false) {
                                $this->debug[] = "Utilisateur NON ajouté dans coel_personne car la requête a échouée.";
                        } else {
                                $this->debug[] = "Utilisateur ajouté à coel_personne.";
                        }
                } catch (PDOException $e) {
                        //$this->debug[] = "EREEUR SQL: " . sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage(), $requete);
                        $messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage(), $requete);
                }
        }

        /**
         * Appelle l'annuaire pour connaître tous les détails de l'utilisateur de courriel $login
         * @TODO gérer le pseudo un jour !
         */
        protected function obtenirInfosAnnuaire($login) {
                $url_annuaire = $this->config['coel']['urlAnnuaire'];
                $login_annuaire = $this->config['coel']['loginAnnuaire'];
                $mdp_annuaire = $this->config['coel']['mdpAnnuaire'];

                $posds = strpos($url_annuaire, "//");
                $protocole = substr($url_annuaire, 0, $posds+2);
                $reste_adresse = substr($url_annuaire, $posds+2);
                // auth HTTP basic cracra
                $url_annuaire = $protocole . $login_annuaire . ':' . $mdp_annuaire . "@" . $reste_adresse;
                $url_annuaire .= '/' . $login;
                $url_annuaire .= '/xml'; // @TODO un jour, faire en sorte que ça lise du JSON

                $resultat_annuaire = file_get_contents($url_annuaire);
                // valeurs par défaut pour éviter des warning
                $tableau_annuaire = array(
                        "nom" => "",
                        "prenom" => "",
                        "ville" => "",
                        "code_postal" => "",
                        "courriel" => ""
                );
                if ($xml_utilisateur = simplexml_load_string($resultat_annuaire))       {
                        // La fonction a retourné un objet
                        foreach ($xml_utilisateur->children() as $key => $val) {
                                if ((string) $val != '') {
                                        $tableau_annuaire[$key] = (String) $val;
                                }
                        } 
                }
                return $tableau_annuaire;
        }

        /**
         * Enregistre le fait que la personne de login $login a accepté la licence de CoeL
         */
        protected function accepterLicence($login) {
                $sortie = false;
                try {
                        $requete =      'UPDATE coel_personne '.
                                                'SET cp_mark_licence = 1 '.
                                                "WHERE cp_login = {$this->bdd->quote($login)} ";
                        $resultat = $this->bdd->exec($requete);
                        if ($resultat === false) {
                                $this->debug[] = "La table Personne n'a pas été mise à jour car la requête a échoué";
                        } else {
                                $this->debug[] = "Création du cookie licence.";
                                $_SESSION['coel_utilisateur']['licence'] = '1';
                                // @TODO CHANGER
                                //$this->setCookiePersistant('coel_licence', '1');
                                $sortie = true;
                        }
                } catch (PDOException $e) {
                        $messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage(), $requete);
                }
                return $sortie;
        }
        
        /**
         * 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->config['coel']['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 = base64_decode(str_pad(strtr($payload, '-_', '+/'), strlen($payload) % 4, '=', STR_PAD_RIGHT));
                $payload = json_decode($payload, true);
        
                return $payload;
        }

        // accesseurs à deux ronds
        protected function getUtilisateurId() {
                if ($utilisateur = $this->getUtilisateur()) {
                        return $utilisateur['id'];
                } else {
                        return '';
                }
        }
        protected function getUtilisateurLogin() {
                if ($utilisateur = $this->getUtilisateur()) {
                        return $utilisateur['login'];
                } else {
                        return '';
                }
        }
        protected function getUtilisateurNomComplet() {
                if ($utilisateur = $this->getUtilisateur()) {
                        return $utilisateur['nom_complet'];
                } else {
                        return '';
                }
        }
        protected function getUtilisateurPrenom() {
                if ($utilisateur = $this->getUtilisateur()) {
                        return $utilisateur['prenom'];
                } else {
                        return '';
                }
        }
        protected function getUtilisateurNom() {
                if ($utilisateur = $this->getUtilisateur()) {
                        return $utilisateur['nom'];
                } else {
                        return '';
                }
        }
        protected function getParametre() {
                if ($utilisateur = $this->getUtilisateur()) {
                        return $utilisateur['parametre'];
                } else {
                        return '';
                }
        }
        protected function getLicence() {
                if (!empty($_SESSION['coel_utilisateur']))      {
                        return (string) $_SESSION['coel_utilisateur']['licence'];
                } else {
                        return '';
                }
        }
        protected function getInfosAnnuaire()   {
                if (!empty($_SESSION['coel_infosAnnuaire']))    {
                        return $_SESSION['coel_infosAnnuaire'];
                } else  {
                        return '';
                }
        }
        protected function setInfosAnnuaire($infosAnnuaire)     {
                $_SESSION['coel_infosAnnuaire'] = $infosAnnuaire;
        }
}

/**
 * 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;
        }
}
?>