Subversion Repositories Applications.framework

Rev

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

Rev Author Line No. Line
260 gduche 1
<?php
263 gduche 2
/**
3
 * La classe OpenIdClient est une implémentation d'un client OpenId, depuis une classe Zend.
4
 * Elle permet d'établir une connexion avec un serveur, en fonction d'un identifiant OpenId.
5
 * Elle permet de communiquer de manière sécurisée avec ce serveur, et doit aboutir a une
6
 * identification centralisée.
7
 * */
8
 
260 gduche 9
class OpenIdClient {
10
 
11
	//OpenID 2.0 namespace. Tous les messages OpenID 2.0 DOIVENT contenir la variable openid.ns et sa valeur
12
    const NS_2_0 = 'http://specs.openid.net/auth/2.0';
13
 
14
 
263 gduche 15
    // TODO : remplacer _storage par une gestion par cache ?
260 gduche 16
	/**
263 gduche 17
     * Variable permettant le stockage d'informations, notammenent à propos des clés DiffieHellmann
18
     * @var Storage $_storage
260 gduche 19
     */
20
    protected $_storage = null;
21
 
22
    /**
263 gduche 23
     * Tableau "cache" interne permettant d'éviter des accès inutiles au fichier storage
260 gduche 24
     * @var array $_cache
25
     */
26
    protected $_cache = array();
263 gduche 27
 
28
    // Client pour les requetes.
260 gduche 29
    private $client;
30
 
31
	/**
32
	 * Constructeur de l'application
33
	 * */
34
	function __construct()	{
35
		$this->client = new Client();
36
		$this->_storage = new StorageFile();
37
	}
38
 
39
	/**
40
	 * Fonction login
41
	 *
42
	 * Return true ou false
43
	 * > Ne retourne rien si true car redirige vers l'adresse du serveur OID
44
	 * */
45
	//FIXME : le paramètre immediate ?
263 gduche 46
	// A vérifier mais ça doit permettre de passer directement le mot de passe. Il reste plus qu'à trouver le nom de la variable mot de passe.
260 gduche 47
 
48
	function login($id, $immediate = false)	{
49
 
263 gduche 50
		// L'original retourne la fonction checkId, avec le parametre immediate = true
260 gduche 51
		// Je ne comprends pas l'utilité, je fusionne les deux pour l'instant
263 gduche 52
		// FIXME : si pas de comportement étrange, valider.
260 gduche 53
 
263 gduche 54
 
260 gduche 55
		//Tests et arrêt si non validé :
56
			//Normaliser (traite si XRI ou URL, normalize URL)
57
			//FIXME : voir avec JP pour équivalent dans framework
58
			if (!$this->normalize($id))	{
59
				return false;
60
			}
61
 
62
			//Discovery
63
			// Récupérer les informations sur le serveur OPEN ID
64
			/*
65
			 FIXME : ca change la valeur de l'id !!!
66
			 if (!$this->_discovery($id, $server, $version)) {
67
		         trigger_error('Discovery failed');
68
		         return false;
69
		    }*/
70
 
71
		    $retour_url = $this->client->consulter($id);
72
		    //Le retour contient les balises suivantes :
73
			/*
74
			 *
75
			 * <link rel="openid.server" href="http://www.myopenid.com/server" />
76
			 * <link rel="openid2.provider" href="http://www.myopenid.com/server" />
77
			 */
78
			$metaServeur = $this->verifierVersion($retour_url);
79
 
80
			//TODO : Voir avec JP : la classe client ne permet pas de vérifer le statut ??
81
 
82
			if ($retour_url === false) {
83
				trigger_error('L\'adresse $id est inacessible', E_USER_ERROR);
84
				return false;
85
			}
86
			if ($metaServeur === false)	{
87
				return false;
88
			}
89
			if (!$this->_associate($metaServeur['serveur'], $metaServeur['version'])) {
90
            	trigger_error('Impossible de s\'associer avec le serveur');
91
        	}
92
 
93
 
94
        	/*TODO : fonctionnement différent
95
        	 if (!$this->_getAssociation(
96
                $server,
97
                $handle,
98
                $macFunc,
99
                $secret,
100
                $expires)) {
101
            /* Use dumb mode *
102
            unset($handle);
103
            unset($macFunc);
104
            unset($secret);
105
            unset($expires);*
106
        }*/
107
 
108
			//on a la version, l'adresse du serveur et le realId si c'est une 2.0 dans metaServeur
109
			if (isset($metaServeur['realId'])) {
110
				$id = $metaServeur['realId'];
111
			}
112
 
113
			//Associate
114
			//getAssociation
115
 
116
		//Organisation des paramètres :
117
		$params = array();
118
 
119
 
120
        if ($metaServeur['version'] >= 2.0) {
121
            $params['openid.ns'] = self::NS_2_0;
122
        }
123
 
124
        $params['openid.mode'] = $immediate ?
125
            'checkid_immediate' : 'checkid_setup';
126
 
127
        $params['openid.identity'] = $id;
128
 
129
        //FIXME : Ex : $params['openid.claimed_id'] = $claimedId; > jvois pas l'intéret
130
        $params['openid.claimed_id'] = $id;
131
        /*
132
         * TODO : gérer les sessions et namespace
133
         * if ($metaServeur['version'] <= 2.0) {
134
            if ($this->_session !== null) {
135
                $this->_session->identity = $id;
136
                $this->_session->claimed_id = $claimedId;
137
            } else if (defined('SID')) {
138
                $_SESSION["zend_openid"] = array(
139
                    "identity" => $id,
140
                    "claimed_id" => $claimedId);
141
            } else {
142
                require_once "Zend/Session/Namespace.php";
143
                $this->_session = new Zend_Session_Namespace("zend_openid");
144
                $this->_session->identity = $id;
145
                $this->_session->claimed_id = $claimedId;
146
            }
147
        }*/
148
 
149
        if (isset($handle)) {
150
            $params['openid.assoc_handle'] = $handle;
151
        }
152
 
153
        //FIXME : $params['openid.return_to'] = $this->absoluteUrl($returnTo);
154
        $params['openid.return_to'] = $this->absoluteUrl(null);
155
 
156
        if (empty($root)) {
157
            $root = $this->selfUrl();
158
            if ($root[strlen($root)-1] != '/') {
159
                $root = dirname($root);
160
            }
161
        }
162
        if ($metaServeur['version'] >= 2.0) {
163
            $params['openid.realm'] = $root;
164
        } else {
165
            $params['openid.trust_root'] = $root;
166
        }
167
 
168
        /*FIXME ::
169
 
170
         if (!Zend_OpenId_Extension::forAll($extensions, 'prepareRequest', $params)) {
171
            $this->_setError("Extension::prepareRequest failure");
172
            return false;
173
        }*/
174
 
175
        $this->redirect($metaServeur['serveur'], $params);
176
        return true;
177
		//Renvoyer vers l'url
178
	}
179
 
180
	/**
181
     * Verifies authentication response from OpenID server.
182
     *
183
     * This is the second step of OpenID authentication process.
184
     * The function returns true on successful authentication and false on
185
     * failure.
186
     *
187
     * @param array $params HTTP query data from OpenID server
188
     * @param string &$identity this argument is set to end-user's claimed
189
     *  identifier or OpenID provider local identifier.
190
     * @param mixed $extensions extension object or array of extensions objects
191
     * @return bool
192
     */
193
    public function verify($params, &$identity = "", $extensions = null)
194
    {
195
 
196
    	echo '<pre>'.print_r($params, true).'</pre>';
197
 
198
        $version = 1.1;
199
        if (isset($params['openid_ns']) &&
200
            $params['openid_ns'] == $this->NS_2_0) {
201
            $version = 2.0;
202
        }
203
 
204
        if (isset($params["openid_claimed_id"])) {
205
            $identity = $params["openid_claimed_id"];
206
        } else if (isset($params["openid_identity"])){
207
            $identity = $params["openid_identity"];
208
        } else {
209
            $identity = "";
210
        }
211
 
212
        if ($version < 2.0 && !isset($params["openid_claimed_id"])) {
213
            if ($this->_session !== null) {
214
                if ($this->_session->identity === $identity) {
215
                    $identity = $this->_session->claimed_id;
216
                }
217
            } else if (defined('SID')) {
218
                if (isset($_SESSION["zend_openid"]["identity"]) &&
219
                    isset($_SESSION["zend_openid"]["claimed_id"]) &&
220
                    $_SESSION["zend_openid"]["identity"] === $identity) {
221
                    $identity = $_SESSION["zend_openid"]["claimed_id"];
222
                }
223
            } else {
224
                require_once "Zend/Session/Namespace.php";
225
                $this->_session = new Zend_Session_Namespace("zend_openid");
226
                if ($this->_session->identity === $identity) {
227
                    $identity = $this->_session->claimed_id;
228
                }
229
            }
230
        }
231
 
232
        if (empty($params['openid_mode'])) {
233
            $this->_setError("Missing openid.mode");
234
            return false;
235
        }
236
        if (empty($params['openid_return_to'])) {
237
            $this->_setError("Missing openid.return_to");
238
            return false;
239
        }
240
        if (empty($params['openid_signed'])) {
241
            $this->_setError("Missing openid.signed");
242
            return false;
243
        }
244
        if (empty($params['openid_sig'])) {
245
            $this->_setError("Missing openid.sig");
246
            return false;
247
        }
248
        if ($params['openid_mode'] != 'id_res') {
249
            $this->_setError("Wrong openid.mode '".$params['openid_mode']."' != 'id_res'");
250
            return false;
251
        }
252
        if (empty($params['openid_assoc_handle'])) {
253
            $this->_setError("Missing openid.assoc_handle");
254
            return false;
255
        }
256
 
257
        if ($params['openid_return_to'] != $this->selfUrl()) {
258
            /* Ignore query part in openid.return_to */
259
            $pos = strpos($params['openid_return_to'], '?');
260
            if ($pos === false ||
261
                SUBSTR($params['openid_return_to'], 0 , $pos) != $this->selfUrl()) {
262
 
263
                /*$this->_setError("Wrong openid.return_to '".
264
                    $params['openid_return_to']."' != '" . $this->selfUrl() ."'");*/
265
                trigger_error('Wrong openid.return_to', E_USER_ERROR);
266
 
267
                return false;
268
            }
269
        }
270
 
271
        if ($version >= 2.0) {
272
            if (empty($params['openid_response_nonce'])) {
273
                trigger_error('Missing openid.response_nonce', E_USER_ERROR);
274
                return false;
275
            }
276
            if (empty($params['openid_op_endpoint'])) {
277
                trigger_error('Missing openid.op_endpoint', E_USER_ERROR);
278
                return false;
279
            /* OpenID 2.0 (11.3) Checking the Nonce */
280
            } else if (!$this->_storage->isUniqueNonce($params['openid_op_endpoint'], $params['openid_response_nonce'])) {
281
                trigger_error('Duplicate openid.response_nonce', E_USER_ERROR);
282
                return false;
283
            }
284
        }
285
 
286
        if (!empty($params['openid_invalidate_handle'])) {
287
            if ($this->_storage->getAssociationByHandle(
288
                $params['openid_invalidate_handle'],
289
                $url,
290
                $macFunc,
291
                $secret,
292
                $expires)) {
293
                $this->_storage->delAssociation($url);
294
            }
295
        }
296
 
297
        if ($this->_storage->getAssociationByHandle(
298
                $params['openid_assoc_handle'],
299
                $url,
300
                $macFunc,
301
                $secret,
302
                $expires)) {
303
            $signed = explode(',', $params['openid_signed']);
304
            $data = '';
305
            foreach ($signed as $key) {
306
                $data .= $key . ':' . $params['openid_' . strtr($key,'.','_')] . "\n";
307
            }
308
            if (base64_decode($params['openid_sig']) ==
309
                Zend_OpenId::hashHmac($macFunc, $data, $secret)) {
310
                /*
311
                 * FIXME dépendance je sais pas pour quoi : a voir :
312
                 * if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) {
313
                    $this->_setError("Extension::parseResponse failure");
314
                    return false;
315
                }*/
316
                /* OpenID 2.0 (11.2) Verifying Discovered Information */
317
                if (isset($params['openid_claimed_id'])) {
318
                    $id = $params['openid_claimed_id'];
319
                    if (!$this->normalize($id)) {
320
                        $this->_setError("Normalization failed");
321
                        return false;
322
                    } else if (!$this->_discovery($id, $discovered_server, $discovered_version)) {
323
                        $this->_setError("Discovery failed: " . $this->getError());
324
                        return false;
325
                    } else if ((!empty($params['openid_identity']) &&
326
                                $params["openid_identity"] != $id) ||
327
                               (!empty($params['openid_op_endpoint']) &&
328
                                $params['openid_op_endpoint'] != $discovered_server) ||
329
                               $discovered_version != $version) {
330
                        $this->_setError("Discovery information verification failed");
331
                        return false;
332
                    }
333
                }
334
                return true;
335
            }
336
            $this->_storage->delAssociation($url);
337
            $this->_setError("Signature check failed");
338
            return false;
339
        }
340
        else
341
        {
342
            /* Use dumb mode */
343
            if (isset($params['openid_claimed_id'])) {
344
                $id = $params['openid_claimed_id'];
345
            } else if (isset($params['openid_identity'])) {
346
                $id = $params['openid_identity'];
347
            } else {
348
                $this->_setError("Missing openid.claimed_id and openid.identity");
349
                return false;
350
            }
351
 
352
            if (!$this->normalize($id)) {
353
              	trigger_error('Normalization failed', E_USER_ERROR);
354
                return false;
355
            } else if (!$this->_discovery($id, $server, $discovered_version)) {
356
                trigger_error('Discovery failed', E_USER_ERROR);
357
                return false;
358
            }
359
 
360
            /* OpenID 2.0 (11.2) Verifying Discovered Information */
361
            if ((isset($params['openid_identity']) &&
362
                 $params["openid_identity"] != $id) ||
363
                (isset($params['openid_op_endpoint']) &&
364
                 $params['openid_op_endpoint'] != $server) ||
365
                $discovered_version != $version) {
366
                trigger_error('Discovery information verification failed', E_USER_ERROR);
367
                return false;
368
            }
369
 
370
            $params2 = array();
371
            foreach ($params as $key => $val) {
372
                if (strpos($key, 'openid_ns_') === 0) {
373
                    $key = 'openid.ns.' . substr($key, strlen('openid_ns_'));
374
                } else if (strpos($key, 'openid_sreg_') === 0) {
375
                    $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_'));
376
                } else if (strpos($key, 'openid_') === 0) {
377
                    $key = 'openid.' . substr($key, strlen('openid_'));
378
                }
379
                $params2[$key] = $val;
380
            }
381
            $params2['openid.mode'] = 'check_authentication';
382
            $ret = $this->client->modifier($serveur, $params2);
383
 
384
            //_httpRequest($server, 'POST', $params2, $status);
385
            if ($ret === false) {
386
                trigger_error("'Dumb' signature verification HTTP request failed", E_USER_ERROR);
387
                return false;
388
            }
389
            $r = array();
390
            if (is_string($ret)) {
391
                foreach(explode("\n", $ret) as $line) {
392
                    $line = trim($line);
393
                    if (!empty($line)) {
394
                        $x = explode(':', $line, 2);
395
                        if (is_array($x) && count($x) == 2) {
396
                            list($key, $value) = $x;
397
                            $r[trim($key)] = trim($value);
398
                        }
399
                    }
400
                }
401
            }
402
            $ret = $r;
403
            if (!empty($ret['invalidate_handle'])) {
404
                if ($this->_storage->getAssociationByHandle(
405
                    $ret['invalidate_handle'],
406
                    $url,
407
                    $macFunc,
408
                    $secret,
409
                    $expires)) {
410
                    $this->_storage->delAssociation($url);
411
                }
412
            }
413
            if (isset($ret['is_valid']) && $ret['is_valid'] == 'true') {
414
                if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) {
415
                    $this->_setError("Extension::parseResponse failure");
416
                    return false;
417
                }
418
                return true;
419
            }
420
            $this->_setError("'Dumb' signature verification failed");
421
            return false;
422
        }
423
    }
