|
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_Provider |
|
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: Provider.php 23088 2010-10-11 19:53:24Z padraic $ |
|
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 * OpenID provider (server) implementation |
|
36 * |
|
37 * @category Zend |
|
38 * @package Zend_OpenId |
|
39 * @subpackage Zend_OpenId_Provider |
|
40 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
41 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
42 */ |
|
43 class Zend_OpenId_Provider |
|
44 { |
|
45 |
|
46 /** |
|
47 * Reference to an implementation of storage object |
|
48 * |
|
49 * @var Zend_OpenId_Provider_Storage $_storage |
|
50 */ |
|
51 private $_storage; |
|
52 |
|
53 /** |
|
54 * Reference to an implementation of user object |
|
55 * |
|
56 * @var Zend_OpenId_Provider_User $_user |
|
57 */ |
|
58 private $_user; |
|
59 |
|
60 /** |
|
61 * Time to live of association session in secconds |
|
62 * |
|
63 * @var integer $_sessionTtl |
|
64 */ |
|
65 private $_sessionTtl; |
|
66 |
|
67 /** |
|
68 * URL to peform interactive user login |
|
69 * |
|
70 * @var string $_loginUrl |
|
71 */ |
|
72 private $_loginUrl; |
|
73 |
|
74 /** |
|
75 * URL to peform interactive validation of consumer by user |
|
76 * |
|
77 * @var string $_trustUrl |
|
78 */ |
|
79 private $_trustUrl; |
|
80 |
|
81 /** |
|
82 * The OP Endpoint URL |
|
83 * |
|
84 * @var string $_opEndpoint |
|
85 */ |
|
86 private $_opEndpoint; |
|
87 |
|
88 /** |
|
89 * Constructs a Zend_OpenId_Provider object with given parameters. |
|
90 * |
|
91 * @param string $loginUrl is an URL that provides login screen for |
|
92 * end-user (by default it is the same URL with additional GET variable |
|
93 * openid.action=login) |
|
94 * @param string $trustUrl is an URL that shows a question if end-user |
|
95 * trust to given consumer (by default it is the same URL with additional |
|
96 * GET variable openid.action=trust) |
|
97 * @param Zend_OpenId_Provider_User $user is an object for communication |
|
98 * with User-Agent and store information about logged-in user (it is a |
|
99 * Zend_OpenId_Provider_User_Session object by default) |
|
100 * @param Zend_OpenId_Provider_Storage $storage is an object for keeping |
|
101 * persistent database (it is a Zend_OpenId_Provider_Storage_File object |
|
102 * by default) |
|
103 * @param integer $sessionTtl is a default time to live for association |
|
104 * session in seconds (1 hour by default). Consumer must reestablish |
|
105 * association after that time. |
|
106 */ |
|
107 public function __construct($loginUrl = null, |
|
108 $trustUrl = null, |
|
109 Zend_OpenId_Provider_User $user = null, |
|
110 Zend_OpenId_Provider_Storage $storage = null, |
|
111 $sessionTtl = 3600) |
|
112 { |
|
113 if ($loginUrl === null) { |
|
114 $loginUrl = Zend_OpenId::selfUrl() . '?openid.action=login'; |
|
115 } else { |
|
116 $loginUrl = Zend_OpenId::absoluteUrl($loginUrl); |
|
117 } |
|
118 $this->_loginUrl = $loginUrl; |
|
119 if ($trustUrl === null) { |
|
120 $trustUrl = Zend_OpenId::selfUrl() . '?openid.action=trust'; |
|
121 } else { |
|
122 $trustUrl = Zend_OpenId::absoluteUrl($trustUrl); |
|
123 } |
|
124 $this->_trustUrl = $trustUrl; |
|
125 if ($user === null) { |
|
126 require_once "Zend/OpenId/Provider/User/Session.php"; |
|
127 $this->_user = new Zend_OpenId_Provider_User_Session(); |
|
128 } else { |
|
129 $this->_user = $user; |
|
130 } |
|
131 if ($storage === null) { |
|
132 require_once "Zend/OpenId/Provider/Storage/File.php"; |
|
133 $this->_storage = new Zend_OpenId_Provider_Storage_File(); |
|
134 } else { |
|
135 $this->_storage = $storage; |
|
136 } |
|
137 $this->_sessionTtl = $sessionTtl; |
|
138 } |
|
139 |
|
140 /** |
|
141 * Sets the OP Endpoint URL |
|
142 * |
|
143 * @param string $url the OP Endpoint URL |
|
144 * @return null |
|
145 */ |
|
146 public function setOpEndpoint($url) |
|
147 { |
|
148 $this->_opEndpoint = $url; |
|
149 } |
|
150 |
|
151 /** |
|
152 * Registers a new user with given $id and $password |
|
153 * Returns true in case of success and false if user with given $id already |
|
154 * exists |
|
155 * |
|
156 * @param string $id user identity URL |
|
157 * @param string $password encoded user password |
|
158 * @return bool |
|
159 */ |
|
160 public function register($id, $password) |
|
161 { |
|
162 if (!Zend_OpenId::normalize($id) || empty($id)) { |
|
163 return false; |
|
164 } |
|
165 return $this->_storage->addUser($id, md5($id.$password)); |
|
166 } |
|
167 |
|
168 /** |
|
169 * Returns true if user with given $id exists and false otherwise |
|
170 * |
|
171 * @param string $id user identity URL |
|
172 * @return bool |
|
173 */ |
|
174 public function hasUser($id) { |
|
175 if (!Zend_OpenId::normalize($id)) { |
|
176 return false; |
|
177 } |
|
178 return $this->_storage->hasUser($id); |
|
179 } |
|
180 |
|
181 /** |
|
182 * Performs login of user with given $id and $password |
|
183 * Returns true in case of success and false otherwise |
|
184 * |
|
185 * @param string $id user identity URL |
|
186 * @param string $password user password |
|
187 * @return bool |
|
188 */ |
|
189 public function login($id, $password) |
|
190 { |
|
191 if (!Zend_OpenId::normalize($id)) { |
|
192 return false; |
|
193 } |
|
194 if (!$this->_storage->checkUser($id, md5($id.$password))) { |
|
195 return false; |
|
196 } |
|
197 $this->_user->setLoggedInUser($id); |
|
198 return true; |
|
199 } |
|
200 |
|
201 /** |
|
202 * Performs logout. Clears information about logged in user. |
|
203 * |
|
204 * @return void |
|
205 */ |
|
206 public function logout() |
|
207 { |
|
208 $this->_user->delLoggedInUser(); |
|
209 return true; |
|
210 } |
|
211 |
|
212 /** |
|
213 * Returns identity URL of current logged in user or false |
|
214 * |
|
215 * @return mixed |
|
216 */ |
|
217 public function getLoggedInUser() { |
|
218 return $this->_user->getLoggedInUser(); |
|
219 } |
|
220 |
|
221 /** |
|
222 * Retrieve consumer's root URL from request query. |
|
223 * Returns URL or false in case of failure |
|
224 * |
|
225 * @param array $params query arguments |
|
226 * @return mixed |
|
227 */ |
|
228 public function getSiteRoot($params) |
|
229 { |
|
230 $version = 1.1; |
|
231 if (isset($params['openid_ns']) && |
|
232 $params['openid_ns'] == Zend_OpenId::NS_2_0) { |
|
233 $version = 2.0; |
|
234 } |
|
235 if ($version >= 2.0 && isset($params['openid_realm'])) { |
|
236 $root = $params['openid_realm']; |
|
237 } else if ($version < 2.0 && isset($params['openid_trust_root'])) { |
|
238 $root = $params['openid_trust_root']; |
|
239 } else if (isset($params['openid_return_to'])) { |
|
240 $root = $params['openid_return_to']; |
|
241 } else { |
|
242 return false; |
|
243 } |
|
244 if (Zend_OpenId::normalizeUrl($root) && !empty($root)) { |
|
245 return $root; |
|
246 } |
|
247 return false; |
|
248 } |
|
249 |
|
250 /** |
|
251 * Allows consumer with given root URL to authenticate current logged |
|
252 * in user. Returns true on success and false on error. |
|
253 * |
|
254 * @param string $root root URL |
|
255 * @param mixed $extensions extension object or array of extensions objects |
|
256 * @return bool |
|
257 */ |
|
258 public function allowSite($root, $extensions=null) |
|
259 { |
|
260 $id = $this->getLoggedInUser(); |
|
261 if ($id === false) { |
|
262 return false; |
|
263 } |
|
264 if ($extensions !== null) { |
|
265 $data = array(); |
|
266 Zend_OpenId_Extension::forAll($extensions, 'getTrustData', $data); |
|
267 } else { |
|
268 $data = true; |
|
269 } |
|
270 $this->_storage->addSite($id, $root, $data); |
|
271 return true; |
|
272 } |
|
273 |
|
274 /** |
|
275 * Prohibit consumer with given root URL to authenticate current logged |
|
276 * in user. Returns true on success and false on error. |
|
277 * |
|
278 * @param string $root root URL |
|
279 * @return bool |
|
280 */ |
|
281 public function denySite($root) |
|
282 { |
|
283 $id = $this->getLoggedInUser(); |
|
284 if ($id === false) { |
|
285 return false; |
|
286 } |
|
287 $this->_storage->addSite($id, $root, false); |
|
288 return true; |
|
289 } |
|
290 |
|
291 /** |
|
292 * Delete consumer with given root URL from known sites of current logged |
|
293 * in user. Next time this consumer will try to authenticate the user, |
|
294 * Provider will ask user's confirmation. |
|
295 * Returns true on success and false on error. |
|
296 * |
|
297 * @param string $root root URL |
|
298 * @return bool |
|
299 */ |
|
300 public function delSite($root) |
|
301 { |
|
302 $id = $this->getLoggedInUser(); |
|
303 if ($id === false) { |
|
304 return false; |
|
305 } |
|
306 $this->_storage->addSite($id, $root, null); |
|
307 return true; |
|
308 } |
|
309 |
|
310 /** |
|
311 * Returns list of known consumers for current logged in user or false |
|
312 * if he is not logged in. |
|
313 * |
|
314 * @return mixed |
|
315 */ |
|
316 public function getTrustedSites() |
|
317 { |
|
318 $id = $this->getLoggedInUser(); |
|
319 if ($id === false) { |
|
320 return false; |
|
321 } |
|
322 return $this->_storage->getTrustedSites($id); |
|
323 } |
|
324 |
|
325 /** |
|
326 * Handles HTTP request from consumer |
|
327 * |
|
328 * @param array $params GET or POST variables. If this parameter is omited |
|
329 * or set to null, then $_GET or $_POST superglobal variable is used |
|
330 * according to REQUEST_METHOD. |
|
331 * @param mixed $extensions extension object or array of extensions objects |
|
332 * @param Zend_Controller_Response_Abstract $response an optional response |
|
333 * object to perform HTTP or HTML form redirection |
|
334 * @return mixed |
|
335 */ |
|
336 public function handle($params=null, $extensions=null, |
|
337 Zend_Controller_Response_Abstract $response = null) |
|
338 { |
|
339 if ($params === null) { |
|
340 if ($_SERVER["REQUEST_METHOD"] == "GET") { |
|
341 $params = $_GET; |
|
342 } else if ($_SERVER["REQUEST_METHOD"] == "POST") { |
|
343 $params = $_POST; |
|
344 } else { |
|
345 return false; |
|
346 } |
|
347 } |
|
348 $version = 1.1; |
|
349 if (isset($params['openid_ns']) && |
|
350 $params['openid_ns'] == Zend_OpenId::NS_2_0) { |
|
351 $version = 2.0; |
|
352 } |
|
353 if (isset($params['openid_mode'])) { |
|
354 if ($params['openid_mode'] == 'associate') { |
|
355 $response = $this->_associate($version, $params); |
|
356 $ret = ''; |
|
357 foreach ($response as $key => $val) { |
|
358 $ret .= $key . ':' . $val . "\n"; |
|
359 } |
|
360 return $ret; |
|
361 } else if ($params['openid_mode'] == 'checkid_immediate') { |
|
362 $ret = $this->_checkId($version, $params, 1, $extensions, $response); |
|
363 if (is_bool($ret)) return $ret; |
|
364 if (!empty($params['openid_return_to'])) { |
|
365 Zend_OpenId::redirect($params['openid_return_to'], $ret, $response); |
|
366 } |
|
367 return true; |
|
368 } else if ($params['openid_mode'] == 'checkid_setup') { |
|
369 $ret = $this->_checkId($version, $params, 0, $extensions, $response); |
|
370 if (is_bool($ret)) return $ret; |
|
371 if (!empty($params['openid_return_to'])) { |
|
372 Zend_OpenId::redirect($params['openid_return_to'], $ret, $response); |
|
373 } |
|
374 return true; |
|
375 } else if ($params['openid_mode'] == 'check_authentication') { |
|
376 $response = $this->_checkAuthentication($version, $params); |
|
377 $ret = ''; |
|
378 foreach ($response as $key => $val) { |
|
379 $ret .= $key . ':' . $val . "\n"; |
|
380 } |
|
381 return $ret; |
|
382 } |
|
383 } |
|
384 return false; |
|
385 } |
|
386 |
|
387 /** |
|
388 * Generates a secret key for given hash function, returns RAW key or false |
|
389 * if function is not supported |
|
390 * |
|
391 * @param string $func hash function (sha1 or sha256) |
|
392 * @return mixed |
|
393 */ |
|
394 protected function _genSecret($func) |
|
395 { |
|
396 if ($func == 'sha1') { |
|
397 $macLen = 20; /* 160 bit */ |
|
398 } else if ($func == 'sha256') { |
|
399 $macLen = 32; /* 256 bit */ |
|
400 } else { |
|
401 return false; |
|
402 } |
|
403 return Zend_OpenId::randomBytes($macLen); |
|
404 } |
|
405 |
|
406 /** |
|
407 * Processes association request from OpenID consumerm generates secret |
|
408 * shared key and send it back using Diffie-Hellman encruption. |
|
409 * Returns array of variables to push back to consumer. |
|
410 * |
|
411 * @param float $version OpenID version |
|
412 * @param array $params GET or POST request variables |
|
413 * @return array |
|
414 */ |
|
415 protected function _associate($version, $params) |
|
416 { |
|
417 $ret = array(); |
|
418 |
|
419 if ($version >= 2.0) { |
|
420 $ret['ns'] = Zend_OpenId::NS_2_0; |
|
421 } |
|
422 |
|
423 if (isset($params['openid_assoc_type']) && |
|
424 $params['openid_assoc_type'] == 'HMAC-SHA1') { |
|
425 $macFunc = 'sha1'; |
|
426 } else if (isset($params['openid_assoc_type']) && |
|
427 $params['openid_assoc_type'] == 'HMAC-SHA256' && |
|
428 $version >= 2.0) { |
|
429 $macFunc = 'sha256'; |
|
430 } else { |
|
431 $ret['error'] = 'Wrong "openid.assoc_type"'; |
|
432 $ret['error-code'] = 'unsupported-type'; |
|
433 return $ret; |
|
434 } |
|
435 |
|
436 $ret['assoc_type'] = $params['openid_assoc_type']; |
|
437 |
|
438 $secret = $this->_genSecret($macFunc); |
|
439 |
|
440 if (empty($params['openid_session_type']) || |
|
441 $params['openid_session_type'] == 'no-encryption') { |
|
442 $ret['mac_key'] = base64_encode($secret); |
|
443 } else if (isset($params['openid_session_type']) && |
|
444 $params['openid_session_type'] == 'DH-SHA1') { |
|
445 $dhFunc = 'sha1'; |
|
446 } else if (isset($params['openid_session_type']) && |
|
447 $params['openid_session_type'] == 'DH-SHA256' && |
|
448 $version >= 2.0) { |
|
449 $dhFunc = 'sha256'; |
|
450 } else { |
|
451 $ret['error'] = 'Wrong "openid.session_type"'; |
|
452 $ret['error-code'] = 'unsupported-type'; |
|
453 return $ret; |
|
454 } |
|
455 |
|
456 if (isset($params['openid_session_type'])) { |
|
457 $ret['session_type'] = $params['openid_session_type']; |
|
458 } |
|
459 |
|
460 if (isset($dhFunc)) { |
|
461 if (empty($params['openid_dh_consumer_public'])) { |
|
462 $ret['error'] = 'Wrong "openid.dh_consumer_public"'; |
|
463 return $ret; |
|
464 } |
|
465 if (empty($params['openid_dh_gen'])) { |
|
466 $g = pack('H*', Zend_OpenId::DH_G); |
|
467 } else { |
|
468 $g = base64_decode($params['openid_dh_gen']); |
|
469 } |
|
470 if (empty($params['openid_dh_modulus'])) { |
|
471 $p = pack('H*', Zend_OpenId::DH_P); |
|
472 } else { |
|
473 $p = base64_decode($params['openid_dh_modulus']); |
|
474 } |
|
475 |
|
476 $dh = Zend_OpenId::createDhKey($p, $g); |
|
477 $dh_details = Zend_OpenId::getDhKeyDetails($dh); |
|
478 |
|
479 $sec = Zend_OpenId::computeDhSecret( |
|
480 base64_decode($params['openid_dh_consumer_public']), $dh); |
|
481 if ($sec === false) { |
|
482 $ret['error'] = 'Wrong "openid.session_type"'; |
|
483 $ret['error-code'] = 'unsupported-type'; |
|
484 return $ret; |
|
485 } |
|
486 $sec = Zend_OpenId::digest($dhFunc, $sec); |
|
487 $ret['dh_server_public'] = base64_encode( |
|
488 Zend_OpenId::btwoc($dh_details['pub_key'])); |
|
489 $ret['enc_mac_key'] = base64_encode($secret ^ $sec); |
|
490 } |
|
491 |
|
492 $handle = uniqid(); |
|
493 $expiresIn = $this->_sessionTtl; |
|
494 |
|
495 $ret['assoc_handle'] = $handle; |
|
496 $ret['expires_in'] = $expiresIn; |
|
497 |
|
498 $this->_storage->addAssociation($handle, |
|
499 $macFunc, $secret, time() + $expiresIn); |
|
500 |
|
501 return $ret; |
|
502 } |
|
503 |
|
504 /** |
|
505 * Performs authentication (or authentication check). |
|
506 * |
|
507 * @param float $version OpenID version |
|
508 * @param array $params GET or POST request variables |
|
509 * @param bool $immediate enables or disables interaction with user |
|
510 * @param mixed $extensions extension object or array of extensions objects |
|
511 * @param Zend_Controller_Response_Abstract $response |
|
512 * @return array |
|
513 */ |
|
514 protected function _checkId($version, $params, $immediate, $extensions=null, |
|
515 Zend_Controller_Response_Abstract $response = null) |
|
516 { |
|
517 $ret = array(); |
|
518 |
|
519 if ($version >= 2.0) { |
|
520 $ret['openid.ns'] = Zend_OpenId::NS_2_0; |
|
521 } |
|
522 $root = $this->getSiteRoot($params); |
|
523 if ($root === false) { |
|
524 return false; |
|
525 } |
|
526 |
|
527 if (isset($params['openid_identity']) && |
|
528 !$this->_storage->hasUser($params['openid_identity'])) { |
|
529 $ret['openid.mode'] = ($immediate && $version >= 2.0) ? 'setup_needed': 'cancel'; |
|
530 return $ret; |
|
531 } |
|
532 |
|
533 /* Check if user already logged in into the server */ |
|
534 if (!isset($params['openid_identity']) || |
|
535 $this->_user->getLoggedInUser() !== $params['openid_identity']) { |
|
536 $params2 = array(); |
|
537 foreach ($params as $key => $val) { |
|
538 if (strpos($key, 'openid_ns_') === 0) { |
|
539 $key = 'openid.ns.' . substr($key, strlen('openid_ns_')); |
|
540 } else if (strpos($key, 'openid_sreg_') === 0) { |
|
541 $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_')); |
|
542 } else if (strpos($key, 'openid_') === 0) { |
|
543 $key = 'openid.' . substr($key, strlen('openid_')); |
|
544 } |
|
545 $params2[$key] = $val; |
|
546 } |
|
547 if ($immediate) { |
|
548 $params2['openid.mode'] = 'checkid_setup'; |
|
549 $ret['openid.mode'] = ($version >= 2.0) ? 'setup_needed': 'id_res'; |
|
550 $ret['openid.user_setup_url'] = $this->_loginUrl |
|
551 . (strpos($this->_loginUrl, '?') === false ? '?' : '&') |
|
552 . Zend_OpenId::paramsToQuery($params2); |
|
553 return $ret; |
|
554 } else { |
|
555 /* Redirect to Server Login Screen */ |
|
556 Zend_OpenId::redirect($this->_loginUrl, $params2, $response); |
|
557 return true; |
|
558 } |
|
559 } |
|
560 |
|
561 if (!Zend_OpenId_Extension::forAll($extensions, 'parseRequest', $params)) { |
|
562 $ret['openid.mode'] = ($immediate && $version >= 2.0) ? 'setup_needed': 'cancel'; |
|
563 return $ret; |
|
564 } |
|
565 |
|
566 /* Check if user trusts to the consumer */ |
|
567 $trusted = null; |
|
568 $sites = $this->_storage->getTrustedSites($params['openid_identity']); |
|
569 if (isset($params['openid_return_to'])) { |
|
570 $root = $params['openid_return_to']; |
|
571 } |
|
572 if (isset($sites[$root])) { |
|
573 $trusted = $sites[$root]; |
|
574 } else { |
|
575 foreach ($sites as $site => $t) { |
|
576 if (strpos($root, $site) === 0) { |
|
577 $trusted = $t; |
|
578 break; |
|
579 } else { |
|
580 /* OpenID 2.0 (9.2) check for realm wild-card matching */ |
|
581 $n = strpos($site, '://*.'); |
|
582 if ($n != false) { |
|
583 $regex = '/^' |
|
584 . preg_quote(substr($site, 0, $n+3), '/') |
|
585 . '[A-Za-z1-9_\.]+?' |
|
586 . preg_quote(substr($site, $n+4), '/') |
|
587 . '/'; |
|
588 if (preg_match($regex, $root)) { |
|
589 $trusted = $t; |
|
590 break; |
|
591 } |
|
592 } |
|
593 } |
|
594 } |
|
595 } |
|
596 |
|
597 if (is_array($trusted)) { |
|
598 if (!Zend_OpenId_Extension::forAll($extensions, 'checkTrustData', $trusted)) { |
|
599 $trusted = null; |
|
600 } |
|
601 } |
|
602 |
|
603 if ($trusted === false) { |
|
604 $ret['openid.mode'] = 'cancel'; |
|
605 return $ret; |
|
606 } else if ($trusted === null) { |
|
607 /* Redirect to Server Trust Screen */ |
|
608 $params2 = array(); |
|
609 foreach ($params as $key => $val) { |
|
610 if (strpos($key, 'openid_ns_') === 0) { |
|
611 $key = 'openid.ns.' . substr($key, strlen('openid_ns_')); |
|
612 } else if (strpos($key, 'openid_sreg_') === 0) { |
|
613 $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_')); |
|
614 } else if (strpos($key, 'openid_') === 0) { |
|
615 $key = 'openid.' . substr($key, strlen('openid_')); |
|
616 } |
|
617 $params2[$key] = $val; |
|
618 } |
|
619 if ($immediate) { |
|
620 $params2['openid.mode'] = 'checkid_setup'; |
|
621 $ret['openid.mode'] = ($version >= 2.0) ? 'setup_needed': 'id_res'; |
|
622 $ret['openid.user_setup_url'] = $this->_trustUrl |
|
623 . (strpos($this->_trustUrl, '?') === false ? '?' : '&') |
|
624 . Zend_OpenId::paramsToQuery($params2); |
|
625 return $ret; |
|
626 } else { |
|
627 Zend_OpenId::redirect($this->_trustUrl, $params2, $response); |
|
628 return true; |
|
629 } |
|
630 } |
|
631 |
|
632 return $this->_respond($version, $ret, $params, $extensions); |
|
633 } |
|
634 |
|
635 /** |
|
636 * Perepares information to send back to consumer's authentication request, |
|
637 * signs it using shared secret and send back through HTTP redirection |
|
638 * |
|
639 * @param array $params GET or POST request variables |
|
640 * @param mixed $extensions extension object or array of extensions objects |
|
641 * @param Zend_Controller_Response_Abstract $response an optional response |
|
642 * object to perform HTTP or HTML form redirection |
|
643 * @return bool |
|
644 */ |
|
645 public function respondToConsumer($params, $extensions=null, |
|
646 Zend_Controller_Response_Abstract $response = null) |
|
647 { |
|
648 $version = 1.1; |
|
649 if (isset($params['openid_ns']) && |
|
650 $params['openid_ns'] == Zend_OpenId::NS_2_0) { |
|
651 $version = 2.0; |
|
652 } |
|
653 $ret = array(); |
|
654 if ($version >= 2.0) { |
|
655 $ret['openid.ns'] = Zend_OpenId::NS_2_0; |
|
656 } |
|
657 $ret = $this->_respond($version, $ret, $params, $extensions); |
|
658 if (!empty($params['openid_return_to'])) { |
|
659 Zend_OpenId::redirect($params['openid_return_to'], $ret, $response); |
|
660 } |
|
661 return true; |
|
662 } |
|
663 |
|
664 /** |
|
665 * Perepares information to send back to consumer's authentication request |
|
666 * and signs it using shared secret. |
|
667 * |
|
668 * @param float $version OpenID protcol version |
|
669 * @param array $ret arguments to be send back to consumer |
|
670 * @param array $params GET or POST request variables |
|
671 * @param mixed $extensions extension object or array of extensions objects |
|
672 * @return array |
|
673 */ |
|
674 protected function _respond($version, $ret, $params, $extensions=null) |
|
675 { |
|
676 if (empty($params['openid_assoc_handle']) || |
|
677 !$this->_storage->getAssociation($params['openid_assoc_handle'], |
|
678 $macFunc, $secret, $expires)) { |
|
679 /* Use dumb mode */ |
|
680 if (!empty($params['openid_assoc_handle'])) { |
|
681 $ret['openid.invalidate_handle'] = $params['openid_assoc_handle']; |
|
682 } |
|
683 $macFunc = $version >= 2.0 ? 'sha256' : 'sha1'; |
|
684 $secret = $this->_genSecret($macFunc); |
|
685 $handle = uniqid(); |
|
686 $expiresIn = $this->_sessionTtl; |
|
687 $this->_storage->addAssociation($handle, |
|
688 $macFunc, $secret, time() + $expiresIn); |
|
689 $ret['openid.assoc_handle'] = $handle; |
|
690 } else { |
|
691 $ret['openid.assoc_handle'] = $params['openid_assoc_handle']; |
|
692 } |
|
693 if (isset($params['openid_return_to'])) { |
|
694 $ret['openid.return_to'] = $params['openid_return_to']; |
|
695 } |
|
696 if (isset($params['openid_claimed_id'])) { |
|
697 $ret['openid.claimed_id'] = $params['openid_claimed_id']; |
|
698 } |
|
699 if (isset($params['openid_identity'])) { |
|
700 $ret['openid.identity'] = $params['openid_identity']; |
|
701 } |
|
702 |
|
703 if ($version >= 2.0) { |
|
704 if (!empty($this->_opEndpoint)) { |
|
705 $ret['openid.op_endpoint'] = $this->_opEndpoint; |
|
706 } else { |
|
707 $ret['openid.op_endpoint'] = Zend_OpenId::selfUrl(); |
|
708 } |
|
709 } |
|
710 $ret['openid.response_nonce'] = gmdate('Y-m-d\TH:i:s\Z') . uniqid(); |
|
711 $ret['openid.mode'] = 'id_res'; |
|
712 |
|
713 Zend_OpenId_Extension::forAll($extensions, 'prepareResponse', $ret); |
|
714 |
|
715 $signed = ''; |
|
716 $data = ''; |
|
717 foreach ($ret as $key => $val) { |
|
718 if (strpos($key, 'openid.') === 0) { |
|
719 $key = substr($key, strlen('openid.')); |
|
720 if (!empty($signed)) { |
|
721 $signed .= ','; |
|
722 } |
|
723 $signed .= $key; |
|
724 $data .= $key . ':' . $val . "\n"; |
|
725 } |
|
726 } |
|
727 $signed .= ',signed'; |
|
728 $data .= 'signed:' . $signed . "\n"; |
|
729 $ret['openid.signed'] = $signed; |
|
730 |
|
731 $ret['openid.sig'] = base64_encode( |
|
732 Zend_OpenId::hashHmac($macFunc, $data, $secret)); |
|
733 |
|
734 return $ret; |
|
735 } |
|
736 |
|
737 /** |
|
738 * Performs authentication validation for dumb consumers |
|
739 * Returns array of variables to push back to consumer. |
|
740 * It MUST contain 'is_valid' variable with value 'true' or 'false'. |
|
741 * |
|
742 * @param float $version OpenID version |
|
743 * @param array $params GET or POST request variables |
|
744 * @return array |
|
745 */ |
|
746 protected function _checkAuthentication($version, $params) |
|
747 { |
|
748 $ret = array(); |
|
749 if ($version >= 2.0) { |
|
750 $ret['ns'] = Zend_OpenId::NS_2_0; |
|
751 } |
|
752 $ret['openid.mode'] = 'id_res'; |
|
753 |
|
754 if (empty($params['openid_assoc_handle']) || |
|
755 empty($params['openid_signed']) || |
|
756 empty($params['openid_sig']) || |
|
757 !$this->_storage->getAssociation($params['openid_assoc_handle'], |
|
758 $macFunc, $secret, $expires)) { |
|
759 $ret['is_valid'] = 'false'; |
|
760 return $ret; |
|
761 } |
|
762 |
|
763 $signed = explode(',', $params['openid_signed']); |
|
764 $data = ''; |
|
765 foreach ($signed as $key) { |
|
766 $data .= $key . ':'; |
|
767 if ($key == 'mode') { |
|
768 $data .= "id_res\n"; |
|
769 } else { |
|
770 $data .= $params['openid_' . strtr($key,'.','_')]."\n"; |
|
771 } |
|
772 } |
|
773 if ($this->_secureStringCompare(base64_decode($params['openid_sig']), |
|
774 Zend_OpenId::hashHmac($macFunc, $data, $secret))) { |
|
775 $ret['is_valid'] = 'true'; |
|
776 } else { |
|
777 $ret['is_valid'] = 'false'; |
|
778 } |
|
779 return $ret; |
|
780 } |
|
781 |
|
782 /** |
|
783 * Securely compare two strings for equality while avoided C level memcmp() |
|
784 * optimisations capable of leaking timing information useful to an attacker |
|
785 * attempting to iteratively guess the unknown string (e.g. password) being |
|
786 * compared against. |
|
787 * |
|
788 * @param string $a |
|
789 * @param string $b |
|
790 * @return bool |
|
791 */ |
|
792 protected function _secureStringCompare($a, $b) |
|
793 { |
|
794 if (strlen($a) !== strlen($b)) { |
|
795 return false; |
|
796 } |
|
797 $result = 0; |
|
798 for ($i = 0; $i < strlen($a); $i++) { |
|
799 $result |= ord($a[$i]) ^ ord($b[$i]); |
|
800 } |
|
801 return $result == 0; |
|
802 } |
|
803 } |