PageRenderTime 125ms CodeModel.GetById 64ms RepoModel.GetById 25ms app.codeStats 0ms

/OpenId/Provider.php

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