424
 
425
 
426
	/**
427
     * Performs discovery of identity and finds OpenID URL, OpenID server URL
428
     * and OpenID protocol version. Returns true on succees and false on
429
     * failure.
430
     *
431
     * @param string &$id OpenID identity URL
432
     * @param string &$server OpenID server URL
433
     * @param float &$version OpenID protocol version
434
     * @return bool
435
     * @todo OpenID 2.0 (7.3) XRI and Yadis discovery
436
     */
437
    protected function _discovery(&$id, &$server, &$version)
438
    {
439
        $realId = $id;
440
        if ($this->_storage->getDiscoveryInfo(
441
                $id,
442
                $realId,
443
                $server,
444
                $version,
445
                $expire)) {
446
            $id = $realId;
447
            return true;
448
        }
449
 
450
        /* TODO: OpenID 2.0 (7.3) XRI and Yadis discovery */
451
 
452
        /* HTML-based discovery */
453
        $clientDiscovery = new Client();
454
 
455
        //TODO : rajouter un test sur le statut de la réponse
456
        // Nécessite la prise en compte des entetes dans le framework
457
 
458
        /*if ($status != 200 || !is_string($response)) {
459
            return false;
460
        }*/
461
        $reponse = $clientDiscovery->consulter($id);
462
        $metaServeur = $this->verifierVersion($reponse);
463
        if (!isset($metaServeur) || empty($reponse))	{
464
        	trigger_error('Aucune donnée OpenId', E_USER_ERROR);
465
        	return false;
466
        }
467
 
468
        $expire = time() + 60 * 60;
469
        $this->_storage->addDiscoveryInfo($id, $metaServeur['realId'], $metaServeur['serveur'], $metaServeur['version'], $expire);
470
        if (!empty($metaServeur['realId'])) {
471
        	$id = $metaServeur['realId'];
472
        }
473
        return true;
474
    }
