Subversion Repositories eFlore/Applications.coel

Rev

Rev 1852 | Rev 1907 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1497 jpm 1
<?php
2
/**
3
 * Service d'itentification d'un utilisateur.
1906 mathias 4
 * Modifié pour fonctionner avec le SSO.
5
 *
1497 jpm 6
 * Encodage en entrée : utf8
7
 * Encodage en sortie : utf8
1906 mathias 8
 *
1497 jpm 9
 * Cas d'utilisation :
1906 mathias 10
 * Le service reçoit un jeton SSO (ou pas) dans le header "Authorization", le fait
11
 * vérifier par l'annuaire; s'il est valide, le décode puis retourne le profil utilisateur
12
 * associé; sinon retourne un profil anonyme.
1497 jpm 13
 *
1906 mathias 14
 * 1: Aucun jeton ou jeton invalide transmis
15
 * 	1: L'application retourne l'identifiant de session en cours (mode anonyme)
1497 jpm 16
 *
1906 mathias 17
 * 2: Jeton valide transmis
18
 * 	1 : Passe ou reste dans l'état "connecté"; le profil actif est retourné
1497 jpm 19
 *
20
 *  En résumé, utilisation des URLs :
1906 mathias 21
 *  /CoelUtilisateur/identite : retour identifiant de session si jamais connecté, sinon retour de l'id (+ login et mot de passe)
1497 jpm 22
 *
23
 * @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
1906 mathias 24
 * @author David DELON <david.delon@clapas.net>
25
 * @author Mathias CHOUET <mathias@tela-botanica.org>
1497 jpm 26
 * @license GPL v3 <http://www.gnu.org/licenses/gpl.txt>
27
 * @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>
28
 * @version $Id$
29
 * @copyright 2009
30
 */
