Rev 1934 | Go to most recent revision | 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);
$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, $mot_de_passe_sha1, $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, $mot_de_passe_sha1);
// 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, $mot_de_passe_sha1, $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_mot_de_passe = '$mot_de_passe_sha1', cp_ce_annuaire = '{$infos['id']}' "
. " WHERE cp_login = '$login' "
//. " AND cp_mot_de_passe = '{$infos['mot_de_passe']}' " // WTF ?
;
// 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, $mot_de_passe_sha1) {
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
$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_mot_de_passe, 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']}', '".$mot_de_passe_sha1."', ".
"'{$infos['id']}', $cp_mark_licence) ";
//$this->debug[] = $requete;
// Ajout des données
$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
*/
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);
$tableau_annuaire = null;
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($payload);
$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;
}
}
?>