475
 
476
 
477
 
478
 
479
	//Parser l'HTML de réponse pour trouver la version du serveur OPEN ID
480
	function verifierVersion($reponseHtml)	{
481
 
482
		// TODO : remplacer l'arlgorythme suivant par cette solution :
483
		//1. Chercher l'existence d'une balise openidN.provider
484
		//2. Déterminer la version en fonction de la chaine : openid2.provider => 2.0; openid.provider => 1.1
485
		//3. Récupérer l'url du serveur href="serveur"
486
		//4. SI 2.0, récupérer la valeur réelle de l'ID
487
 
488
		//TODO : penser à tester les deux versions du serveur
489
 
490
		$metaServeur = Array();
491
 
492
		if (preg_match(
493
                '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
494
                $reponseHtml,
495
                $r)) {
496
            $metaServeur['version'] = 2.0;
497
            $metaServeur['serveur'] = $r[3];
498
        } else if (preg_match(
499
                '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\3[^>]*\/?>/i',
500
                $reponseHtml,
501
                $r)) {
502
            $metaServeur['version'] = 2.0;
503
            $metaServeur['serveur'] = $r[1];
504
        } else if (preg_match(
505
                '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
506
                $reponseHtml,
507
                $r)) {
508
            $metaServeur['version'] = 1.1;
509
            $metaServeur['serveur'] = $r[3];
510
        } else if (preg_match(
511
                '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\3[^>]*\/?>/i',
512
                $reponseHtml,
513
                $r)) {
514
            $metaServeur['version'] = 1.1;
515
            $metaServeur['serveur'] = $r[2];
516
        } else {
517
            return false;
518
        }
519
 
520
        if ($metaServeur['version'] >= 2.0) {
521
            if (preg_match(
522
                    '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
523
                    $reponseHtml,
524
                    $r)) {
525
                $metaServeur['realId'] = $r[3];
526
            } else if (preg_match(
527
                    '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\3[^>]*\/?>/i',
528
                    $reponseHtml,
529
                    $r)) {
530
                $metaServeur['realId'] = $r[2];
531
            }
532
        } else {
533
            if (preg_match(
534
                    '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
535
                    $reponseHtml,
536
                    $r)) {
537
                $metaServeur['realId'] = $r[3];
538
            } else if (preg_match(
539
                    '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\3[^>]*\/?>/i',
540
                    $reponseHtml,
541
                    $r)) {
542
                $metaServeur['realId'] = $r[2];
543
            }
544
        }
545
 
546
        return $metaServeur;
547
	}