31
class CoelUtilisateur extends Coel {
32
 
1906 mathias 33
	protected $licence_acceptee = false;
34
 
35
	/**
36
	 * Point d'entrée GET
37
	 */
38
	public function getRessource() {
39
		// par défaut, mode anonyme
40
		$infos = $this->getUtilisateurAnonyme();
41
		// recherche d'un utilisateur identifié en fonction du jeton SSO
42
		$infosIdentifie = $this->getUtilisateurSSO();
43
		if ($infosIdentifie != null) {
44
			//echo "Infos SSO : "; var_dump($infosIdentifie);
45
			$infos = $infosIdentifie;
46
			// si la licence n'a pas été acceptée, au revoir les enfants
47
			// on envoie tout de même les infos à l'interface mais on déconnecte les services
1497 jpm 48
			$this->deconnecterUtilisateur();
49
		}
1906 mathias 50
		// et vlan, passe-moi l'éponge
51
		$this->envoyer($infos);
52
	}
53
 
54
	/**
55
	 * Renvoie un profil utilisateur CoeL anonyme
56
	 * (licence vide, id de session, non connecté)
57
	 */
58
	protected function getUtilisateurAnonyme() {
59
		$info = array("", session_id(), false);
60
		return $info;
61
	}
62
 
63
	/**
64
	 * Recherche un jeton SSO dans l'entête HTTP "Authorization", vérifie ce
65
	 * jeton auprès de l'annuaire et en cas de succès charge les informations
66
	 * de l'utilisateur associé; si besoin, copie l'utilisateur dans la table
67
	 * Personnes de CoeL
68
	 *
69
	 * @return Array un profil utilisateur ou null
70
	 */
71
	protected function getUtilisateurSSO() {
72
		$utilisateur = null;
73
		// lecture du jeton
74
		$jeton = $this->lireJetonEntete();
75
		//echo "Jeton : "; var_dump($jeton);
76
		if ($jeton != null) {
77
			// validation par l'annuaire
78
			$valide = $this->verifierJeton($jeton);
79
			if ($valide === true) {
80
				// décodage du courriel utilisateur depuis le jeton
81
				$donneesJeton = $this->decoderJeton($jeton);
82
				if ($donneesJeton != null && $donneesJeton["sub"] != "") {
83
					// récupération de l'utilisateur
84
					$courriel = $donneesJeton["sub"];
85
 
86
					// lecture des infos dans l'annuaire (relou mais nécessaire pour la copie dans la table Personnes de CoeL)
87
					$infosAnnuaire = $this->obtenirInfosAnnuaire($courriel);
88
					$this->setInfosAnnuaire($infosAnnuaire);
89
					//echo "Infos Annu: "; var_dump($infosAnnuaire);
90
 
91
					// lecture de l'utilisateur connu (ou pas) par CoeL (table Personnes)
92
					$utilisateur_existant = $this->chargerUtilisateur($courriel);
93
					//echo "UTIL EXIST: "; var_dump($utilisateur_existant);
94
 
95
					// Vérification de la nécessité de mettre à jour l'utilisateur COEL vis à vis de l'annuaire de Tela Botanica
96
					if (!is_null($infosAnnuaire) &&  $this->avoirBesoinMiseAJour($utilisateur_existant)) {
97
						$this->debug[] = "Besoin d'une mise à jour";
98
 
99
						$presence_dans_coel = ($utilisateur_existant != false); // si on l'a trouvé juste au dessus
100
						$this->debug[] = "Presence: $presence_dans_coel";
101
						$mot_de_passe_sha1 = $infosAnnuaire['pass'];
102
						//echo "MDP: " .$mot_de_passe_sha1;
103
 
104
						if ($presence_dans_coel) {
105
							// Nécessite de faire une mise à jour
106
							$this->debug[] = "Mise à jour de l'utilisateur {$infosAnnuaire['id']}";
107
							$this->mettreAJourUtilisateur($login, $mot_de_passe_sha1, $infosAnnuaire);
108
							$utilisateur_existant['licence'] = (int) $this->recupererLicenceUtilisateur($infosAnnuaire['id']);
109
						} else {
110
							echo "Insertion";
111
							// Nécessite d'ajouter le nouvel utilisateur
112
							$this->debug[] = "Ajout d'une nouvel utilisateur";
113
							$this->ajouterUtilisateurACoel($infosAnnuaire, $mot_de_passe_sha1);
114
							// rechargement après l'avoir ajouté (un peu nul)
115
							$utilisateur_existant = $this->chargerUtilisateur($courriel);
116
						}
117
 
118
						$this->setUtilisateur($utilisateur_existant);
1497 jpm 119
					}
1906 mathias 120
 
121
					// stockage de l'utilisateur en session
122
					$this->setUtilisateur($utilisateur_existant);
123
 
124
					// renvoi des infos
125
					$utilisateur = array($utilisateur_existant['licence'], $infosAnnuaire['id'], true, $this->getUtilisateurNomComplet(), $this->getUtilisateurPrenom(), $this->getUtilisateurNom(), $this->getParametre());
1497 jpm 126
				}
127
			}
128
		}
129
 
1906 mathias 130
		return $utilisateur;
1497 jpm 131
	}
132
 
1906 mathias 133
	protected function deconnecterUtilisateur() {
1497 jpm 134
		$_SESSION['coel_utilisateur'] = '';
135
		$_SESSION['coel_infosAnnuaire'] = '';
136
	}
137
 
1906 mathias 138
	/**
139
	 * Retourne true si le compte utilisateur a été modifié dans l'annuaire, et que les
140
	 * modifications ont besoin d'être répercutées dans la table Personnes de Coel
141
	 */
142
	protected function avoirBesoinMiseAJour($info_annuaire_coel) {
1497 jpm 143
		$necessite_maj = false;
144
		if ($info_annuaire_coel == false) {
145
			// Le login et/ou le mot de passe a pu changer
146
			$necessite_maj = true;
147
		} else {
148
			$info_annuaire_distant = $this->getInfosAnnuaire();
149
			if ($this->comparerInfosAnnuairesDistantEtCoel($info_annuaire_distant, $info_annuaire_coel) == false) {
150
				$necessite_maj = true;
151
			}
152
		}
153
		return $necessite_maj;
154
	}
155
 
1906 mathias 156
	protected function comparerInfosAnnuairesDistantEtCoel($annuaire_distant, $annuaire_coel) {
1497 jpm 157
		$identique = true;
158
		$tableau_annuaire_distant = array('nom' => $annuaire_distant['nom'],
159
			'prenom' => $annuaire_distant['prenom'],
160
			'ville' => $annuaire_distant['ville'],
161
			'code_postal' => $annuaire_distant['code_postal']);
162
		$tableau_annuaire_coel = array('nom' => $annuaire_coel['nom'],
163
			'prenom' => $annuaire_coel['prenom'],
164
			'ville' => $annuaire_coel['ville'],
165
			'code_postal' => $annuaire_coel['code_postal']);
166
		foreach ($tableau_annuaire_distant as $cle => $valeur) {
167
			if ($tableau_annuaire_coel[$cle] != $valeur) {
168
				$identique = false;
169
				break;
170
			}
171
		}
172
		return $identique;
173
	}
1906 mathias 174
 
175
	/**
176
	 * Vérifie si un utilisateur est présent dans la table Personnes de CoeL
177
	 */
178
	protected function verifierPresenceUtilisateur($id) {
1497 jpm 179
		$present = false;
180
		$requete =	'SELECT COUNT(cp_id_personne) AS nbre '.
181
					'FROM coel_personne '.
1508 jpm 182
					"WHERE cp_ce_annuaire = {$this->bdd->quote($id)} ".
1635 aurelien 183
					"	AND cp_ce_annuaire = cp_id_personne ";
1497 jpm 184
		try {
185
			$nbre = $this->bdd->query($requete)->fetchColumn();
1508 jpm 186
			if (0 == $nbre) {
187
				$this->debug[] = "Utilisateur NON présent dans l'annuaire de COEL.";
188
			} else if (1 == $nbre) {
189
				$this->debug[] = "Utilisateur présent dans l'annuaire de COEL.";
1497 jpm 190
				$present = true;
1508 jpm 191
			} else if (false === $nbre) {
192
				$this->debug[] = "Erreur dans la requête de vérification de présence dans l'annuaire de COEL.";
1497 jpm 193
			}
194
		} catch (PDOException $e) {
195
			$this->messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage());
196
		}
197
 
198
		return $present;
199
	}
