/trunk/jrest/jrest.ini.php.defaut |
---|
16,6 → 16,7 |
urlBaseJrest = "http://www.tela-botanica.org/eflore/coel/jrest/" |
aideBaseUrlTpl = "http://www.tela-botanica.org/wikini/eflore/wakka.php?wiki=" |
urlAnnuaire = "http://www.tela-botanica.org/service:annuaire-test:utilisateur/identite-complete-par-courriel" |
urlServiceBaseAuth = "https://www.tela-botanica.org/service-test:annuaire:auth/" |
loginAnnuaire = |
mdpAnnuaire = |
correctionLogin = |
/trunk/jrest/services/CoelUtilisateur.php |
---|
1,29 → 1,28 |
<?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 : |
* 1: Aucun login ni mot de passe transmis |
* 1: L'application retourne l'identifiant de session en cours |
* 2: Une identification est toujours active, cette identification est retournée |
* 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. |
* |
* 2: Ce service recoit un login et un mot de passe |
* 1 : On tente d'identifier l'utilisateur |
* 2 : Si réussi, l'état passe à : connecté |
* 3 : sinon, l'état passe à : pas connecté |
* 1: Aucun jeton ou jeton invalide transmis |
* 1: L'application retourne l'identifiant de session en cours (mode anonyme) |
* |
* 3: Ce service reçoit un identifiant (différent du login) et aucun mot de passe : |
* 1 : Déconnexion |
* 2: Jeton valide transmis |
* 1 : Passe ou reste dans l'état "connecté"; le profil actif est retourné |
* |
* En résumé, utilisation des URLs : |
* /CoelUtilisateur/ : retour identifiant de session si jamais connecté, sinon retour de l'id (+ login et mot de passe) |
* /CoelUtilisateur/ * / * / : idem ci-dessus |
* /CoelUtilisateur/id : déconexion |
* /CoelUtilisateur/login/mot_de_passe : connexion |
* /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>abstract |
* @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$ |
31,171 → 30,77 |
*/ |
class CoelUtilisateur extends Coel { |
private $licence_acceptee = false; |
protected $licence_acceptee = false; |
// TODO : controle systematique... dans tous les services si connected : name cookie = name service |
/** |
* Point d'entrée GET |
*/ |
public function getRessource(){ |
$this->getElement(array('*','*')); |
} |
public function getElement($param){ |
$identification = false; |
$info = array(); |
// Si la licence n'a pas été accepté nous réinitialisons tout |
if (!isset($param[2]) && (!isset($_COOKIE['coel_licence']) || (isset($_COOKIE['coel_licence']) && $_COOKIE['coel_licence'] != 1))) { |
// par défaut, mode anonyme |
$infos = $this->getUtilisateurAnonyme(); |
// recherche d'un utilisateur identifié en fonction du jeton SSO |
$infosIdentifie = $this->getUtilisateurSSO(); |
if ($infosIdentifie != null) { |
//echo "Infos SSO : "; var_dump($infosIdentifie); |
$infos = $infosIdentifie; |
// si la licence n'a pas été acceptée, au revoir les enfants |
// on envoie tout de même les infos à l'interface mais on déconnecte les services |
$this->deconnecterUtilisateur(); |
} |
// S'il y a 3 paramètres, c'est qu'on cherche à mettre à jour la licence |
if (isset($param[2])) { |
$this->debug[] = "Licence acceptee"; |
$this->licence_acceptee = true; |
// Mise à jour de la licence |
if (!$this->accepterLicence($param[0])) { |
$this->debug[] = "Erreur de mise à jour licence utilisateur"; |
// et vlan, passe-moi l'éponge |
$this->envoyer($infos); |
} |
} else if (isset($param[1])) { // Non connecté : vérification |
if (!$this->getUtilisateur()) { // Si non identifié |
$id = null; |
if ($param[0] == '*' && $param[1] == '*') { // Tentative d'identification depuis les cookies permanents |
if (isset($_COOKIE['coel_login']) && ($this->souvenirUtilisateur($_COOKIE['coel_login'], $_COOKIE['coel_mot_de_passe']))) { |
$id = $this->getUtilisateurId(); |
$identification = true; |
$info = array($id, $identification, $this->getUtilisateurNomComplet(), $this->getUtilisateurPrenom(), $this->getUtilisateurNom(), $this->getParametre()); |
} |
} else if ($this->connecterUtilisateur($param[0], $param[1], 1)) { // Tentative d'identification depuis les paramêtres passés dans l'url du service |
$id = $this->getUtilisateurId(); |
$identification = true; |
$info = array($id, $identification, $this->getUtilisateurNomComplet(), $this->getUtilisateurPrenom(), $this->getUtilisateurNom(), $this->getParametre()); |
} |
// L'identification a échouée, nous retournons un session_id comme identifiant |
if (is_null($id)) { |
$id = session_id(); |
$identification = false; |
$info = array($id, $identification); |
/** |
* Renvoie un profil utilisateur CoeL anonyme |
* (licence vide, id de session, non connecté) |
*/ |
protected function getUtilisateurAnonyme() { |
$info = array("", session_id(), false); |
return $info; |
} |
} else { // Déjà identifié |
$id = $this->getUtilisateurId(); |
$identification = true; |
$info = array($id, $identification, $this->getUtilisateurNomComplet(), $this->getUtilisateurPrenom(), $this->getUtilisateurNom(), $this->getParametre()); |
} |
} else { // Déconnexion |
$this->deconnecterUtilisateur(); |
$id = session_id(); |
$identification = false; |
$info = array($id, $identification); |
} |
array_unshift($info, $this->getLicence()); |
/** |
* 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"]; |
// Envoie sur la sortie standard |
$this->envoyer($info); |
} |
private function getUtilisateurId() { |
if ($utilisateur = $this->getUtilisateur()) { |
return $utilisateur['id']; |
} else { |
return ''; |
} |
} |
private function getUtilisateurLogin() { |
if ($utilisateur = $this->getUtilisateur()) { |
return $utilisateur['login']; |
} else { |
return ''; |
} |
} |
private function getUtilisateurNomComplet() { |
if ($utilisateur = $this->getUtilisateur()) { |
return $utilisateur['nom_complet']; |
} else { |
return ''; |
} |
} |
private function getUtilisateurPrenom() { |
if ($utilisateur = $this->getUtilisateur()) { |
return $utilisateur['prenom']; |
} else { |
return ''; |
} |
} |
private function getUtilisateurNom() { |
if ($utilisateur = $this->getUtilisateur()) { |
return $utilisateur['nom']; |
} else { |
return ''; |
} |
} |
private function getParametre() { |
if ($utilisateur = $this->getUtilisateur()) { |
return $utilisateur['parametre']; |
} else { |
return ''; |
} |
} |
private function getLicence() { |
if (!empty($_SESSION['coel_utilisateur'])) { |
return (string) $_SESSION['coel_utilisateur']['licence']; |
} else { |
return ''; |
} |
} |
private function getInfosAnnuaire() { |
if (!empty($_SESSION['coel_infosAnnuaire'])) { |
return $_SESSION['coel_infosAnnuaire']; |
} else { |
return ''; |
} |
} |
private function deconnecterUtilisateur() { |
$_SESSION['coel_utilisateur'] = ''; |
$_SESSION['coel_infosAnnuaire'] = ''; |
$this->supprimerCookie('coel_login'); |
$this->supprimerCookie('coel_mot_de_passe'); |
$this->supprimerCookie('coel_licence'); |
$this->supprimerCookie('coel_permanence'); |
} |
private function connecterUtilisateur($login, $mot_de_passe, $permanence = 1) { |
// Dans tous les cas, on récupère les informations de l'utilisateur présentes dans l'annuaire de Tela Botanica |
$infosAnnuaire = $this->obtenirInfosAnnuaire($login); |
// 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); |
if ($mot_de_passe == 'debug' && $utilisateur_existant = $this->chargerUtilisateur($login)) { |
$this->setUtilisateur($utilisateur_existant, $permanence); |
return true; |
} else { |
$mot_de_passe_sha1 = sha1($mot_de_passe); |
$mot_de_passe_md5 = md5($mot_de_passe); |
if ($utilisateur_existant = $this->chargerUtilisateur($login, $mot_de_passe_sha1)) { |
// OK, nous poursuivons |
} else if ($utilisateur_existant = $this->chargerUtilisateur($login, $mot_de_passe_md5)) { |
// Mise à jour du mot de passe md5 en sha1 |
$this->mettreAJourMotDePasse($login, $mot_de_passe_md5, $mot_de_passe_sha1); |
$utilisateur_existant['mot_de_passe'] = $mot_de_passe_sha1; |
} |
// 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 du COEL vis à vis de l'annuaire de Tela Botanica |
// 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"; |
// Vérifions que la personne s'est bien identifiée |
if ($infosAnnuaire['mot_de_passe'] == $mot_de_passe_md5) { |
$this->debug[] = "Identification correcte avec md5"; |
$utilisateur_existant = $this->getInfoAnnuaireCoelDepuisInfoAnnuaireDistant($mot_de_passe_sha1, $infosAnnuaire); |
$presence_dans_coel = $this->verifierPresenceUtilisateur($infosAnnuaire['id']); |
$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']}"; |
202,34 → 107,39 |
$this->mettreAJourUtilisateur($login, $mot_de_passe_sha1, $infosAnnuaire); |
$utilisateur_existant['licence'] = (int) $this->recupererLicenceUtilisateur($infosAnnuaire['id']); |
} else { |
echo "Insertion"; |
// 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); |
} |
$this->setUtilisateur($utilisateur_existant, $permanence); |
} else { |
$this->debug[] = "Identification INCORRECTE avec md5"; |
return false; |
$this->setUtilisateur($utilisateur_existant); |
} |
} else if ($utilisateur_existant != false) { |
// L'utilisateur est toutefois présent dans l'annuaire de COEL, est correctement identifié et n'a pas besoin de mise à jour |
$this->setUtilisateur($utilisateur_existant, $permanence); |
} else { |
// L'utilisateur n'existe ni dans l'annuaire de Tela Botanica ni dans celui de COEL |
return false; |
} |
// L'utilisateur a t il accepté la licence? Nécessaire pour être connecté! |
if ($utilisateur_existant['licence'] == 1) { |
return true; |
} else { |
return false; |
// 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()); |
} |
} |
} |
private function avoirBesoinMiseAJour($info_annuaire_coel) { |
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 |
243,7 → 153,7 |
return $necessite_maj; |
} |
private function comparerInfosAnnuairesDistantEtCoel($annuaire_distant, $annuaire_coel) { |
protected function comparerInfosAnnuairesDistantEtCoel($annuaire_distant, $annuaire_coel) { |
$identique = true; |
$tableau_annuaire_distant = array('nom' => $annuaire_distant['nom'], |
'prenom' => $annuaire_distant['prenom'], |
262,16 → 172,10 |
return $identique; |
} |
private function getInfoAnnuaireCoelDepuisInfoAnnuaireDistant($mot_de_passe_sha1, $infos) { |
$cp_fmt_nom_complet = $infos['prenom'].' '.$infos['nom']; |
$utilisateur_existant = array('id' => $infos['id'], 'login' => $infos['courriel'], |
'mot_de_passe' => $mot_de_passe_sha1, |
'nom_complet' => $cp_fmt_nom_complet, 'nom' => $infos['nom'], 'prenom' => $infos['prenom'], |
'parametre' => '', 'licence' => '0'); |
return $utilisateur_existant; |
} |
private function verifierPresenceUtilisateur($id) { |
/** |
* 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 '. |
294,7 → 198,11 |
return $present; |
} |
private function recupererLicenceUtilisateur($id) { |
/** |
* 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)} ". |
312,24 → 220,11 |
} |
} |
private function mettreAJourMotDePasse($login, $mot_de_passe_md5, $mot_de_passe_sha1) { |
/** |
* Met à jour les données de l'utilisateur dans la table CoeL Personnes |
*/ |
protected function mettreAJourUtilisateur($login, $mot_de_passe_sha1, $infos) { |
try { |
$requete = 'UPDATE coel_personne '. |
"SET cp_mot_de_passe = '$mot_de_passe_sha1' ". |
"WHERE cp_login = '$login' ". |
" AND cp_mot_de_passe = '$mot_de_passe_md5' "; |
// Ajout des données |
$resultat = $this->bdd->exec($requete); |
if ($resultat === false) { |
$this->messages[] = "Le mot de passe de 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); |
} |
} |
private 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']}', ". |
349,7 → 244,10 |
} |
} |
private function ajouterUtilisateurACoel($infos, $mot_de_passe_sha1) { |
/** |
* 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 |
375,20 → 273,10 |
} |
} |
private function souvenirUtilisateur($login, $mot_de_passe_sha1) { |
if ($login == '' && $mot_de_passe_sha1 == '') { |
return false; |
} else if ($utilisateur_existant = $this->chargerUtilisateur($login, $mot_de_passe_sha1)) { |
if ($utilisateur_existant['mot_de_passe'] == $mot_de_passe_sha1) { |
$this->setUtilisateur($utilisateur_existant, $_COOKIE['coel_permanence']); |
return true; |
} else { |
return false; |
} |
} |
} |
private function obtenirInfosAnnuaire($login) { |
/** |
* 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']; |
414,11 → 302,10 |
return $tableau_annuaire; |
} |
private function setInfosAnnuaire($infosAnnuaire) { |
$_SESSION['coel_infosAnnuaire'] = $infosAnnuaire; |
} |
private function accepterLicence($login) { |
/** |
* 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 '. |
426,10 → 313,11 |
"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ée."; |
$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; |
} |
438,5 → 326,140 |
} |
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; |
} |
} |
?> |
/trunk/jrest/services/Coel.php |
---|
552,6 → 552,10 |
return (isset($_SESSION['coel_utilisateur']) ? $_SESSION['coel_utilisateur'] : ''); |
} |
/** |
* Tente de trouver un utilisateur dans la table Personnes de CoeL, en fonction de son |
* courriel ($login); si le mot de passe est fournie, le vérifie, sinon l'ignore |
*/ |
protected function chargerUtilisateur($login, $mot_de_passe = null) { |
$sortie = false; |
$requete = 'SELECT cp_id_personne AS id, cp_fmt_nom_complet AS nom_complet, cp_prenom AS prenom, '. |
584,13 → 588,19 |
return $sortie; |
} |
/** |
* Définit un utilisateur comme "actif" en plaçant un objet Utilisateur dans la session; |
* Ne pose plus de cookies depuis l'adaptation au SSO, sauf pour la licence (acceptée ou non) |
* @param unknown $utilisateur |
* @param number $permanence |
*/ |
protected function setUtilisateur($utilisateur, $permanence = 1) { |
$_SESSION['coel_utilisateur'] = $utilisateur; |
$this->debug[] = $_SESSION['coel_utilisateur']; |
$this->setCookiePersistant('coel_login', $utilisateur['login'], $permanence); |
//$this->debug[] = $_SESSION['coel_utilisateur']; |
/*$this->setCookiePersistant('coel_login', $utilisateur['login'], $permanence); |
$this->setCookiePersistant('coel_mot_de_passe', $utilisateur['mot_de_passe'], $permanence); |
$this->setCookiePersistant('coel_permanence', $permanence, $permanence);*/ |
$this->setCookiePersistant('coel_licence', $utilisateur['licence'], $permanence); |
$this->setCookiePersistant('coel_permanence', $permanence, $permanence); |
} |
protected function setCookiePersistant($cookie_nom, $valeur, $permanence = 1) { |
664,7 → 674,7 |
} |
/* |
* Retourne une pair de coordonnée depuis un tableau représentant une adresse |
* Retourne une paire de coordonnée depuis un tableau représentant une adresse |
* ou une adresse partielle à l'aide du service Nominatim. |
* (http://wiki.openstreetmap.org/wiki/Nominatim) |
* |
/trunk/src/org/tela_botanica/client/configuration/Configuration.java |
---|
89,6 → 89,7 |
listesId.put("nbElementsPage", Integer.valueOf(configuration.get("nbElementsPage"))); |
listesId.put("relationPersonnePublication", Integer.valueOf(configuration.get("relationPersonnePublication"))); |
urls.put("base", configuration.get("serviceBaseUrl")); |
urls.put("baseAuth", configuration.get("serviceAuthBaseUrl")); |
urls.put("cel", configuration.get("celUrl")); |
urls.put("remarques", configuration.get("remarquesUrl")); |
urls.put("bogue", configuration.get("bogueUrl")); |
118,6 → 119,14 |
return getUrl("base"); |
} |
/** |
* Accesseur pour l'url de base du service annuaire::auth |
*/ |
public String getAuthServiceBaseUrl() { |
return getUrl("baseAuth"); |
} |
public String getUrl(String code) { |
if (urls.containsKey(code)) { |
return urls.get(code); |
/trunk/src/org/tela_botanica/client/util/UtilDAO.java |
---|
18,6 → 18,7 |
public class UtilDAO { |
private static String baseUrl = ((Configuration) Registry.get(RegistreId.CONFIG)).getServiceBaseUrl(); |
private static String baseAuthUrl = ((Configuration) Registry.get(RegistreId.CONFIG)).getAuthServiceBaseUrl(); |
public static final String GET = "GET"; |
public static final String POST = "POST"; |
public static final String DELETE = "DELETE"; |
96,8 → 97,31 |
return cheminUrl; |
} |
/** |
* Construit un JsonRestRequestBuilder avec le header Authorization |
*/ |
public static JsonRestRequestBuilder construireRequeteAuth(String nomService, HashMap<String, String> parametres) { |
return construireRequeteAuth(nomService, parametres, true); |
} |
/** |
* Construit un JsonRestRequestBuilder simple; si authorizationHeader vaut true, ajoute le header |
* "Authorization" pour le service d'authentification SSO (annuaire::auth) |
* |
* @param nomService le nom du service (identite, login, logout...) |
* @param parametres tableau de paramètres |
* @param boolean authorizationHeader si true, enverra le jeton (si existant) dans un header Authorization |
* @return JsonRestRequestBuilder |
*/ |
public static JsonRestRequestBuilder construireRequeteAuth(String nomService, HashMap<String, String> parametres, boolean authorizationHeader) { |
String parametresUrl = construireUrlParametres(parametres); |
String urlComplete = baseAuthUrl + nomService + parametresUrl; |
String urlCompleteEncodee = URL.encode(urlComplete); |
JsonRestRequestBuilder jrrb = new JsonRestRequestBuilder(JsonRestRequestBuilder.GET, urlCompleteEncodee, authorizationHeader); |
return jrrb; |
} |
public static String getUrlService(String nomService) { |
return baseUrl + nomService; |
} |
} |
/trunk/src/org/tela_botanica/client/http/JsonRestRequestBuilder.java |
---|
11,10 → 11,16 |
public class JsonRestRequestBuilder extends RequestBuilderWithCredentials { |
// avec header "Authorization" |
public JsonRestRequestBuilder(Method httpMethod, String url) { |
super(httpMethod, url); |
} |
// ajoute le header "Authorization" si authorizationHeader vaut true |
public JsonRestRequestBuilder(Method httpMethod, String url, boolean authorizationHeader) { |
super(httpMethod, url, authorizationHeader); |
} |
public Request envoyerRequete(String donneesRequete, RequestCallback retour) { |
Request requeteSortie = null; |
try { |
/trunk/src/org/tela_botanica/client/http/RequestBuilderWithCredentials.java |
---|
1,11 → 1,27 |
package org.tela_botanica.client.http; |
import org.tela_botanica.client.modeles.Utilisateur; |
import com.google.gwt.http.client.RequestBuilder; |
public class RequestBuilderWithCredentials extends RequestBuilder { |
/** |
* Crée une requête AJAX avec les headers "Credentials" (pour CORS) et "Authorization" (pour SSO) |
*/ |
public RequestBuilderWithCredentials(Method httpMethod, String url) { |
this(httpMethod, url, true); |
} |
/** |
* Crée une requête AJAX avec les headers "Credentials" (pour CORS); si authorizationHeader est true, |
* ajoute le header "Authorization" pour SSO |
*/ |
public RequestBuilderWithCredentials(Method httpMethod, String url, boolean authorizationHeader) { |
super(httpMethod, url); |
if(authorizationHeader && Utilisateur.getJeton() != null && ! Utilisateur.getJeton().isEmpty()) { |
this.setHeader("Authorization", Utilisateur.getJeton()); |
} |
this.setIncludeCredentials(true); |
} |
} |
/trunk/src/org/tela_botanica/client/http/JsonRestRequestCallback.java |
---|
37,7 → 37,7 |
// Affichage du débogage. |
afficherDebug(response); |
// Si le code de réponse HTTP ne vaut pas 200 OK, on lance le mécanise d'erreur HTTP |
// Si le code de réponse HTTP ne vaut pas 200 OK, on lance le mécanisme d'erreur HTTP |
if (response.getStatusCode() != 200) { |
onErrorHTTP(request, response); |
} else { |
/trunk/src/org/tela_botanica/client/Modele.java |
---|
92,6 → 92,11 |
uDao.deconnecterUtilisateur(); |
} |
public void getEtatUtilisateur(Rafraichissable vue) { |
UtilisateurAsyncDao uDao = new UtilisateurAsyncDao(vue); |
uDao.getEtatUtilisateur(); |
} |
public void accepterLicence(Rafraichissable vue) { |
UtilisateurAsyncDao uDao = new UtilisateurAsyncDao(vue); |
uDao.accepterLicence(); |
/trunk/src/org/tela_botanica/client/modeles/UtilisateurAsyncDao.java |
---|
1,5 → 1,8 |
package org.tela_botanica.client.modeles; |
import java.util.HashMap; |
import org.tela_botanica.client.Coel; |
import org.tela_botanica.client.RegistreId; |
import org.tela_botanica.client.http.JsonRestRequestBuilder; |
import org.tela_botanica.client.http.JsonRestRequestCallback; |
7,11 → 10,15 |
import org.tela_botanica.client.util.UtilDAO; |
import com.extjs.gxt.ui.client.Registry; |
import com.google.gwt.core.client.GWT; |
import com.google.gwt.http.client.Request; |
import com.google.gwt.http.client.Response; |
import com.google.gwt.json.client.JSONArray; |
import com.google.gwt.json.client.JSONBoolean; |
import com.google.gwt.json.client.JSONNumber; |
import com.google.gwt.json.client.JSONObject; |
import com.google.gwt.json.client.JSONString; |
import com.google.gwt.json.client.JSONValue; |
import com.google.gwt.user.client.Timer; |
/** |
* Modele DAO, specifique, permettant la validation, l'acces aux donnees distantes et la présentation des donnees en retour |
23,6 → 30,8 |
private Utilisateur utilisateur = null; |
private Rafraichissable vueARafraichir = null; |
private static Timer rafraichisseurJeton = null; |
/** |
* Constructeur |
* @param retour : méthode appellée en retour d'appel. |
33,70 → 42,194 |
} |
/** |
* Méthode de classe d'appel du service des gestion d'identification. |
* Recupere des informations d'authentification à partir du JSON renvoyé par les |
* services Auth de l'annuaire (SSO) |
* |
* @param valeurJson |
* @return AuthInfo |
*/ |
public void getEtatUtilisateur() { |
final JsonRestRequestBuilder rb = UtilDAO.construireRequete(SERVICE_NOM); |
rb.envoyerRequete(null, new JsonRestRequestCallback() { |
public static AuthInfo parserAuthJson(JSONValue valeurJson) { |
AuthInfo authInfo = new AuthInfo(); |
JSONObject authJson = valeurJson.isObject(); |
JSONValue erreurJSON = authJson.get("error"); |
// test si erreur |
if (erreurJSON != null) { |
JSONString erreur = erreurJSON.isString(); |
if (erreur != null) { |
authInfo.error = erreur.stringValue(); |
} else { |
authInfo.error = "erreur d'authentification"; |
} |
} else { |
boolean session = authJson.get("session").isBoolean().booleanValue(); |
authInfo.session = session; |
// test si session ouverte |
if (session) { |
JSONValue tokenJson = authJson.get("token"); |
String token = null; |
// protection paranoïaque |
if (tokenJson != null) { |
JSONString tokenString = tokenJson.isString(); |
if (tokenString != null) { |
token = tokenString.stringValue(); |
} |
} |
String tokenId = authJson.get("token_id").isString().stringValue(); |
int duration = (int) authJson.get("duration").isNumber().doubleValue(); |
JSONValue lastModifJson = authJson.get("last_modif"); |
authInfo.token = token; |
authInfo.tokenId = tokenId; |
authInfo.duration = duration; |
// test si lastModif est null |
if (lastModifJson != null) { |
JSONNumber lastModif = lastModifJson.isNumber(); |
if (lastModif != null) { |
authInfo.lastModif = (int) lastModif.doubleValue(); |
} |
} |
} |
} |
return authInfo; |
} |
/** |
* Enregistre le jeton (potentiellement NULL), sa durée de vie; appelle le service Coel pour |
* obtenir les données de l'utilisateur relatives à l'application; lorsque le jeton |
* n'est pas nul et que sa durée de vie est spécifiée, déclenche un rafraîchissement |
* périodique du jeton |
* |
* @param objetRetour le retour de l'appel au service annuaire:auth (connexion, deconnexion ou identite) |
*/ |
protected void gererRetourAuthInfo(AuthInfo objetRetour) { |
// Stockage du jeton rafraîchi et de sa durée (pourrait avoir changé) |
Utilisateur.setJeton(objetRetour.token); |
Utilisateur.setDureeJeton(objetRetour.duration); |
// Rafraîchissement automatique du jeton - s'annule lorsque le jeton devient nul |
if (Utilisateur.getJeton() != null && Utilisateur.getDureeJeton() > 0) { |
if (rafraichisseurJeton == null) { // on sait jamais |
// 3/4 de la durée de vie du jeton, en millisecondes (ça laisse de la marge) |
int delaiRepetition = (Utilisateur.getDureeJeton() * 1000) * 3 / 4; |
delaiRepetition = 10000; // debug |
rafraichisseurJeton = new Timer() { |
@Override |
public void surReponse(JSONValue responseValue) { |
if (responseValue.isArray() != null) { |
final JSONArray reponse = responseValue.isArray(); |
// Identifiant utilisateur ou identifiant de session si non identifié |
String login = ((JSONString) reponse.get(2)).stringValue(); |
// Drapeau leve si utilisateur deja identifié |
boolean identifie = ((JSONBoolean) reponse.get(3)).booleanValue(); |
utilisateur.setIdentification(identifie); |
utilisateur.setLogin(login); |
vueARafraichir.rafraichir(utilisateur); |
public void run() { |
//Coel.LogVersFirebug("rafraichir utilisateur"); |
// Appel de l'annuaire pour rafraîchir le jeton (cb nul pour l'instant) |
getEtatUtilisateur(); |
} |
}; |
rafraichisseurJeton.scheduleRepeating(delaiRepetition); |
} |
}); |
} else { |
if (rafraichisseurJeton != null) { // on sait jamais non plus |
rafraichisseurJeton.cancel(); |
rafraichisseurJeton = null; |
} |
} |
// Obtention de l'utilisateur final d'après le service de préférences |
//Coel.LogVersFirebug("va chercher utilisateur Coel"); |
getEtatUtilisateurSansAnnuaire(); |
} |
/** |
* Méthode déconnectant un utilisateur de l'application. |
* @param identifiant de l'utilisateur à déconnecter. |
*/ |
public void deconnecterUtilisateur() { |
String[] parametres = {utilisateur.getId()}; |
//Coel.LogVersFirebug("deconnexion"); |
final JsonRestRequestBuilder rb = UtilDAO.construireRequeteAuth("deconnexion", null); |
rb.envoyerRequete(null, new JsonRestRequestCallback() { |
@Override |
public void surReponse(JSONValue responseValue) { |
//Coel.LogVersFirebug(responseValue.toString()); |
AuthInfo info = parserAuthJson(responseValue); |
gererRetourAuthInfo(info); |
} |
}); |
} |
final JsonRestRequestBuilder rb = UtilDAO.construireRequete(SERVICE_NOM, parametres); |
/** |
* Méthode connectant un utilisateur à l'application |
*/ |
public void connecterUtilisateur() { |
HashMap<String, String> parametres = new HashMap<String, String>(); |
parametres.put("login", utilisateur.getLogin()); |
parametres.put("password", utilisateur.getMotDePasse()); |
//Coel.LogVersFirebug("connexion"); |
final JsonRestRequestBuilder rb = UtilDAO.construireRequeteAuth("connexion", parametres); |
rb.envoyerRequete(null, new JsonRestRequestCallback() { |
@Override |
public void surReponse(JSONValue responseValue) { |
if (responseValue.isArray() != null) { |
final JSONArray reponse = responseValue.isArray(); |
// Identifiant utilisateur ou identifiant de session si non identifié |
String id = ((JSONString) reponse.get(1)).stringValue(); |
// Drapeau levé si l'utilisateur est déjà identifié |
boolean identifie = ((JSONBoolean) reponse.get(2)).booleanValue(); |
AuthInfo info = parserAuthJson(responseValue); |
gererRetourAuthInfo(info); |
} |
}); |
} |
// Nous réinitialisons l'utilisateur |
utilisateur = new Utilisateur(id, identifie); |
/** |
* Va chercher les infos de l'utilisateur en vérifiant d'abord l'identité auprès de l'annuaire |
*/ |
public void getEtatUtilisateur() { |
getEtatUtilisateur(false); |
} |
GWT.log(utilisateur.toString(), null); |
vueARafraichir.rafraichir(utilisateur); |
/** |
* Va chercher les infos de l'utilisateur; si ignorerAnnuaire est false, vérifiera d'abord |
* l'identité auprès de l'annuaire (service annuaire:auth/identite) |
*/ |
public void getEtatUtilisateur(boolean ignorerAnnuaire) { |
// par défaut, on appelle d'abord le service auth/identite de l'annuaire, afin de connaître |
// le statut de l'utilisateur vis à vis du SSO (connecté ou non) et rafraîchir le jeton |
if (! ignorerAnnuaire) { |
//Coel.LogVersFirebug("get état"); |
// sans header Authorization, sinon une déconnexion depuis une autre appli ne sera pas |
// prise en compte et le jeton sera rafraîchi indéfiniment |
final JsonRestRequestBuilder rb = UtilDAO.construireRequeteAuth("identite", null, false); |
rb.envoyerRequete(null, new JsonRestRequestCallback() { |
@Override |
public void surReponse(JSONValue responseValue) { |
AuthInfo info = parserAuthJson(responseValue); |
gererRetourAuthInfo(info); |
} |
@Override |
public void onErrorHTTP(Request request, Response reponse) { |
// Si on a été déconnecté, on va chercher un profil utilisateur "anonyme" et |
// on avertit l'interface |
gererRetourAuthInfo(new AuthInfo()); |
} |
}); |
} else { // sinon on optimise, quand on vient de se (de)connecter, pas la peine de rappeler l'annuaire |
getEtatUtilisateurSansAnnuaire(); |
} |
} |
/** |
* Méthode connectant un utilisateur à l'application. |
* @param Utilisateur l'utilisateur courant. |
* Va chercher les infos de l'utilisateur Coel sans vérifier l'identité auprès de l'annuaire; |
* transmet un jeton SSO à chaque fois pour que le service se base dessus |
*/ |
public void connecterUtilisateur() { |
String[] parametres = {utilisateur.getLogin(), utilisateur.getMotDePasse()}; |
final JsonRestRequestBuilder rb = UtilDAO.construireRequete(SERVICE_NOM, parametres); |
public void getEtatUtilisateurSansAnnuaire() { |
final JsonRestRequestBuilder rb = UtilDAO.construireRequete(SERVICE_NOM); |
rb.envoyerRequete(null, new JsonRestRequestCallback() { |
@Override |
public void surReponse(JSONValue responseValue) { |
if (responseValue.isArray() != null) { |
final JSONArray reponse = responseValue.isArray(); |
//Coel.LogVersFirebug("réponse reçue"); |
JSONArray reponse = responseValue.isArray(); |
if (reponse != null) { |
// Identifiant utilisateur ou identifiant de session si non identifié |
String login = ((JSONString) reponse.get(1)).stringValue(); |
// Drapeau leve si utilisateur deja identifié |
boolean identifie = ((JSONBoolean) reponse.get(2)).booleanValue(); |
utilisateur.setIdentification(identifie); |
utilisateur.setLogin(login); |
//Ajout des informations licence |
if (reponse.get(0).isString() != null) { |
utilisateur.setLicence(reponse.get(0).isString().stringValue()); |
104,45 → 237,54 |
utilisateur.setLicence(""); |
} |
if (reponse.size() > 0) { |
//Coel.LogVersFirebug("Utilisateur CoeL chargé !!"); |
//Coel.LogVersFirebug("id: " + identifie + ", login: " + login + ", licence: " + utilisateur.getLicence()); |
//Coel.LogVersFirebug("Taille réponse:" + reponse.size()); |
// Utilisateur connecté (non anonyme) |
if (reponse.size() > 3) { |
//Coel.LogVersFirebug("Utilisateur identifié, on charge plus de trucs !"); |
// Identifiant de l'utilisateur ou identifiant de session si non identifié |
utilisateur.setId(((JSONString) reponse.get(1)).stringValue()); |
String idUtilisateur = ((JSONString) reponse.get(1)).stringValue(); |
//Coel.LogVersFirebug("ID utilisateur: " + idUtilisateur); |
utilisateur.setId(idUtilisateur); |
// Drapeau levé si l'utilisateur est déjà identifié |
utilisateur.setIdentification(((JSONBoolean) reponse.get(2)).booleanValue()); |
// Plus de deux valeurs, l'utilisateur est identifié nous récupérons des données supplémentaires |
if (reponse.size() > 3) { |
// Nom complet de l'utilisateur |
if (reponse.get(3).isString() != null) { |
utilisateur.setNomComplet(((JSONString) reponse.get(3)).stringValue()); |
String nomComplet = ((JSONString) reponse.get(3)).stringValue(); |
//Coel.LogVersFirebug("Nom complet: " + nomComplet); |
utilisateur.setNomComplet(nomComplet); |
} |
// Prénom de l'utilisateur |
if (reponse.get(4).isString() != null) { |
utilisateur.setPrenom(((JSONString) reponse.get(4)).stringValue()); |
String prenom = ((JSONString) reponse.get(4)).stringValue(); |
utilisateur.setPrenom(prenom); |
//Coel.LogVersFirebug("Prénom: " + prenom); |
} |
// Nom de l'utilisateur |
if (reponse.get(5).isString() != null) { |
utilisateur.setNom(((JSONString) reponse.get(5)).stringValue()); |
String nom = ((JSONString) reponse.get(5)).stringValue(); |
utilisateur.setNom(nom); |
//Coel.LogVersFirebug("Nom: " + nom); |
} |
// Paramètres |
// Paramètre => was ist das ? |
if (reponse.get(6).isString() != null) { |
utilisateur.setParametre(((JSONString) reponse.get(6)).stringValue()); |
String parametre = ((JSONString) reponse.get(6)).stringValue(); |
utilisateur.setParametre(parametre); |
//Coel.LogVersFirebug("Paramètre: " + parametre); |
} |
} |
} |
//Coel.LogVersFirebug("Rafraîchissement vue"); |
vueARafraichir.rafraichir(utilisateur); |
} |
} |
} |
}); |
} |
/* |
* */ |
/** |
* @TODO CHANGER ÇA ! |
*/ |
public void accepterLicence() { |
String[] parametres = {utilisateur.getLogin(), utilisateur.getMotDePasse(), utilisateur.getId()}; |
/trunk/src/org/tela_botanica/client/modeles/Utilisateur.java |
---|
1,6 → 1,5 |
package org.tela_botanica.client.modeles; |
import java.util.HashMap; |
import java.util.Iterator; |
import java.util.Set; |
10,6 → 9,11 |
private static final long serialVersionUID = -4016615552202089985L; |
// Jeton encodé en base64 passé de manière automatique aux services dans un header |
private static String jeton = ""; |
// Durée de vie du jeton en secondes (sert au rafraîchissement automatique) |
private static int duree = 0; |
public static final String PREFIXE = "cp"; |
public static String[] champsObligatoires = {"cp_id_personne"}; |
142,6 → 146,22 |
set("parametre", param); |
} |
public static String getJeton() { |
return Utilisateur.jeton; |
} |
public static void setJeton(String jeton) { |
Utilisateur.jeton = jeton; |
} |
public static int getDureeJeton() { |
return Utilisateur.duree; |
} |
public static void setDureeJeton(int duree) { |
Utilisateur.duree = duree; |
} |
// +---------------------------------------------------------------------------------------------------------------+ |
// IDENTIFIÉ |
/** |
/trunk/src/org/tela_botanica/client/modeles/AuthInfo.java |
---|
New file |
0,0 → 1,30 |
package org.tela_botanica.client.modeles; |
/** |
* Une mini classe pour stocker facilement le retour d'un appel aux services |
* annuaire:auth (connexion, deconnexion, identite) |
* |
* @author mathias |
*/ |
public class AuthInfo { |
public String token; |
public boolean session; |
public String tokenId; |
public int duration; |
public Integer lastModif; // Integer pour pouvoir mettre null dedans |
public String error; |
public AuthInfo(String token, boolean session, String tokenId, int duration, Integer lastModif, String error) { |
this.token = token; |
this.session = session; |
this.tokenId = tokenId; |
this.duration = duration; |
this.lastModif = lastModif; |
this.error = error; |
} |
public AuthInfo() { |
this(null, false, null, 0, null, null); |
} |
} |
/trunk/src/org/tela_botanica/client/Mediateur.java |
---|
160,8 → 160,8 |
creerPanneauSud(); |
fenetreJournal = new FenetreJournal(this); |
// Connection de l'utilisateur |
connecterUtilisateur(null, null); |
// Connexion de l'utilisateur |
getEtatUtilisateur(); |
// Creer la table qui gère le panneau de chargement/déchargement |
fileDAttenteChargement = new HashMap<Object, Integer>(); |
441,6 → 441,10 |
modele.deconnecterUtilisateur(this); |
} |
public void getEtatUtilisateur() { |
modele.getEtatUtilisateur(this); |
} |
public void repandreEtatIdentification(Utilisateur utilisateur) { |
// Mise à jour du registre |
Registry.register(RegistreId.UTILISATEUR_COURANT, utilisateur); |
/trunk/war/config/config.defaut.js |
---|
1,5 → 1,6 |
var configuration = { |
serviceBaseUrl:"http://www.tela-botanica.org/eflore/coel/jrest/", |
serviceAuthBaseUrl:"https://www.tela-botanica.org/service:annuaire:auth/", |
bogueUrl:"http://www.tela-botanica.net/suivi/index.php?project=31", |
commentaireUrl:"http://www.tela-botanica.org/wikini/eflore/wakka.php?wiki=AideCoelCommentaire", |
celUrl:"http://www.tela-botanica.org/appli:cel", |