Subversion Repositories Applications.framework

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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