1906 mathias 200
 
201
	/**
202
	 * Renvoie l'état d'acceptation de la licence pour un utilisateur donné :
203
	 * 0 (non acceptée) ou 1 (acceptée)
204
	 */
205
	protected function recupererLicenceUtilisateur($id) {
1497 jpm 206
		$requete =	'SELECT cp_mark_licence '.
207
					'FROM coel_personne '.
1508 jpm 208
					"WHERE cp_ce_annuaire = {$this->bdd->quote($id)} ".
1635 aurelien 209
					"	AND cp_ce_annuaire = cp_id_personne ";
1497 jpm 210
		try {
211
			$licence = $this->bdd->query($requete)->fetchColumn();
212
			if ($licence === false) {
213
				$this->debug[] = "La licence n'a pas pu être récupérée.";
214
				return 0;
215
			} else {
216
				return $licence;
217
			}
218
		} catch (PDOException $e) {
219
			$this->messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage());
220
		}
221
	}
1906 mathias 222
 
223
	/**
224
	 * Met à jour les données de l'utilisateur dans la table CoeL Personnes
225
	 */
226
	protected function mettreAJourUtilisateur($login, $mot_de_passe_sha1, $infos) {
1497 jpm 227
		try {
228
		   	$cp_fmt_nom_complet = $infos['prenom'].' '.$infos['nom'];
229
			$requete = 	'UPDATE coel_personne '.
1508 jpm 230
		   				"SET cp_id_personne = '{$infos['id']}', ".
1497 jpm 231
						"	cp_fmt_nom_complet = '$cp_fmt_nom_complet', cp_prenom = '{$infos['prenom']}', cp_nom = '{$infos['nom']}', ".
232
						"	cp_code_postal = '{$infos['code_postal']}', cp_ville = '{$infos['ville']}', cp_truk_courriel = '{$infos['courriel']}', ".
233
						"	cp_login = '{$infos['courriel']}', cp_mot_de_passe = '$mot_de_passe_sha1', cp_ce_annuaire = '{$infos['id']}' ".
234
		   				"WHERE cp_login = '$login' ".
1587 aurelien 235
		   				"	AND cp_mot_de_passe = '{$infos['mot_de_passe']}' ";
1508 jpm 236
 
1497 jpm 237
			// Ajout des données
238
			$resultat = $this->bdd->exec($requete);
239
			if ($resultat === false) {
240
				$this->messages[] = "L'utilisateur n'a pas été mis à jour car la requête a échouée.";
241
			}
242
		} catch (PDOException $e) {
243
			$messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage(), $requete);
244
		}
245
	}
1906 mathias 246
 
247
	/**
248
	 * Ajoute une copie de l'utilisateur dans la table CoeL Personnes
249
	 */