548
 
549
	/**
550
     * Create (or reuse existing) association between OpenID consumer and
551
     * OpenID server based on Diffie-Hellman key agreement. Returns true
552
     * on success and false on failure.
553
     *
554
     * @param string $url OpenID server url
555
     * @param float $version OpenID protocol version
556
     * @param string $priv_key for testing only
557
     * @return bool
558
     */
559
    protected function _associate($url, $version, $priv_key=null)
560
    {
561
        /* Check if we already have association in chace or storage */
562
       /*
563
        * TODO : Utiliser le stockage plutot
564
        * */
565
        if ($this->_getAssociation(
566
                $url,
567
                $handle,
568
                $macFunc,
569
                $secret,
570
                $expires)) {
571
            return true;
572
        }
573
 
574
       /*
575
        * TODO : utiliser le fichier de config
576
        * if ($this->_dumbMode) {
577
	       return true;
578
       }*/
579
 
580
        $params = array();
581
 
582
        if ($version >= 2.0) {
583
            $params = array(
584
                'openid.ns'           => $this->NS_2_0,
585
                'openid.mode'         => 'associate',
586
                'openid.assoc_type'   => 'HMAC-SHA256',
587
                'openid.session_type' => 'DH-SHA256',
588
            );
589
        } else {
590
            $params = array(
591
                'openid.mode'         => 'associate',
592
                'openid.assoc_type'   => 'HMAC-SHA1',
593
                'openid.session_type' => 'DH-SHA1',
594
            );
595
        }
596
 
597
        $dh = DiffieHellmanUtil::createDhKey(pack('H*', DiffieHellmanUtil::DH_P),
598
                                       pack('H*', DiffieHellmanUtil::DH_G),
599
                                       $priv_key);
600
        $dh_details = DiffieHellmanUtil::getDhKeyDetails($dh);
601
 
602
        $params['openid.dh_modulus']         = base64_encode(
603
            DiffieHellmanUtil::btwoc($dh_details['p']));
604
        $params['openid.dh_gen']             = base64_encode(
605
            DiffieHellmanUtil::btwoc($dh_details['g']));
606
        $params['openid.dh_consumer_public'] = base64_encode(
607
            DiffieHellmanUtil::btwoc($dh_details['pub_key']));
608
 
609
        while(1) {
610
        	//FIXME : c'est pas une modification ...
611
            $ret = $this->client->modifier($url, $params); // FIXME : a quoi sert status ?, $status);
612
            if ($ret === false) {
613
                //$this->_setError("HTTP request failed");
614
                trigger_error('La requête a échoué', E_USER_ERROR);
615
                return false;
616
            }
617
 
618
            $r = array();
619
            $bad_response = false;
620
            foreach(explode("\n", $ret) as $line) {
621
                $line = trim($line);
622
                if (!empty($line)) {
623
                    $x = explode(':', $line, 2);
624
                    if (is_array($x) && count($x) == 2) {
625
                        list($key, $value) = $x;
626
                        $r[trim($key)] = trim($value);
627
                    } else {
628
                        $bad_response = true;
629
                    }
630
                }
631
            }
632
            if ($bad_response && strpos($ret, 'Unknown session type') !== false) {
633
                $r['error_code'] = 'unsupported-type';
634
            }
635
            $ret = $r;
636
 
637
            if (isset($ret['error_code']) &&
638
                $ret['error_code'] == 'unsupported-type') {
639
                if ($params['openid.session_type'] == 'DH-SHA256') {
640
                    $params['openid.session_type'] = 'DH-SHA1';
641
                    $params['openid.assoc_type'] = 'HMAC-SHA1';
642
                } else if ($params['openid.session_type'] == 'DH-SHA1') {
643
                    $params['openid.session_type'] = 'no-encryption';
644
                } else {
645
                    trigger_error("The OpenID service responded with: " . $ret['error_code'], E_USER_ERROR);
646
                    return false;
647
                }
648
            } else {
649
                break;
650
            }
651
        }
652
 
653
        /*
654
        FIXME : gestion du statut avec la classe client ??
655
        if ($status != 200) {
656
            $this->_setError("The server responded with status code: " . $status);
657
            return false;
658
        }*/
659
 
660
        if ($version >= 2.0 &&
661
            isset($ret['ns']) &&
662
            $ret['ns'] != $this->NS_2_0) {
663
            $this->_setError("Wrong namespace definition in the server response");
664
            return false;
665
        }
666
 
667
        if (!isset($ret['assoc_handle']) ||
668
            !isset($ret['expires_in']) ||
669
            !isset($ret['assoc_type']) ||
670
            $params['openid.assoc_type'] != $ret['assoc_type']) {
671
            if ($params['openid.assoc_type'] != $ret['assoc_type']) {
672
                $this->_setError("The returned assoc_type differed from the supplied openid.assoc_type");
673
            } else {
674
                $this->_setError("Missing required data from provider (assoc_handle, expires_in, assoc_type are required)");
675
            }
676
            return false;
677
        }
678
 
679
        $handle     = $ret['assoc_handle'];
680
        $expiresIn = $ret['expires_in'];
681
 
682
        if ($ret['assoc_type'] == 'HMAC-SHA1') {
683
            $macFunc = 'sha1';
684
        } else if ($ret['assoc_type'] == 'HMAC-SHA256' &&
685
            $version >= 2.0) {
686
            $macFunc = 'sha256';
687
        } else {
688
            $this->_setError("Unsupported assoc_type");
689
            return false;
690
        }
691
 
692
        if ((empty($ret['session_type']) ||
693
             ($version >= 2.0 && $ret['session_type'] == 'no-encryption')) &&
694
             isset($ret['mac_key'])) {
695
            $secret = base64_decode($ret['mac_key']);
696
        } else if (isset($ret['session_type']) &&
697
            $ret['session_type'] == 'DH-SHA1' &&
698
            !empty($ret['dh_server_public']) &&
699
            !empty($ret['enc_mac_key'])) {
700
            $dhFunc = 'sha1';
701
        } else if (isset($ret['session_type']) &&
702
            $ret['session_type'] == 'DH-SHA256' &&
703
            $version >= 2.0 &&
704
            !empty($ret['dh_server_public']) &&
705
            !empty($ret['enc_mac_key'])) {
706
            $dhFunc = 'sha256';
707
        } else {
708
            $this->_setError("Unsupported session_type");
709
            return false;
710
        }
711
        if (isset($dhFunc)) {
712
            $serverPub = base64_decode($ret['dh_server_public']);
713
            $dhSec = DiffieHellmanUtil::computeDhSecret($serverPub, $dh);
714
            if ($dhSec === false) {
715
                $this->_setError("DH secret comutation failed");
716
                return false;
717
            }
718
            $sec = $this->digest($dhFunc, $dhSec);
719
            if ($sec === false) {
720
                $this->_setError("Could not create digest");
721
                return false;
722
            }
723
            $secret = $sec ^ base64_decode($ret['enc_mac_key']);
724
        }
725
        if ($macFunc == 'sha1') {
726
            if (DiffieHellmanUtil::strlen($secret) != 20) {
727
                $this->_setError("The length of the sha1 secret must be 20");
728
                return false;
729
            }
730
        } else if ($macFunc == 'sha256') {
731
            if (DiffieHellmanUtil::strlen($secret) != 32) {
732
                $this->_setError("The length of the sha256 secret must be 32");
733
                return false;
734
            }
735
        }
736
 
737
        $this->_addAssociation(
738
            $url,
739
            $handle,
740
            $macFunc,
741
            $secret,
742
            time() + $expiresIn);
743
       /* $this->association['url'] = $url;
744
        $this->association['handle'] = $handle;
745
        $this->association['macFunc'] = $macFunc;
746
        $this->association['secret'] = $secret;
747
        $this->association['expiresIn'] = time() + $expiresIn;*/
748
 
749
        return true;
750
    }
