|
1 <?php |
|
2 |
|
3 /** |
|
4 * Zend Framework |
|
5 * |
|
6 * LICENSE |
|
7 * |
|
8 * This source file is subject to the new BSD license that is bundled |
|
9 * with this package in the file LICENSE.txt. |
|
10 * It is also available through the world-wide-web at this URL: |
|
11 * http://framework.zend.com/license/new-bsd |
|
12 * If you did not receive a copy of the license and are unable to |
|
13 * obtain it through the world-wide-web, please send an email |
|
14 * to license@zend.com so we can send you a copy immediately. |
|
15 * |
|
16 * @category Zend |
|
17 * @package Zend_OpenId |
|
18 * @subpackage Zend_OpenId_Consumer |
|
19 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
20 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
21 * @version $Id: Consumer.php 20096 2010-01-06 02:05:09Z bkarwin $ |
|
22 */ |
|
23 |
|
24 /** |
|
25 * @see Zend_OpenId |
|
26 */ |
|
27 require_once "Zend/OpenId.php"; |
|
28 |
|
29 /** |
|
30 * @see Zend_OpenId_Extension |
|
31 */ |
|
32 require_once "Zend/OpenId/Extension.php"; |
|
33 |
|
34 /** |
|
35 * @see Zend_OpenId_Consumer_Storage |
|
36 */ |
|
37 require_once "Zend/OpenId/Consumer/Storage.php"; |
|
38 |
|
39 /** |
|
40 * @see Zend_Http_Client |
|
41 */ |
|
42 require_once 'Zend/Http/Client.php'; |
|
43 |
|
44 /** |
|
45 * OpenID consumer implementation |
|
46 * |
|
47 * @category Zend |
|
48 * @package Zend_OpenId |
|
49 * @subpackage Zend_OpenId_Consumer |
|
50 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
51 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
52 */ |
|
53 class Zend_OpenId_Consumer |
|
54 { |
|
55 |
|
56 /** |
|
57 * Reference to an implementation of storage object |
|
58 * |
|
59 * @var Zend_OpenId_Consumer_Storage $_storage |
|
60 */ |
|
61 protected $_storage = null; |
|
62 |
|
63 /** |
|
64 * Enables or disables consumer to use association with server based on |
|
65 * Diffie-Hellman key agreement |
|
66 * |
|
67 * @var Zend_OpenId_Consumer_Storage $_dumbMode |
|
68 */ |
|
69 protected $_dumbMode = false; |
|
70 |
|
71 /** |
|
72 * Internal cache to prevent unnecessary access to storage |
|
73 * |
|
74 * @var array $_cache |
|
75 */ |
|
76 protected $_cache = array(); |
|
77 |
|
78 /** |
|
79 * HTTP client to make HTTP requests |
|
80 * |
|
81 * @var Zend_Http_Client $_httpClient |
|
82 */ |
|
83 private $_httpClient = null; |
|
84 |
|
85 /** |
|
86 * HTTP session to store climed_id between requests |
|
87 * |
|
88 * @var Zend_Session_Namespace $_session |
|
89 */ |
|
90 private $_session = null; |
|
91 |
|
92 /** |
|
93 * Last error message for logi, check or verify failure |
|
94 * |
|
95 * @var string $_error |
|
96 */ |
|
97 private $_error = ''; |
|
98 |
|
99 /** |
|
100 * Constructs a Zend_OpenId_Consumer object with given $storage. |
|
101 * Enables or disables future association with server based on |
|
102 * Diffie-Hellman key agreement. |
|
103 * |
|
104 * @param Zend_OpenId_Consumer_Storage $storage implementation of custom |
|
105 * storage object |
|
106 * @param bool $dumbMode Enables or disables consumer to use association |
|
107 * with server based on Diffie-Hellman key agreement |
|
108 */ |
|
109 public function __construct(Zend_OpenId_Consumer_Storage $storage = null, |
|
110 $dumbMode = false) |
|
111 { |
|
112 if ($storage === null) { |
|
113 require_once "Zend/OpenId/Consumer/Storage/File.php"; |
|
114 $this->_storage = new Zend_OpenId_Consumer_Storage_File(); |
|
115 } else { |
|
116 $this->_storage = $storage; |
|
117 } |
|
118 $this->_dumbMode = $dumbMode; |
|
119 } |
|
120 |
|
121 /** |
|
122 * Performs check (with possible user interaction) of OpenID identity. |
|
123 * |
|
124 * This is the first step of OpenID authentication process. |
|
125 * On success the function does not return (it does HTTP redirection to |
|
126 * server and exits). On failure it returns false. |
|
127 * |
|
128 * @param string $id OpenID identity |
|
129 * @param string $returnTo URL to redirect response from server to |
|
130 * @param string $root HTTP URL to identify consumer on server |
|
131 * @param mixed $extensions extension object or array of extensions objects |
|
132 * @param Zend_Controller_Response_Abstract $response an optional response |
|
133 * object to perform HTTP or HTML form redirection |
|
134 * @return bool |
|
135 */ |
|
136 public function login($id, $returnTo = null, $root = null, $extensions = null, |
|
137 Zend_Controller_Response_Abstract $response = null) |
|
138 { |
|
139 return $this->_checkId( |
|
140 false, |
|
141 $id, |
|
142 $returnTo, |
|
143 $root, |
|
144 $extensions, |
|
145 $response); |
|
146 } |
|
147 |
|
148 /** |
|
149 * Performs immediate check (without user interaction) of OpenID identity. |
|
150 * |
|
151 * This is the first step of OpenID authentication process. |
|
152 * On success the function does not return (it does HTTP redirection to |
|
153 * server and exits). On failure it returns false. |
|
154 * |
|
155 * @param string $id OpenID identity |
|
156 * @param string $returnTo HTTP URL to redirect response from server to |
|
157 * @param string $root HTTP URL to identify consumer on server |
|
158 * @param mixed $extensions extension object or array of extensions objects |
|
159 * @param Zend_Controller_Response_Abstract $response an optional response |
|
160 * object to perform HTTP or HTML form redirection |
|
161 * @return bool |
|
162 */ |
|
163 public function check($id, $returnTo=null, $root=null, $extensions = null, |
|
164 Zend_Controller_Response_Abstract $response = null) |
|
165 |
|
166 { |
|
167 return $this->_checkId( |
|
168 true, |
|
169 $id, |
|
170 $returnTo, |
|
171 $root, |
|
172 $extensions, |
|
173 $response); |
|
174 } |
|
175 |
|
176 /** |
|
177 * Verifies authentication response from OpenID server. |
|
178 * |
|
179 * This is the second step of OpenID authentication process. |
|
180 * The function returns true on successful authentication and false on |
|
181 * failure. |
|
182 * |
|
183 * @param array $params HTTP query data from OpenID server |
|
184 * @param string &$identity this argument is set to end-user's claimed |
|
185 * identifier or OpenID provider local identifier. |
|
186 * @param mixed $extensions extension object or array of extensions objects |
|
187 * @return bool |
|
188 */ |
|
189 public function verify($params, &$identity = "", $extensions = null) |
|
190 { |
|
191 $this->_setError(''); |
|
192 |
|
193 $version = 1.1; |
|
194 if (isset($params['openid_ns']) && |
|
195 $params['openid_ns'] == Zend_OpenId::NS_2_0) { |
|
196 $version = 2.0; |
|
197 } |
|
198 |
|
199 if (isset($params["openid_claimed_id"])) { |
|
200 $identity = $params["openid_claimed_id"]; |
|
201 } else if (isset($params["openid_identity"])){ |
|
202 $identity = $params["openid_identity"]; |
|
203 } else { |
|
204 $identity = ""; |
|
205 } |
|
206 |
|
207 if ($version < 2.0 && !isset($params["openid_claimed_id"])) { |
|
208 if ($this->_session !== null) { |
|
209 if ($this->_session->identity === $identity) { |
|
210 $identity = $this->_session->claimed_id; |
|
211 } |
|
212 } else if (defined('SID')) { |
|
213 if (isset($_SESSION["zend_openid"]["identity"]) && |
|
214 isset($_SESSION["zend_openid"]["claimed_id"]) && |
|
215 $_SESSION["zend_openid"]["identity"] === $identity) { |
|
216 $identity = $_SESSION["zend_openid"]["claimed_id"]; |
|
217 } |
|
218 } else { |
|
219 require_once "Zend/Session/Namespace.php"; |
|
220 $this->_session = new Zend_Session_Namespace("zend_openid"); |
|
221 if ($this->_session->identity === $identity) { |
|
222 $identity = $this->_session->claimed_id; |
|
223 } |
|
224 } |
|
225 } |
|
226 |
|
227 if (empty($params['openid_mode'])) { |
|
228 $this->_setError("Missing openid.mode"); |
|
229 return false; |
|
230 } |
|
231 if (empty($params['openid_return_to'])) { |
|
232 $this->_setError("Missing openid.return_to"); |
|
233 return false; |
|
234 } |
|
235 if (empty($params['openid_signed'])) { |
|
236 $this->_setError("Missing openid.signed"); |
|
237 return false; |
|
238 } |
|
239 if (empty($params['openid_sig'])) { |
|
240 $this->_setError("Missing openid.sig"); |
|
241 return false; |
|
242 } |
|
243 if ($params['openid_mode'] != 'id_res') { |
|
244 $this->_setError("Wrong openid.mode '".$params['openid_mode']."' != 'id_res'"); |
|
245 return false; |
|
246 } |
|
247 if (empty($params['openid_assoc_handle'])) { |
|
248 $this->_setError("Missing openid.assoc_handle"); |
|
249 return false; |
|
250 } |
|
251 if ($params['openid_return_to'] != Zend_OpenId::selfUrl()) { |
|
252 /* Ignore query part in openid.return_to */ |
|
253 $pos = strpos($params['openid_return_to'], '?'); |
|
254 if ($pos === false || |
|
255 SUBSTR($params['openid_return_to'], 0 , $pos) != Zend_OpenId::selfUrl()) { |
|
256 |
|
257 $this->_setError("Wrong openid.return_to '". |
|
258 $params['openid_return_to']."' != '" . Zend_OpenId::selfUrl() ."'"); |
|
259 return false; |
|
260 } |
|
261 } |
|
262 |
|
263 if ($version >= 2.0) { |
|
264 if (empty($params['openid_response_nonce'])) { |
|
265 $this->_setError("Missing openid.response_nonce"); |
|
266 return false; |
|
267 } |
|
268 if (empty($params['openid_op_endpoint'])) { |
|
269 $this->_setError("Missing openid.op_endpoint"); |
|
270 return false; |
|
271 /* OpenID 2.0 (11.3) Checking the Nonce */ |
|
272 } else if (!$this->_storage->isUniqueNonce($params['openid_op_endpoint'], $params['openid_response_nonce'])) { |
|
273 $this->_setError("Duplicate openid.response_nonce"); |
|
274 return false; |
|
275 } |
|
276 } |
|
277 |
|
278 |
|
279 if (!empty($params['openid_invalidate_handle'])) { |
|
280 if ($this->_storage->getAssociationByHandle( |
|
281 $params['openid_invalidate_handle'], |
|
282 $url, |
|
283 $macFunc, |
|
284 $secret, |
|
285 $expires)) { |
|
286 $this->_storage->delAssociation($url); |
|
287 } |
|
288 } |
|
289 |
|
290 if ($this->_storage->getAssociationByHandle( |
|
291 $params['openid_assoc_handle'], |
|
292 $url, |
|
293 $macFunc, |
|
294 $secret, |
|
295 $expires)) { |
|
296 $signed = explode(',', $params['openid_signed']); |
|
297 $data = ''; |
|
298 foreach ($signed as $key) { |
|
299 $data .= $key . ':' . $params['openid_' . strtr($key,'.','_')] . "\n"; |
|
300 } |
|
301 if (base64_decode($params['openid_sig']) == |
|
302 Zend_OpenId::hashHmac($macFunc, $data, $secret)) { |
|
303 if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) { |
|
304 $this->_setError("Extension::parseResponse failure"); |
|
305 return false; |
|
306 } |
|
307 /* OpenID 2.0 (11.2) Verifying Discovered Information */ |
|
308 if (isset($params['openid_claimed_id'])) { |
|
309 $id = $params['openid_claimed_id']; |
|
310 if (!Zend_OpenId::normalize($id)) { |
|
311 $this->_setError("Normalization failed"); |
|
312 return false; |
|
313 } else if (!$this->_discovery($id, $discovered_server, $discovered_version)) { |
|
314 $this->_setError("Discovery failed: " . $this->getError()); |
|
315 return false; |
|
316 } else if ((!empty($params['openid_identity']) && |
|
317 $params["openid_identity"] != $id) || |
|
318 (!empty($params['openid_op_endpoint']) && |
|
319 $params['openid_op_endpoint'] != $discovered_server) || |
|
320 $discovered_version != $version) { |
|
321 $this->_setError("Discovery information verification failed"); |
|
322 return false; |
|
323 } |
|
324 } |
|
325 return true; |
|
326 } |
|
327 $this->_storage->delAssociation($url); |
|
328 $this->_setError("Signature check failed"); |
|
329 return false; |
|
330 } |
|
331 else |
|
332 { |
|
333 /* Use dumb mode */ |
|
334 if (isset($params['openid_claimed_id'])) { |
|
335 $id = $params['openid_claimed_id']; |
|
336 } else if (isset($params['openid_identity'])) { |
|
337 $id = $params['openid_identity']; |
|
338 } else { |
|
339 $this->_setError("Missing openid.claimed_id and openid.identity"); |
|
340 return false; |
|
341 } |
|
342 |
|
343 if (!Zend_OpenId::normalize($id)) { |
|
344 $this->_setError("Normalization failed"); |
|
345 return false; |
|
346 } else if (!$this->_discovery($id, $server, $discovered_version)) { |
|
347 $this->_setError("Discovery failed: " . $this->getError()); |
|
348 return false; |
|
349 } |
|
350 |
|
351 /* OpenID 2.0 (11.2) Verifying Discovered Information */ |
|
352 if ((isset($params['openid_identity']) && |
|
353 $params["openid_identity"] != $id) || |
|
354 (isset($params['openid_op_endpoint']) && |
|
355 $params['openid_op_endpoint'] != $server) || |
|
356 $discovered_version != $version) { |
|
357 $this->_setError("Discovery information verification failed"); |
|
358 return false; |
|
359 } |
|
360 |
|
361 $params2 = array(); |
|
362 foreach ($params as $key => $val) { |
|
363 if (strpos($key, 'openid_ns_') === 0) { |
|
364 $key = 'openid.ns.' . substr($key, strlen('openid_ns_')); |
|
365 } else if (strpos($key, 'openid_sreg_') === 0) { |
|
366 $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_')); |
|
367 } else if (strpos($key, 'openid_') === 0) { |
|
368 $key = 'openid.' . substr($key, strlen('openid_')); |
|
369 } |
|
370 $params2[$key] = $val; |
|
371 } |
|
372 $params2['openid.mode'] = 'check_authentication'; |
|
373 $ret = $this->_httpRequest($server, 'POST', $params2, $status); |
|
374 if ($status != 200) { |
|
375 $this->_setError("'Dumb' signature verification HTTP request failed"); |
|
376 return false; |
|
377 } |
|
378 $r = array(); |
|
379 if (is_string($ret)) { |
|
380 foreach(explode("\n", $ret) as $line) { |
|
381 $line = trim($line); |
|
382 if (!empty($line)) { |
|
383 $x = explode(':', $line, 2); |
|
384 if (is_array($x) && count($x) == 2) { |
|
385 list($key, $value) = $x; |
|
386 $r[trim($key)] = trim($value); |
|
387 } |
|
388 } |
|
389 } |
|
390 } |
|
391 $ret = $r; |
|
392 if (!empty($ret['invalidate_handle'])) { |
|
393 if ($this->_storage->getAssociationByHandle( |
|
394 $ret['invalidate_handle'], |
|
395 $url, |
|
396 $macFunc, |
|
397 $secret, |
|
398 $expires)) { |
|
399 $this->_storage->delAssociation($url); |
|
400 } |
|
401 } |
|
402 if (isset($ret['is_valid']) && $ret['is_valid'] == 'true') { |
|
403 if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) { |
|
404 $this->_setError("Extension::parseResponse failure"); |
|
405 return false; |
|
406 } |
|
407 return true; |
|
408 } |
|
409 $this->_setError("'Dumb' signature verification failed"); |
|
410 return false; |
|
411 } |
|
412 } |
|
413 |
|
414 /** |
|
415 * Store assiciation in internal chace and external storage |
|
416 * |
|
417 * @param string $url OpenID server url |
|
418 * @param string $handle association handle |
|
419 * @param string $macFunc HMAC function (sha1 or sha256) |
|
420 * @param string $secret shared secret |
|
421 * @param integer $expires expiration UNIX time |
|
422 * @return void |
|
423 */ |
|
424 protected function _addAssociation($url, $handle, $macFunc, $secret, $expires) |
|
425 { |
|
426 $this->_cache[$url] = array($handle, $macFunc, $secret, $expires); |
|
427 return $this->_storage->addAssociation( |
|
428 $url, |
|
429 $handle, |
|
430 $macFunc, |
|
431 $secret, |
|
432 $expires); |
|
433 } |
|
434 |
|
435 /** |
|
436 * Retrive assiciation information for given $url from internal cahce or |
|
437 * external storage |
|
438 * |
|
439 * @param string $url OpenID server url |
|
440 * @param string &$handle association handle |
|
441 * @param string &$macFunc HMAC function (sha1 or sha256) |
|
442 * @param string &$secret shared secret |
|
443 * @param integer &$expires expiration UNIX time |
|
444 * @return void |
|
445 */ |
|
446 protected function _getAssociation($url, &$handle, &$macFunc, &$secret, &$expires) |
|
447 { |
|
448 if (isset($this->_cache[$url])) { |
|
449 $handle = $this->_cache[$url][0]; |
|
450 $macFunc = $this->_cache[$url][1]; |
|
451 $secret = $this->_cache[$url][2]; |
|
452 $expires = $this->_cache[$url][3]; |
|
453 return true; |
|
454 } |
|
455 if ($this->_storage->getAssociation( |
|
456 $url, |
|
457 $handle, |
|
458 $macFunc, |
|
459 $secret, |
|
460 $expires)) { |
|
461 $this->_cache[$url] = array($handle, $macFunc, $secret, $expires); |
|
462 return true; |
|
463 } |
|
464 return false; |
|
465 } |
|
466 |
|
467 /** |
|
468 * Performs HTTP request to given $url using given HTTP $method. |
|
469 * Send additinal query specified by variable/value array, |
|
470 * On success returns HTTP response without headers, false on failure. |
|
471 * |
|
472 * @param string $url OpenID server url |
|
473 * @param string $method HTTP request method 'GET' or 'POST' |
|
474 * @param array $params additional qwery parameters to be passed with |
|
475 * @param int &$staus HTTP status code |
|
476 * request |
|
477 * @return mixed |
|
478 */ |
|
479 protected function _httpRequest($url, $method = 'GET', array $params = array(), &$status = null) |
|
480 { |
|
481 $client = $this->_httpClient; |
|
482 if ($client === null) { |
|
483 $client = new Zend_Http_Client( |
|
484 $url, |
|
485 array( |
|
486 'maxredirects' => 4, |
|
487 'timeout' => 15, |
|
488 'useragent' => 'Zend_OpenId' |
|
489 ) |
|
490 ); |
|
491 } else { |
|
492 $client->setUri($url); |
|
493 } |
|
494 |
|
495 $client->resetParameters(); |
|
496 if ($method == 'POST') { |
|
497 $client->setMethod(Zend_Http_Client::POST); |
|
498 $client->setParameterPost($params); |
|
499 } else { |
|
500 $client->setMethod(Zend_Http_Client::GET); |
|
501 $client->setParameterGet($params); |
|
502 } |
|
503 |
|
504 try { |
|
505 $response = $client->request(); |
|
506 } catch (Exception $e) { |
|
507 $this->_setError('HTTP Request failed: ' . $e->getMessage()); |
|
508 return false; |
|
509 } |
|
510 $status = $response->getStatus(); |
|
511 $body = $response->getBody(); |
|
512 if ($status == 200 || ($status == 400 && !empty($body))) { |
|
513 return $body; |
|
514 }else{ |
|
515 $this->_setError('Bad HTTP response'); |
|
516 return false; |
|
517 } |
|
518 } |
|
519 |
|
520 /** |
|
521 * Create (or reuse existing) association between OpenID consumer and |
|
522 * OpenID server based on Diffie-Hellman key agreement. Returns true |
|
523 * on success and false on failure. |
|
524 * |
|
525 * @param string $url OpenID server url |
|
526 * @param float $version OpenID protocol version |
|
527 * @param string $priv_key for testing only |
|
528 * @return bool |
|
529 */ |
|
530 protected function _associate($url, $version, $priv_key=null) |
|
531 { |
|
532 |
|
533 /* Check if we already have association in chace or storage */ |
|
534 if ($this->_getAssociation( |
|
535 $url, |
|
536 $handle, |
|
537 $macFunc, |
|
538 $secret, |
|
539 $expires)) { |
|
540 return true; |
|
541 } |
|
542 |
|
543 if ($this->_dumbMode) { |
|
544 /* Use dumb mode */ |
|
545 return true; |
|
546 } |
|
547 |
|
548 $params = array(); |
|
549 |
|
550 if ($version >= 2.0) { |
|
551 $params = array( |
|
552 'openid.ns' => Zend_OpenId::NS_2_0, |
|
553 'openid.mode' => 'associate', |
|
554 'openid.assoc_type' => 'HMAC-SHA256', |
|
555 'openid.session_type' => 'DH-SHA256', |
|
556 ); |
|
557 } else { |
|
558 $params = array( |
|
559 'openid.mode' => 'associate', |
|
560 'openid.assoc_type' => 'HMAC-SHA1', |
|
561 'openid.session_type' => 'DH-SHA1', |
|
562 ); |
|
563 } |
|
564 |
|
565 $dh = Zend_OpenId::createDhKey(pack('H*', Zend_OpenId::DH_P), |
|
566 pack('H*', Zend_OpenId::DH_G), |
|
567 $priv_key); |
|
568 $dh_details = Zend_OpenId::getDhKeyDetails($dh); |
|
569 |
|
570 $params['openid.dh_modulus'] = base64_encode( |
|
571 Zend_OpenId::btwoc($dh_details['p'])); |
|
572 $params['openid.dh_gen'] = base64_encode( |
|
573 Zend_OpenId::btwoc($dh_details['g'])); |
|
574 $params['openid.dh_consumer_public'] = base64_encode( |
|
575 Zend_OpenId::btwoc($dh_details['pub_key'])); |
|
576 |
|
577 while(1) { |
|
578 $ret = $this->_httpRequest($url, 'POST', $params, $status); |
|
579 if ($ret === false) { |
|
580 $this->_setError("HTTP request failed"); |
|
581 return false; |
|
582 } |
|
583 |
|
584 $r = array(); |
|
585 $bad_response = false; |
|
586 foreach(explode("\n", $ret) as $line) { |
|
587 $line = trim($line); |
|
588 if (!empty($line)) { |
|
589 $x = explode(':', $line, 2); |
|
590 if (is_array($x) && count($x) == 2) { |
|
591 list($key, $value) = $x; |
|
592 $r[trim($key)] = trim($value); |
|
593 } else { |
|
594 $bad_response = true; |
|
595 } |
|
596 } |
|
597 } |
|
598 if ($bad_response && strpos($ret, 'Unknown session type') !== false) { |
|
599 $r['error_code'] = 'unsupported-type'; |
|
600 } |
|
601 $ret = $r; |
|
602 |
|
603 if (isset($ret['error_code']) && |
|
604 $ret['error_code'] == 'unsupported-type') { |
|
605 if ($params['openid.session_type'] == 'DH-SHA256') { |
|
606 $params['openid.session_type'] = 'DH-SHA1'; |
|
607 $params['openid.assoc_type'] = 'HMAC-SHA1'; |
|
608 } else if ($params['openid.session_type'] == 'DH-SHA1') { |
|
609 $params['openid.session_type'] = 'no-encryption'; |
|
610 } else { |
|
611 $this->_setError("The OpenID service responded with: " . $ret['error_code']); |
|
612 return false; |
|
613 } |
|
614 } else { |
|
615 break; |
|
616 } |
|
617 } |
|
618 |
|
619 if ($status != 200) { |
|
620 $this->_setError("The server responded with status code: " . $status); |
|
621 return false; |
|
622 } |
|
623 |
|
624 if ($version >= 2.0 && |
|
625 isset($ret['ns']) && |
|
626 $ret['ns'] != Zend_OpenId::NS_2_0) { |
|
627 $this->_setError("Wrong namespace definition in the server response"); |
|
628 return false; |
|
629 } |
|
630 |
|
631 if (!isset($ret['assoc_handle']) || |
|
632 !isset($ret['expires_in']) || |
|
633 !isset($ret['assoc_type']) || |
|
634 $params['openid.assoc_type'] != $ret['assoc_type']) { |
|
635 if ($params['openid.assoc_type'] != $ret['assoc_type']) { |
|
636 $this->_setError("The returned assoc_type differed from the supplied openid.assoc_type"); |
|
637 } else { |
|
638 $this->_setError("Missing required data from provider (assoc_handle, expires_in, assoc_type are required)"); |
|
639 } |
|
640 return false; |
|
641 } |
|
642 |
|
643 $handle = $ret['assoc_handle']; |
|
644 $expiresIn = $ret['expires_in']; |
|
645 |
|
646 if ($ret['assoc_type'] == 'HMAC-SHA1') { |
|
647 $macFunc = 'sha1'; |
|
648 } else if ($ret['assoc_type'] == 'HMAC-SHA256' && |
|
649 $version >= 2.0) { |
|
650 $macFunc = 'sha256'; |
|
651 } else { |
|
652 $this->_setError("Unsupported assoc_type"); |
|
653 return false; |
|
654 } |
|
655 |
|
656 if ((empty($ret['session_type']) || |
|
657 ($version >= 2.0 && $ret['session_type'] == 'no-encryption')) && |
|
658 isset($ret['mac_key'])) { |
|
659 $secret = base64_decode($ret['mac_key']); |
|
660 } else if (isset($ret['session_type']) && |
|
661 $ret['session_type'] == 'DH-SHA1' && |
|
662 !empty($ret['dh_server_public']) && |
|
663 !empty($ret['enc_mac_key'])) { |
|
664 $dhFunc = 'sha1'; |
|
665 } else if (isset($ret['session_type']) && |
|
666 $ret['session_type'] == 'DH-SHA256' && |
|
667 $version >= 2.0 && |
|
668 !empty($ret['dh_server_public']) && |
|
669 !empty($ret['enc_mac_key'])) { |
|
670 $dhFunc = 'sha256'; |
|
671 } else { |
|
672 $this->_setError("Unsupported session_type"); |
|
673 return false; |
|
674 } |
|
675 if (isset($dhFunc)) { |
|
676 $serverPub = base64_decode($ret['dh_server_public']); |
|
677 $dhSec = Zend_OpenId::computeDhSecret($serverPub, $dh); |
|
678 if ($dhSec === false) { |
|
679 $this->_setError("DH secret comutation failed"); |
|
680 return false; |
|
681 } |
|
682 $sec = Zend_OpenId::digest($dhFunc, $dhSec); |
|
683 if ($sec === false) { |
|
684 $this->_setError("Could not create digest"); |
|
685 return false; |
|
686 } |
|
687 $secret = $sec ^ base64_decode($ret['enc_mac_key']); |
|
688 } |
|
689 if ($macFunc == 'sha1') { |
|
690 if (Zend_OpenId::strlen($secret) != 20) { |
|
691 $this->_setError("The length of the sha1 secret must be 20"); |
|
692 return false; |
|
693 } |
|
694 } else if ($macFunc == 'sha256') { |
|
695 if (Zend_OpenId::strlen($secret) != 32) { |
|
696 $this->_setError("The length of the sha256 secret must be 32"); |
|
697 return false; |
|
698 } |
|
699 } |
|
700 $this->_addAssociation( |
|
701 $url, |
|
702 $handle, |
|
703 $macFunc, |
|
704 $secret, |
|
705 time() + $expiresIn); |
|
706 return true; |
|
707 } |
|
708 |
|
709 /** |
|
710 * Performs discovery of identity and finds OpenID URL, OpenID server URL |
|
711 * and OpenID protocol version. Returns true on succees and false on |
|
712 * failure. |
|
713 * |
|
714 * @param string &$id OpenID identity URL |
|
715 * @param string &$server OpenID server URL |
|
716 * @param float &$version OpenID protocol version |
|
717 * @return bool |
|
718 * @todo OpenID 2.0 (7.3) XRI and Yadis discovery |
|
719 */ |
|
720 protected function _discovery(&$id, &$server, &$version) |
|
721 { |
|
722 $realId = $id; |
|
723 if ($this->_storage->getDiscoveryInfo( |
|
724 $id, |
|
725 $realId, |
|
726 $server, |
|
727 $version, |
|
728 $expire)) { |
|
729 $id = $realId; |
|
730 return true; |
|
731 } |
|
732 |
|
733 /* TODO: OpenID 2.0 (7.3) XRI and Yadis discovery */ |
|
734 |
|
735 /* HTML-based discovery */ |
|
736 $response = $this->_httpRequest($id, 'GET', array(), $status); |
|
737 if ($status != 200 || !is_string($response)) { |
|
738 return false; |
|
739 } |
|
740 if (preg_match( |
|
741 '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i', |
|
742 $response, |
|
743 $r)) { |
|
744 $version = 2.0; |
|
745 $server = $r[3]; |
|
746 } else if (preg_match( |
|
747 '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\3[^>]*\/?>/i', |
|
748 $response, |
|
749 $r)) { |
|
750 $version = 2.0; |
|
751 $server = $r[2]; |
|
752 } else if (preg_match( |
|
753 '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i', |
|
754 $response, |
|
755 $r)) { |
|
756 $version = 1.1; |
|
757 $server = $r[3]; |
|
758 } else if (preg_match( |
|
759 '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\3[^>]*\/?>/i', |
|
760 $response, |
|
761 $r)) { |
|
762 $version = 1.1; |
|
763 $server = $r[2]; |
|
764 } else { |
|
765 return false; |
|
766 } |
|
767 if ($version >= 2.0) { |
|
768 if (preg_match( |
|
769 '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i', |
|
770 $response, |
|
771 $r)) { |
|
772 $realId = $r[3]; |
|
773 } else if (preg_match( |
|
774 '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\3[^>]*\/?>/i', |
|
775 $response, |
|
776 $r)) { |
|
777 $realId = $r[2]; |
|
778 } |
|
779 } else { |
|
780 if (preg_match( |
|
781 '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i', |
|
782 $response, |
|
783 $r)) { |
|
784 $realId = $r[3]; |
|
785 } else if (preg_match( |
|
786 '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\3[^>]*\/?>/i', |
|
787 $response, |
|
788 $r)) { |
|
789 $realId = $r[2]; |
|
790 } |
|
791 } |
|
792 |
|
793 $expire = time() + 60 * 60; |
|
794 $this->_storage->addDiscoveryInfo($id, $realId, $server, $version, $expire); |
|
795 $id = $realId; |
|
796 return true; |
|
797 } |
|
798 |
|
799 /** |
|
800 * Performs check of OpenID identity. |
|
801 * |
|
802 * This is the first step of OpenID authentication process. |
|
803 * On success the function does not return (it does HTTP redirection to |
|
804 * server and exits). On failure it returns false. |
|
805 * |
|
806 * @param bool $immediate enables or disables interaction with user |
|
807 * @param string $id OpenID identity |
|
808 * @param string $returnTo HTTP URL to redirect response from server to |
|
809 * @param string $root HTTP URL to identify consumer on server |
|
810 * @param mixed $extensions extension object or array of extensions objects |
|
811 * @param Zend_Controller_Response_Abstract $response an optional response |
|
812 * object to perform HTTP or HTML form redirection |
|
813 * @return bool |
|
814 */ |
|
815 protected function _checkId($immediate, $id, $returnTo=null, $root=null, |
|
816 $extensions=null, Zend_Controller_Response_Abstract $response = null) |
|
817 { |
|
818 $this->_setError(''); |
|
819 |
|
820 if (!Zend_OpenId::normalize($id)) { |
|
821 $this->_setError("Normalisation failed"); |
|
822 return false; |
|
823 } |
|
824 $claimedId = $id; |
|
825 |
|
826 if (!$this->_discovery($id, $server, $version)) { |
|
827 $this->_setError("Discovery failed: " . $this->getError()); |
|
828 return false; |
|
829 } |
|
830 if (!$this->_associate($server, $version)) { |
|
831 $this->_setError("Association failed: " . $this->getError()); |
|
832 return false; |
|
833 } |
|
834 if (!$this->_getAssociation( |
|
835 $server, |
|
836 $handle, |
|
837 $macFunc, |
|
838 $secret, |
|
839 $expires)) { |
|
840 /* Use dumb mode */ |
|
841 unset($handle); |
|
842 unset($macFunc); |
|
843 unset($secret); |
|
844 unset($expires); |
|
845 } |
|
846 |
|
847 $params = array(); |
|
848 if ($version >= 2.0) { |
|
849 $params['openid.ns'] = Zend_OpenId::NS_2_0; |
|
850 } |
|
851 |
|
852 $params['openid.mode'] = $immediate ? |
|
853 'checkid_immediate' : 'checkid_setup'; |
|
854 |
|
855 $params['openid.identity'] = $id; |
|
856 |
|
857 $params['openid.claimed_id'] = $claimedId; |
|
858 |
|
859 if ($version <= 2.0) { |
|
860 if ($this->_session !== null) { |
|
861 $this->_session->identity = $id; |
|
862 $this->_session->claimed_id = $claimedId; |
|
863 } else if (defined('SID')) { |
|
864 $_SESSION["zend_openid"] = array( |
|
865 "identity" => $id, |
|
866 "claimed_id" => $claimedId); |
|
867 } else { |
|
868 require_once "Zend/Session/Namespace.php"; |
|
869 $this->_session = new Zend_Session_Namespace("zend_openid"); |
|
870 $this->_session->identity = $id; |
|
871 $this->_session->claimed_id = $claimedId; |
|
872 } |
|
873 } |
|
874 |
|
875 if (isset($handle)) { |
|
876 $params['openid.assoc_handle'] = $handle; |
|
877 } |
|
878 |
|
879 $params['openid.return_to'] = Zend_OpenId::absoluteUrl($returnTo); |
|
880 |
|
881 if (empty($root)) { |
|
882 $root = Zend_OpenId::selfUrl(); |
|
883 if ($root[strlen($root)-1] != '/') { |
|
884 $root = dirname($root); |
|
885 } |
|
886 } |
|
887 if ($version >= 2.0) { |
|
888 $params['openid.realm'] = $root; |
|
889 } else { |
|
890 $params['openid.trust_root'] = $root; |
|
891 } |
|
892 |
|
893 if (!Zend_OpenId_Extension::forAll($extensions, 'prepareRequest', $params)) { |
|
894 $this->_setError("Extension::prepareRequest failure"); |
|
895 return false; |
|
896 } |
|
897 |
|
898 Zend_OpenId::redirect($server, $params, $response); |
|
899 return true; |
|
900 } |
|
901 |
|
902 /** |
|
903 * Sets HTTP client object to make HTTP requests |
|
904 * |
|
905 * @param Zend_Http_Client $client HTTP client object to be used |
|
906 */ |
|
907 public function setHttpClient($client) { |
|
908 $this->_httpClient = $client; |
|
909 } |
|
910 |
|
911 /** |
|
912 * Returns HTTP client object that will be used to make HTTP requests |
|
913 * |
|
914 * @return Zend_Http_Client |
|
915 */ |
|
916 public function getHttpClient() { |
|
917 return $this->_httpClient; |
|
918 } |
|
919 |
|
920 /** |
|
921 * Sets session object to store climed_id |
|
922 * |
|
923 * @param Zend_Session_Namespace $session HTTP client object to be used |
|
924 */ |
|
925 public function setSession(Zend_Session_Namespace $session) { |
|
926 $this->_session = $session; |
|
927 } |
|
928 |
|
929 /** |
|
930 * Returns session object that is used to store climed_id |
|
931 * |
|
932 * @return Zend_Session_Namespace |
|
933 */ |
|
934 public function getSession() { |
|
935 return $this->_session; |
|
936 } |
|
937 |
|
938 /** |
|
939 * Saves error message |
|
940 * |
|
941 * @param string $message error message |
|
942 */ |
|
943 protected function _setError($message) |
|
944 { |
|
945 $this->_error = $message; |
|
946 } |
|
947 |
|
948 /** |
|
949 * Returns error message that explains failure of login, check or verify |
|
950 * |
|
951 * @return string |
|
952 */ |
|
953 public function getError() |
|
954 { |
|
955 return $this->_error; |
|
956 } |
|
957 |
|
958 } |