250
	protected function ajouterUtilisateurACoel($infos, $mot_de_passe_sha1) {
1497 jpm 251
		try {
252
			// Construction de la requête d'ajout
253
		   	// Notes : pour rester compatibles avec l'annuaire de Tela, les utilisateurs sont ajoutés directement avec l'id
254
		   	// de l'annuaire Tela. Dans CoelPersonne, les personnes qui ne sont pas utilisateur sont ajoutés avec un id supérieur à 100 000
255
		   	$cp_fmt_nom_complet = $infos['prenom'].' '.$infos['nom'];
256
		   	$cp_mark_licence = '0';
257
		   	$requete = 	'INSERT INTO coel_personne '.
1765 aurelien 258
		   				'	(cp_id_personne, cp_fmt_nom_complet, cp_prenom, cp_nom, cp_code_postal, '.
1508 jpm 259
						'	cp_ville, cp_truk_courriel, cp_login, cp_mot_de_passe, cp_ce_annuaire, cp_mark_licence) '.
1765 aurelien 260
		   				"VALUES ('{$infos['id']}', '$cp_fmt_nom_complet', '{$infos['prenom']}', '{$infos['nom']}', ".
1508 jpm 261
		   				"'{$infos['code_postal']}', '{$infos['ville']}', '{$infos['courriel']}', '{$infos['courriel']}', '".$mot_de_passe_sha1."', ".
1497 jpm 262
		   				"'{$infos['id']}', $cp_mark_licence) ";
1508 jpm 263
		   	//$this->debug[] = $requete;
1497 jpm 264
			// Ajout des données
265
			$resultat = $this->bdd->exec($requete);
266
			if ($resultat === false) {
1508 jpm 267
				$this->debug[] = "Utilisateur NON ajouté dans coel_personne car la requête a échouée.";
268
			} else {
269
				$this->debug[] = "Utilisateur ajouté à coel_personne.";
1497 jpm 270
			}
271
		} catch (PDOException $e) {
272
			$messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage(), $requete);
273
		}
274
	}
1852 mathias 275
 
1906 mathias 276
	/**
277
	 * Appelle l'annuaire pour connaître tous les détails de l'utilisateur de courriel $login
278
	 */
279
	protected function obtenirInfosAnnuaire($login)	{
1497 jpm 280
		$url_annuaire = $this->config['coel']['urlAnnuaire'];
281
		$login_annuaire = $this->config['coel']['loginAnnuaire'];
282
		$mdp_annuaire = $this->config['coel']['mdpAnnuaire'];
1852 mathias 283
 
284
		$posds = strpos($url_annuaire, "//");
285
		$protocole = substr($url_annuaire, 0, $posds+2);
286
		$reste_adresse = substr($url_annuaire, $posds+2);
287
		// auth HTTP basic cracra
288
		$url_annuaire = $protocole . $login_annuaire . ':' . $mdp_annuaire . "@" . $reste_adresse;
289
		$url_annuaire .= '/' . $login;
290
		$url_annuaire .= '/xml'; // @TODO un jour, faire en sorte que ça lise du JSON
291
 
1497 jpm 292
		$resultat_annuaire = file_get_contents($url_annuaire);
293
		$tableau_annuaire = null;
294
		if ($xml_utilisateur = simplexml_load_string($resultat_annuaire))	{
295
			// La fonction a retourné un objet
296
			foreach ($xml_utilisateur->children() as $key => $val) {
297
				if ((string) $val != '') {
298
			  		$tableau_annuaire[$key] = (String) $val;
299
				}
300
			}
301
		}
302
		return $tableau_annuaire;
303
	}
1852 mathias 304
 
1906 mathias 305
	/**
306
	 * Enregistre le fait que la personne de login $login a accepté la licence de CoeL
307
	 */
308
	protected function accepterLicence($login) {
1497 jpm 309
		$sortie = false;
310
		try {
311
			$requete = 	'UPDATE coel_personne '.
312
						'SET cp_mark_licence = 1 '.
1508 jpm 313
						"WHERE cp_login = {$this->bdd->quote($login)} ";
1497 jpm 314
			$resultat = $this->bdd->exec($requete);
315
			if ($resultat === false) {
1906 mathias 316
				$this->debug[] = "La table Personne n'a pas été mise à jour car la requête a échoué";
1497 jpm 317
			} else {
1508 jpm 318
				$this->debug[] = "Création du cookie licence.";
1497 jpm 319
				$_SESSION['coel_utilisateur']['licence'] = '1';
1906 mathias 320
				// @TODO CHANGER
1508 jpm 321
				$this->setCookiePersistant('coel_licence', '1');
1497 jpm 322
				$sortie = true;
323
			}
324
		} catch (PDOException $e) {
325
		 	$messages[] = sprintf($this->getTxt('sql_erreur'), $e->getFile(), $e->getLine(), $e->getMessage(), $requete);
326
		}
327
		return $sortie;
328
	}
1906 mathias 329
 
330
	/**
331
	 * Essaye de trouver un jeton JWT non vide dans l'entête HTTP "Authorization"
332
	 *
333
	 * @return String un jeton JWT ou null
334
	 */