751
 
752
	/**
753
     * Store assiciation in internal chace and external storage
754
     *
755
     * @param string $url OpenID server url
756
     * @param string $handle association handle
757
     * @param string $macFunc HMAC function (sha1 or sha256)
758
     * @param string $secret shared secret
759
     * @param integer $expires expiration UNIX time
760
     * @return void
761
     */
762
    protected function _addAssociation($url, $handle, $macFunc, $secret, $expires)
763
    {
764
        $this->_cache[$url] = array($handle, $macFunc, $secret, $expires);
765
        return $this->_storage->addAssociation(
766
            $url,
767
            $handle,
768
            $macFunc,
769
            $secret,
770
            $expires);
771
    }
772
 
773
	/**
774
     * Retrive assiciation information for given $url from internal cahce or
775
     * external storage
776
     *
777
     * @param string $url OpenID server url
778
     * @param string &$handle association handle
779
     * @param string &$macFunc HMAC function (sha1 or sha256)
780
     * @param string &$secret shared secret
781
     * @param integer &$expires expiration UNIX time
782
     * @return void
783
     */
784
    protected function _getAssociation($url, &$handle, &$macFunc, &$secret, &$expires)
785
    {
786
        if (isset($this->_cache[$url])) {
787
            $handle   = $this->_cache[$url][0];
788
            $macFunc = $this->_cache[$url][1];
789
            $secret   = $this->_cache[$url][2];
790
            $expires  = $this->_cache[$url][3];
791
            return true;
792
        }
793
        if ($this->_storage->getAssociation(
794
                $url,
795
                $handle,
796
                $macFunc,
797
                $secret,
798
                $expires)) {
799
            $this->_cache[$url] = array($handle, $macFunc, $secret, $expires);
800
            return true;
801
        }
802
        return false;
803
    }