335
	protected function lireJetonEntete() {
336
		$jwt = null;
337
		$headers = apache_request_headers();
338
		if (isset($headers["Authorization"]) && ($headers["Authorization"] != "")) {
339
			$jwt = $headers["Authorization"];
340
		}
341
		return $jwt;
342
	}
343
 
344
	/**
345
	 * Vérifie un jeton auprès de l'annuaire
346
	 *
347
	 * @param String $jeton un jeton JWT
348
	 * @return true si le jeton est vérifié, false sinon
349
	 */
350
	protected function verifierJeton($jeton) {
351
		$urlServiceVerification =$this->config['coel']['urlServiceBaseAuth'] . "verifierjeton";
352
		$urlServiceVerification .= "?token=" . $jeton;
353
 
354
		// file_get_contents râle si le certificat HTTPS est auto-signé
355
		//$retour = file_get_contents($urlServiceVerification);
356
 
357
		// curl avec les options suivantes ignore le pb de certificat (pour tester en local)
358
		$ch = curl_init();
359
		$timeout = 5;
360
		curl_setopt($ch, CURLOPT_URL, $urlServiceVerification);
361
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
362
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
363
		// équivalent de "-k"
364
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
365
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
366
		$data = curl_exec($ch);
367
		curl_close($ch);
368
		$retour = $data;
369
 
370
		$retour = json_decode($retour, true);
371
 
372
		return ($retour === true);
373
	}
374
 
375
	/**
376
	 * Décode un jeton JWT (SSO) précédemment validé et retourne les infos
377
	 * qu'il contient (payload / claims)
378
	 * @param String $jeton un jeton JWT précédemment validé
379
	 */
380
	protected function decoderJeton($jeton) {
381
		$parts = explode('.', $jeton);
382
		$payload = $parts[1];
383
		$payload = base64_decode($payload);
384
		$payload = json_decode($payload, true);
385
 
386
		return $payload;
387
	}
388
 
389
	// accesseurs à deux ronds
390
	protected function getUtilisateurId() {
391
		if ($utilisateur = $this->getUtilisateur()) {
392
			return $utilisateur['id'];
393
		} else {
394
			return '';
395
		}
396
	}
397
	protected function getUtilisateurLogin() {
398
		if ($utilisateur = $this->getUtilisateur()) {
399
			return $utilisateur['login'];
400
		} else {
401
			return '';
402
		}
403
	}
404
	protected function getUtilisateurNomComplet() {
405
		if ($utilisateur = $this->getUtilisateur()) {
406
			return $utilisateur['nom_complet'];
407
		} else {
408
			return '';
409
		}
410
	}
411
	protected function getUtilisateurPrenom() {
412
		if ($utilisateur = $this->getUtilisateur()) {
413
			return $utilisateur['prenom'];
414
		} else {
415
			return '';
416
		}
417
	}
418
	protected function getUtilisateurNom() {
419
		if ($utilisateur = $this->getUtilisateur()) {
420
			return $utilisateur['nom'];
421
		} else {
422
			return '';
423
		}
424
	}
425
	protected function getParametre() {
426
		if ($utilisateur = $this->getUtilisateur()) {
427
			return $utilisateur['parametre'];
428
		} else {
429
			return '';
430
		}
431
	}
432
	protected function getLicence()	{
433
		if (!empty($_SESSION['coel_utilisateur']))	{
434
			return (string) $_SESSION['coel_utilisateur']['licence'];
435
		} else {
436
			return '';
437
		}
438
	}
439
	protected function getInfosAnnuaire()	{
440
		if (!empty($_SESSION['coel_infosAnnuaire']))	{
441
			return $_SESSION['coel_infosAnnuaire'];
442
		} else	{
443
			return '';
444
		}
445
	}
446
	protected function setInfosAnnuaire($infosAnnuaire)	{
447
		$_SESSION['coel_infosAnnuaire'] = $infosAnnuaire;
448
	}
1497 jpm 449
}
1906 mathias 450
 
451
/**
452
 * Compatibilité avec nginx - merci http://php.net/manual/fr/function.getallheaders.php
453
 */
454
if (! function_exists('apache_request_headers')) {
455
	function apache_request_headers() {
456
		$headers = '';
457
		foreach ($_SERVER as $name => $value) {
458
			if (substr($name, 0, 5) == 'HTTP_') {
459
				$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
460
			}
461
		}
462
		return $headers;
463
	}
464
}
1497 jpm 465
?>