804
 
805
	/**
806
     * Normalizes URL according to RFC 3986 to use it in comparison operations.
807
     * The function gets URL argument by reference and modifies it.
808
     * It returns true on success and false of failure.
809
     *
810
     * @param string &$id url to be normalized
811
     * @return bool
812
     */
813
    static public function normalizeUrl(&$id)
814
    {
815
        // RFC 3986, 6.2.2.  Syntax-Based Normalization
816
 
817
        // RFC 3986, 6.2.2.2 Percent-Encoding Normalization
818
        $i = 0;
819
        $n = strlen($id);
820
        $res = '';
821
        while ($i < $n) {
822
            if ($id[$i] == '%') {
823
                if ($i + 2 >= $n) {
824
                    return false;
825
                }
826
                ++$i;
827
                if ($id[$i] >= '0' && $id[$i] <= '9') {
828
                    $c = ord($id[$i]) - ord('0');
829
                } else if ($id[$i] >= 'A' && $id[$i] <= 'F') {
830
                    $c = ord($id[$i]) - ord('A') + 10;
831
                } else if ($id[$i] >= 'a' && $id[$i] <= 'f') {
832
                    $c = ord($id[$i]) - ord('a') + 10;
833
                } else {
834
                    return false;
835
                }
836
                ++$i;
837
                if ($id[$i] >= '0' && $id[$i] <= '9') {
838
                    $c = ($c << 4) | (ord($id[$i]) - ord('0'));
839
                } else if ($id[$i] >= 'A' && $id[$i] <= 'F') {
840
                    $c = ($c << 4) | (ord($id[$i]) - ord('A') + 10);
841
                } else if ($id[$i] >= 'a' && $id[$i] <= 'f') {
842
                    $c = ($c << 4) | (ord($id[$i]) - ord('a') + 10);
843
                } else {
844
                    return false;
845
                }
846
                ++$i;
847
                $ch = chr($c);
848
                if (($ch >= 'A' && $ch <= 'Z') ||
849
                    ($ch >= 'a' && $ch <= 'z') ||
850
                    $ch == '-' ||
851
                    $ch == '.' ||
852
                    $ch == '_' ||
853
                    $ch == '~') {
854
                    $res .= $ch;
855
                } else {
856
                    $res .= '%';
857
                    if (($c >> 4) < 10) {
858
                        $res .= chr(($c >> 4) + ord('0'));
859
                    } else {
860
                        $res .= chr(($c >> 4) - 10 + ord('A'));
861
                    }
862
                    $c = $c & 0xf;
863
                    if ($c < 10) {
864
                        $res .= chr($c + ord('0'));
865
                    } else {
866
                        $res .= chr($c - 10 + ord('A'));
867
                    }
868
                }
869
            } else {
870
                $res .= $id[$i++];
871
            }
872
        }
873
 
874
        if (!preg_match('|^([^:]+)://([^:@]*(?:[:][^@]*)?@)?([^/:@?#]*)(?:[:]([^/?#]*))?(/[^?#]*)?((?:[?](?:[^#]*))?)((?:#.*)?)$|', $res, $reg)) {
875
            return false;
876
        }
877
        $scheme = $reg[1];
878
        $auth = $reg[2];
879
        $host = $reg[3];
880
        $port = $reg[4];
881
        $path = $reg[5];
882
        $query = $reg[6];
883
        $fragment = $reg[7]; /* strip it */
884
 
885
        if (empty($scheme) || empty($host)) {
886
            return false;
887
        }
888
 
889
        // RFC 3986, 6.2.2.1.  Case Normalization
890
        $scheme = strtolower($scheme);
891
        $host = strtolower($host);
892
 
893
        // RFC 3986, 6.2.2.3.  Path Segment Normalization
894
        if (!empty($path)) {
895
            $i = 0;
896
            $n = strlen($path);
897
            $res = "";
898
            while ($i < $n) {
899
                if ($path[$i] == '/') {
900
                    ++$i;
901
                    while ($i < $n && $path[$i] == '/') {
902
                        ++$i;
903
                    }
904
                    if ($i < $n && $path[$i] == '.') {
905
                        ++$i;
906
                        if ($i < $n && $path[$i] == '.') {
907
                            ++$i;
908
                            if ($i == $n || $path[$i] == '/') {
909
                                if (($pos = strrpos($res, '/')) !== false) {
910
                                    $res = substr($res, 0, $pos);
911
                                }
912
                            } else {
913
                                    $res .= '/..';
914
                            }
915
                        } else if ($i != $n && $path[$i] != '/') {
916
                            $res .= '/.';
917
                        }
918
                    } else {
919
                        $res .= '/';
920
                    }
921
                } else {
922
                    $res .= $path[$i++];
923
                }
924
            }
925
            $path = $res;
926
        }
927
 
928
        // RFC 3986,6.2.3.  Scheme-Based Normalization
929
        if ($scheme == 'http') {
930
            if ($port == 80) {
931
                $port = '';
932
            }
933
        } else if ($scheme == 'https') {
934
            if ($port == 443) {
935
                $port = '';
936
            }
937
        }
938
        if (empty($path)) {
939
            $path = '/';
940
        }
941
 
942
        $id = $scheme
943
            . '://'
944
            . $auth
945
            . $host
946
            . (empty($port) ? '' : (':' . $port))
947
            . $path
948
            . $query;
949
        return true;
950
    }
951
 
952
 
953
    /**
954
     * Normaliser l'identifiant OpenId qui peut être une URL ou nom XRI
955
     * Retourne true ou false en cas d'erreur.
956
     *
957
     * Règles de normalisation :
958
     * 1. If the user's input starts with one of the "xri://", "xri://$ip*",
959
     *    or "xri://$dns*" prefixes, they MUST be stripped off, so that XRIs
960
     *    are used in the canonical form, and URI-authority XRIs are further
961
     *    considered URL identifiers.
962
     * 2. If the first character of the resulting string is an XRI Global
963
     *    Context Symbol ("=", "@", "+", "$", "!"), then the input SHOULD be
964
     *    treated as an XRI.
965
     * 3. Otherwise, the input SHOULD be treated as an http URL; if it does
966
     *    not include a "http" or "https" scheme, the Identifier MUST be
967
     *    prefixed with the string "http://".
968
     * 4. URL identifiers MUST then be further normalized by both following
969
     *    redirects when retrieving their content and finally applying the
970
     *    rules in Section 6 of [RFC3986] to the final destination URL.
971
     * @param string &$id identifier to be normalized
972
     * @return bool
973
     */
974
    static public function normalize(&$id)
975
    {
976
        $id = trim($id);
977
        if (strlen($id) === 0) {
978
            return true;
979
        }
980
 
981
        // 7.2.1
982
        if (strpos($id, 'xri://$ip*') === 0) {
983
            $id = substr($id, strlen('xri://$ip*'));
984
        } else if (strpos($id, 'xri://$dns*') === 0) {
985
            $id = substr($id, strlen('xri://$dns*'));
986
        } else if (strpos($id, 'xri://') === 0) {
987
            $id = substr($id, strlen('xri://'));
988
        }
989
 
990
        // 7.2.2
991
        if ($id[0] == '=' ||
992
            $id[0] == '@' ||
993
            $id[0] == '+' ||
994
            $id[0] == '$' ||
995
            $id[0] == '!') {
996
            return true;
997
        }
998
 
999
        // 7.2.3
1000
        if (strpos($id, "://") === false) {
1001
            $id = 'http://' . $id;
1002
        }
1003
 
1004
        // 7.2.4
1005
        return self::normalizeURL($id);
1006
    }
1007
 
1008
	/**
1009
     * Generates a hash value (message digest) according to given algorithm.
1010
     * It returns RAW binary string.
1011
     *
1012
     * This is a wrapper function that uses one of available internal function
1013
     * dependent on given PHP configuration. It may use various functions from
1014
     *  ext/openssl, ext/hash, ext/mhash or ext/standard.
1015
     *
1016
     * @param string $func digest algorithm
1017
     * @param string $data data to sign
1018
     * @return string RAW digital signature
1019
     * @throws Zend_OpenId_Exception
1020
     */
1021
    public function digest($func, $data)
1022
    {
1023
        if (function_exists('openssl_digest')) {
1024
            return openssl_digest($data, $func, true);
1025
        } else if (function_exists('hash')) {
1026
            return hash($func, $data, true);
1027
        } else if ($func === 'sha1') {
1028
            return sha1($data, true);
1029
        } else if ($func === 'sha256') {
1030
            if (function_exists('mhash')) {
1031
                return mhash(MHASH_SHA256 , $data);
1032
            }
1033
        }
1034
        /*require_once "Zend/OpenId/Exception.php";
1035
        throw new Zend_OpenId_Exception(
1036
            'Unsupported digest algorithm "' . $func . '".',
1037
            Zend_OpenId_Exception::UNSUPPORTED_DIGEST);*/
1038
        trigger_error('Unsupported digest algorithm '.$func , E_USER_ERROR);
1039
    }
1040
 
1041
 
1042
 
1043
 
1044
 
1045
 
1046
 
1047
 
1048
 	/**
1049
     * Returns a full URL that was requested on current HTTP request.
1050
     *
1051
     * @return string
1052
     */
1053
    public function selfUrl()
1054
    {
1055
        /*FIXME :
1056
         * if ($this->$selfUrl !== null) {
1057
            return $this->$selfUrl;
1058
        } */
1059
 
1060
        if (isset($_SERVER['SCRIPT_URI'])) {
1061
            return $_SERVER['SCRIPT_URI'];
1062
        }
1063
        $url = '';
1064
        $port = '';
1065
        if (isset($_SERVER['HTTP_HOST'])) {
1066
            if (($pos = strpos($_SERVER['HTTP_HOST'], ':')) === false) {
1067
                if (isset($_SERVER['SERVER_PORT'])) {
1068
                    $port = ':' . $_SERVER['SERVER_PORT'];
1069
                }
1070
                $url = $_SERVER['HTTP_HOST'];
1071
            } else {
1072
                $url = substr($_SERVER['HTTP_HOST'], 0, $pos);
1073
                $port = substr($_SERVER['HTTP_HOST'], $pos);
1074
            }
1075
        } else if (isset($_SERVER['SERVER_NAME'])) {
1076
            $url = $_SERVER['SERVER_NAME'];
1077
            if (isset($_SERVER['SERVER_PORT'])) {
1078
                $port = ':' . $_SERVER['SERVER_PORT'];
1079
            }
1080
        }
1081
        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
1082
            $url = 'https://' . $url;
1083
            if ($port == ':443') {
1084
                $port = '';
1085
            }
1086
        } else {
1087
            $url = 'http://' . $url;
1088
            if ($port == ':80') {
1089
                $port = '';
1090
            }
1091
        }
1092
 
1093
        $url .= $port;
1094
        if (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
1095
            $url .= $_SERVER['HTTP_X_REWRITE_URL'];
1096
        } elseif (isset($_SERVER['REQUEST_URI'])) {
1097
            $query = strpos($_SERVER['REQUEST_URI'], '?');
1098
            if ($query === false) {
1099
                $url .= $_SERVER['REQUEST_URI'];
1100
            } else {
1101
                $url .= substr($_SERVER['REQUEST_URI'], 0, $query);
1102
            }
1103
        } else if (isset($_SERVER['SCRIPT_URL'])) {
1104
            $url .= $_SERVER['SCRIPT_URL'];
1105
        } else if (isset($_SERVER['REDIRECT_URL'])) {
1106
            $url .= $_SERVER['REDIRECT_URL'];
1107
        } else if (isset($_SERVER['PHP_SELF'])) {
1108
            $url .= $_SERVER['PHP_SELF'];
1109
        } else if (isset($_SERVER['SCRIPT_NAME'])) {
1110
            $url .= $_SERVER['SCRIPT_NAME'];
1111
            if (isset($_SERVER['PATH_INFO'])) {
1112
                $url .= $_SERVER['PATH_INFO'];
1113
            }
1114
        }
1115
        return $url;
1116
    }
1117
 
1118
 
1119
    //TODO : vérifier si les fonctions FWK & ZEND sont bien équivalente
1120
	/**
1121
     * Retourne l'url absolue d'une url donnée
1122
     *
1123
     * @param string $url absilute or relative URL
1124
     * @return string
1125
     */
1126
    public function absoluteUrl($url)
1127
    {
1128
    	if (!empty($ur))	{
1129
    		$urlAbsolue = new Url($url);
1130
    		$urlAbsolue->normaliser();
1131
    		$url = $urlAbsolue->getUrl();
1132
    	} else {
1133
    		$url = $this->selfUrl();
1134
    	}
1135
 
1136
    	return $url;
1137
    	/*
1138
        if (empty($url)) {
1139
            return $this->selfUrl();
1140
        } else if (!preg_match('|^([^:]+)://|', $url)) {
1141
            if (preg_match('|^([^:]+)://([^:@]*(?:[:][^@]*)?@)?([^/:@?#]*)(?:[:]([^/?#]*))?(/[^?]*)?((?:[?](?:[^#]*))?(?:#.*)?)$|', $this->selfUrl(), $reg)) {
1142
                $scheme = $reg[1];
1143
                $auth = $reg[2];
1144
                $host = $reg[3];
1145
                $port = $reg[4];
1146
                $path = $reg[5];
1147
                $query = $reg[6];
1148
                if ($url[0] == '/') {
1149
                    return $scheme
1150
                        . '://'
1151
                        . $auth
1152
                        . $host
1153
                        . (empty($port) ? '' : (':' . $port))
1154
                        . $url;
1155
                } else {
1156
                    $dir = dirname($path);
1157
                    return $scheme
1158
                        . '://'
1159
                        . $auth
1160
                        . $host
1161
                        . (empty($port) ? '' : (':' . $port))
1162
                        . (strlen($dir) > 1 ? $dir : '')
1163
                        . '/'
1164
                        . $url;
1165
                }
1166
            }
1167
        }
1168
        return $url;*/
1169
    }
1170
 
1171
 
1172
    //TODO : voir si on ne peut pas glisser ça dans client ?
1173
	//FIXME : je met une fonction SIMPLISSIME a améliorer et reécrire
1174
    // La fonction de Zend est plus poussée est prend en compte le cas ou l'header ne peut pas etre envoyé
1175
    /**
1176
     * Rediriger vers la page du serveur avec les paramètres de confiration
1177
     *
1178
     * @param string $url URL de retour
1179
     * @param array $params paramètres additionnels
1180
     */
1181
    public function redirect($url, $params)	{
1182
    	//1. fabriquer l'url Get
1183
    	$urlRedirection = new Url($url);
1184
    	$urlRedirection->setRequete($params);
1185
    	//echo $urlRedirection->getUrl();
1186
    	try {
1187
    		header('Location:'.$urlRedirection->getUrl());
1188
    	} catch (Exception $e) {
1189
    		//TODO : voir autres méthodes de redirection
1190
    		// > balise META
1191
    		// > formulaire HTML
1192
    		// > JS
1193
    	}
1194
    }
1195
}
1196
?>