PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/openid/Auth/OpenID/Consumer.php

https://bitbucket.org/asosso/joomla15
PHP | 2232 lines | 1121 code | 279 blank | 832 comment | 192 complexity | cd2c3a4082d53698aebb2aa0d7e8260a MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * This module documents the main interface with the OpenID consumer
  4. * library. The only part of the library which has to be used and
  5. * isn't documented in full here is the store required to create an
  6. * Auth_OpenID_Consumer instance. More on the abstract store type and
  7. * concrete implementations of it that are provided in the
  8. * documentation for the Auth_OpenID_Consumer constructor.
  9. *
  10. * OVERVIEW
  11. *
  12. * The OpenID identity verification process most commonly uses the
  13. * following steps, as visible to the user of this library:
  14. *
  15. * 1. The user enters their OpenID into a field on the consumer's
  16. * site, and hits a login button.
  17. * 2. The consumer site discovers the user's OpenID server using the
  18. * YADIS protocol.
  19. * 3. The consumer site sends the browser a redirect to the identity
  20. * server. This is the authentication request as described in
  21. * the OpenID specification.
  22. * 4. The identity server's site sends the browser a redirect back
  23. * to the consumer site. This redirect contains the server's
  24. * response to the authentication request.
  25. *
  26. * The most important part of the flow to note is the consumer's site
  27. * must handle two separate HTTP requests in order to perform the full
  28. * identity check.
  29. *
  30. * LIBRARY DESIGN
  31. *
  32. * This consumer library is designed with that flow in mind. The goal
  33. * is to make it as easy as possible to perform the above steps
  34. * securely.
  35. *
  36. * At a high level, there are two important parts in the consumer
  37. * library. The first important part is this module, which contains
  38. * the interface to actually use this library. The second is the
  39. * Auth_OpenID_Interface class, which describes the interface to use
  40. * if you need to create a custom method for storing the state this
  41. * library needs to maintain between requests.
  42. *
  43. * In general, the second part is less important for users of the
  44. * library to know about, as several implementations are provided
  45. * which cover a wide variety of situations in which consumers may use
  46. * the library.
  47. *
  48. * This module contains a class, Auth_OpenID_Consumer, with methods
  49. * corresponding to the actions necessary in each of steps 2, 3, and 4
  50. * described in the overview. Use of this library should be as easy
  51. * as creating an Auth_OpenID_Consumer instance and calling the
  52. * methods appropriate for the action the site wants to take.
  53. *
  54. * STORES AND DUMB MODE
  55. *
  56. * OpenID is a protocol that works best when the consumer site is able
  57. * to store some state. This is the normal mode of operation for the
  58. * protocol, and is sometimes referred to as smart mode. There is
  59. * also a fallback mode, known as dumb mode, which is available when
  60. * the consumer site is not able to store state. This mode should be
  61. * avoided when possible, as it leaves the implementation more
  62. * vulnerable to replay attacks.
  63. *
  64. * The mode the library works in for normal operation is determined by
  65. * the store that it is given. The store is an abstraction that
  66. * handles the data that the consumer needs to manage between http
  67. * requests in order to operate efficiently and securely.
  68. *
  69. * Several store implementation are provided, and the interface is
  70. * fully documented so that custom stores can be used as well. See
  71. * the documentation for the Auth_OpenID_Consumer class for more
  72. * information on the interface for stores. The implementations that
  73. * are provided allow the consumer site to store the necessary data in
  74. * several different ways, including several SQL databases and normal
  75. * files on disk.
  76. *
  77. * There is an additional concrete store provided that puts the system
  78. * in dumb mode. This is not recommended, as it removes the library's
  79. * ability to stop replay attacks reliably. It still uses time-based
  80. * checking to make replay attacks only possible within a small
  81. * window, but they remain possible within that window. This store
  82. * should only be used if the consumer site has no way to retain data
  83. * between requests at all.
  84. *
  85. * IMMEDIATE MODE
  86. *
  87. * In the flow described above, the user may need to confirm to the
  88. * lidentity server that it's ok to authorize his or her identity.
  89. * The server may draw pages asking for information from the user
  90. * before it redirects the browser back to the consumer's site. This
  91. * is generally transparent to the consumer site, so it is typically
  92. * ignored as an implementation detail.
  93. *
  94. * There can be times, however, where the consumer site wants to get a
  95. * response immediately. When this is the case, the consumer can put
  96. * the library in immediate mode. In immediate mode, there is an
  97. * extra response possible from the server, which is essentially the
  98. * server reporting that it doesn't have enough information to answer
  99. * the question yet.
  100. *
  101. * USING THIS LIBRARY
  102. *
  103. * Integrating this library into an application is usually a
  104. * relatively straightforward process. The process should basically
  105. * follow this plan:
  106. *
  107. * Add an OpenID login field somewhere on your site. When an OpenID
  108. * is entered in that field and the form is submitted, it should make
  109. * a request to the your site which includes that OpenID URL.
  110. *
  111. * First, the application should instantiate the Auth_OpenID_Consumer
  112. * class using the store of choice (Auth_OpenID_FileStore or one of
  113. * the SQL-based stores). If the application has a custom
  114. * session-management implementation, an object implementing the
  115. * {@link Auth_Yadis_PHPSession} interface should be passed as the
  116. * second parameter. Otherwise, the default uses $_SESSION.
  117. *
  118. * Next, the application should call the Auth_OpenID_Consumer object's
  119. * 'begin' method. This method takes the OpenID URL. The 'begin'
  120. * method returns an Auth_OpenID_AuthRequest object.
  121. *
  122. * Next, the application should call the 'redirectURL' method of the
  123. * Auth_OpenID_AuthRequest object. The 'return_to' URL parameter is
  124. * the URL that the OpenID server will send the user back to after
  125. * attempting to verify his or her identity. The 'trust_root' is the
  126. * URL (or URL pattern) that identifies your web site to the user when
  127. * he or she is authorizing it. Send a redirect to the resulting URL
  128. * to the user's browser.
  129. *
  130. * That's the first half of the authentication process. The second
  131. * half of the process is done after the user's ID server sends the
  132. * user's browser a redirect back to your site to complete their
  133. * login.
  134. *
  135. * When that happens, the user will contact your site at the URL given
  136. * as the 'return_to' URL to the Auth_OpenID_AuthRequest::redirectURL
  137. * call made above. The request will have several query parameters
  138. * added to the URL by the identity server as the information
  139. * necessary to finish the request.
  140. *
  141. * Lastly, instantiate an Auth_OpenID_Consumer instance as above and
  142. * call its 'complete' method, passing in all the received query
  143. * arguments.
  144. *
  145. * There are multiple possible return types possible from that
  146. * method. These indicate the whether or not the login was successful,
  147. * and include any additional information appropriate for their type.
  148. *
  149. * PHP versions 4 and 5
  150. *
  151. * LICENSE: See the COPYING file included in this distribution.
  152. *
  153. * @package OpenID
  154. * @author JanRain, Inc. <openid@janrain.com>
  155. * @copyright 2005-2008 Janrain, Inc.
  156. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  157. */
  158. // Do not allow direct access
  159. defined( '_JEXEC' ) or die( 'Restricted access' );
  160. /**
  161. * Require utility classes and functions for the consumer.
  162. */
  163. require_once "Auth/OpenID.php";
  164. require_once "Auth/OpenID/Message.php";
  165. require_once "Auth/OpenID/HMAC.php";
  166. require_once "Auth/OpenID/Association.php";
  167. require_once "Auth/OpenID/CryptUtil.php";
  168. require_once "Auth/OpenID/DiffieHellman.php";
  169. require_once "Auth/OpenID/KVForm.php";
  170. require_once "Auth/OpenID/Nonce.php";
  171. require_once "Auth/OpenID/Discover.php";
  172. require_once "Auth/OpenID/URINorm.php";
  173. require_once "Auth/Yadis/Manager.php";
  174. require_once "Auth/Yadis/XRI.php";
  175. /**
  176. * This is the status code returned when the complete method returns
  177. * successfully.
  178. */
  179. define('Auth_OpenID_SUCCESS', 'success');
  180. /**
  181. * Status to indicate cancellation of OpenID authentication.
  182. */
  183. define('Auth_OpenID_CANCEL', 'cancel');
  184. /**
  185. * This is the status code completeAuth returns when the value it
  186. * received indicated an invalid login.
  187. */
  188. define('Auth_OpenID_FAILURE', 'failure');
  189. /**
  190. * This is the status code completeAuth returns when the
  191. * {@link Auth_OpenID_Consumer} instance is in immediate mode, and the
  192. * identity server sends back a URL to send the user to to complete his
  193. * or her login.
  194. */
  195. define('Auth_OpenID_SETUP_NEEDED', 'setup needed');
  196. /**
  197. * This is the status code beginAuth returns when the page fetched
  198. * from the entered OpenID URL doesn't contain the necessary link tags
  199. * to function as an identity page.
  200. */
  201. define('Auth_OpenID_PARSE_ERROR', 'parse error');
  202. /**
  203. * An OpenID consumer implementation that performs discovery and does
  204. * session management. See the Consumer.php file documentation for
  205. * more information.
  206. *
  207. * @package OpenID
  208. */
  209. class Auth_OpenID_Consumer {
  210. /**
  211. * @access private
  212. */
  213. var $discoverMethod = 'Auth_OpenID_discover';
  214. /**
  215. * @access private
  216. */
  217. var $session_key_prefix = "_openid_consumer_";
  218. /**
  219. * @access private
  220. */
  221. var $_token_suffix = "last_token";
  222. /**
  223. * Initialize a Consumer instance.
  224. *
  225. * You should create a new instance of the Consumer object with
  226. * every HTTP request that handles OpenID transactions.
  227. *
  228. * @param Auth_OpenID_OpenIDStore $store This must be an object
  229. * that implements the interface in {@link
  230. * Auth_OpenID_OpenIDStore}. Several concrete implementations are
  231. * provided, to cover most common use cases. For stores backed by
  232. * MySQL, PostgreSQL, or SQLite, see the {@link
  233. * Auth_OpenID_SQLStore} class and its sublcasses. For a
  234. * filesystem-backed store, see the {@link Auth_OpenID_FileStore}
  235. * module. As a last resort, if it isn't possible for the server
  236. * to store state at all, an instance of {@link
  237. * Auth_OpenID_DumbStore} can be used.
  238. *
  239. * @param mixed $session An object which implements the interface
  240. * of the {@link Auth_Yadis_PHPSession} class. Particularly, this
  241. * object is expected to have these methods: get($key), set($key),
  242. * $value), and del($key). This defaults to a session object
  243. * which wraps PHP's native session machinery. You should only
  244. * need to pass something here if you have your own sessioning
  245. * implementation.
  246. *
  247. * @param str $consumer_cls The name of the class to instantiate
  248. * when creating the internal consumer object. This is used for
  249. * testing.
  250. */
  251. function Auth_OpenID_Consumer(&$store, $session = null,
  252. $consumer_cls = null)
  253. {
  254. if ($session === null) {
  255. $session = new Auth_Yadis_PHPSession();
  256. }
  257. $this->session =& $session;
  258. if ($consumer_cls !== null) {
  259. $this->consumer =& new $consumer_cls($store);
  260. } else {
  261. $this->consumer =& new Auth_OpenID_GenericConsumer($store);
  262. }
  263. $this->_token_key = $this->session_key_prefix . $this->_token_suffix;
  264. }
  265. /**
  266. * Used in testing to define the discovery mechanism.
  267. *
  268. * @access private
  269. */
  270. function getDiscoveryObject(&$session, $openid_url,
  271. $session_key_prefix)
  272. {
  273. return new Auth_Yadis_Discovery($session, $openid_url,
  274. $session_key_prefix);
  275. }
  276. /**
  277. * Start the OpenID authentication process. See steps 1-2 in the
  278. * overview at the top of this file.
  279. *
  280. * @param string $user_url Identity URL given by the user. This
  281. * method performs a textual transformation of the URL to try and
  282. * make sure it is normalized. For example, a user_url of
  283. * example.com will be normalized to http://example.com/
  284. * normalizing and resolving any redirects the server might issue.
  285. *
  286. * @param bool $anonymous True if the OpenID request is to be sent
  287. * to the server without any identifier information. Use this
  288. * when you want to transport data but don't want to do OpenID
  289. * authentication with identifiers.
  290. *
  291. * @return Auth_OpenID_AuthRequest $auth_request An object
  292. * containing the discovered information will be returned, with a
  293. * method for building a redirect URL to the server, as described
  294. * in step 3 of the overview. This object may also be used to add
  295. * extension arguments to the request, using its 'addExtensionArg'
  296. * method.
  297. */
  298. function begin($user_url, $anonymous=false)
  299. {
  300. $openid_url = $user_url;
  301. $disco = $this->getDiscoveryObject($this->session,
  302. $openid_url,
  303. $this->session_key_prefix);
  304. // Set the 'stale' attribute of the manager. If discovery
  305. // fails in a fatal way, the stale flag will cause the manager
  306. // to be cleaned up next time discovery is attempted.
  307. $m = $disco->getManager();
  308. $loader = new Auth_Yadis_ManagerLoader();
  309. if ($m) {
  310. if ($m->stale) {
  311. $disco->destroyManager();
  312. } else {
  313. $m->stale = true;
  314. $disco->session->set($disco->session_key,
  315. serialize($loader->toSession($m)));
  316. }
  317. }
  318. $endpoint = $disco->getNextService($this->discoverMethod,
  319. $this->consumer->fetcher);
  320. // Reset the 'stale' attribute of the manager.
  321. $m =& $disco->getManager();
  322. if ($m) {
  323. $m->stale = false;
  324. $disco->session->set($disco->session_key,
  325. serialize($loader->toSession($m)));
  326. }
  327. if ($endpoint === null) {
  328. return null;
  329. } else {
  330. return $this->beginWithoutDiscovery($endpoint,
  331. $anonymous);
  332. }
  333. }
  334. /**
  335. * Start OpenID verification without doing OpenID server
  336. * discovery. This method is used internally by Consumer.begin
  337. * after discovery is performed, and exists to provide an
  338. * interface for library users needing to perform their own
  339. * discovery.
  340. *
  341. * @param Auth_OpenID_ServiceEndpoint $endpoint an OpenID service
  342. * endpoint descriptor.
  343. *
  344. * @param bool anonymous Set to true if you want to perform OpenID
  345. * without identifiers.
  346. *
  347. * @return Auth_OpenID_AuthRequest $auth_request An OpenID
  348. * authentication request object.
  349. */
  350. function &beginWithoutDiscovery($endpoint, $anonymous=false)
  351. {
  352. $loader = new Auth_OpenID_ServiceEndpointLoader();
  353. $auth_req = $this->consumer->begin($endpoint);
  354. $this->session->set($this->_token_key,
  355. $loader->toSession($auth_req->endpoint));
  356. if (!$auth_req->setAnonymous($anonymous)) {
  357. return new Auth_OpenID_FailureResponse(null,
  358. "OpenID 1 requests MUST include the identifier " .
  359. "in the request.");
  360. }
  361. return $auth_req;
  362. }
  363. /**
  364. * Called to interpret the server's response to an OpenID
  365. * request. It is called in step 4 of the flow described in the
  366. * consumer overview.
  367. *
  368. * @param string $current_url The URL used to invoke the application.
  369. * Extract the URL from your application's web
  370. * request framework and specify it here to have it checked
  371. * against the openid.current_url value in the response. If
  372. * the current_url URL check fails, the status of the
  373. * completion will be FAILURE.
  374. *
  375. * @param array $query An array of the query parameters (key =>
  376. * value pairs) for this HTTP request. Defaults to null. If
  377. * null, the GET or POST data are automatically gotten from the
  378. * PHP environment. It is only useful to override $query for
  379. * testing.
  380. *
  381. * @return Auth_OpenID_ConsumerResponse $response A instance of an
  382. * Auth_OpenID_ConsumerResponse subclass. The type of response is
  383. * indicated by the status attribute, which will be one of
  384. * SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
  385. */
  386. function complete($current_url, $query=null)
  387. {
  388. if ($current_url && !is_string($current_url)) {
  389. // This is ugly, but we need to complain loudly when
  390. // someone uses the API incorrectly.
  391. trigger_error("current_url must be a string; see NEWS file " .
  392. "for upgrading notes.",
  393. E_USER_ERROR);
  394. }
  395. if ($query === null) {
  396. $query = Auth_OpenID::getQuery();
  397. }
  398. $loader = new Auth_OpenID_ServiceEndpointLoader();
  399. $endpoint_data = $this->session->get($this->_token_key);
  400. $endpoint =
  401. $loader->fromSession($endpoint_data);
  402. $message = Auth_OpenID_Message::fromPostArgs($query);
  403. $response = $this->consumer->complete($message, $endpoint,
  404. $current_url);
  405. $this->session->del($this->_token_key);
  406. if (in_array($response->status, array(Auth_OpenID_SUCCESS,
  407. Auth_OpenID_CANCEL))) {
  408. if ($response->identity_url !== null) {
  409. $disco = $this->getDiscoveryObject($this->session,
  410. $response->identity_url,
  411. $this->session_key_prefix);
  412. $disco->cleanup(true);
  413. }
  414. }
  415. return $response;
  416. }
  417. }
  418. /**
  419. * A class implementing HMAC/DH-SHA1 consumer sessions.
  420. *
  421. * @package OpenID
  422. */
  423. class Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
  424. var $session_type = 'DH-SHA1';
  425. var $hash_func = 'Auth_OpenID_SHA1';
  426. var $secret_size = 20;
  427. var $allowed_assoc_types = array('HMAC-SHA1');
  428. function Auth_OpenID_DiffieHellmanSHA1ConsumerSession($dh = null)
  429. {
  430. if ($dh === null) {
  431. $dh = new Auth_OpenID_DiffieHellman();
  432. }
  433. $this->dh = $dh;
  434. }
  435. function getRequest()
  436. {
  437. $math =& Auth_OpenID_getMathLib();
  438. $cpub = $math->longToBase64($this->dh->public);
  439. $args = array('dh_consumer_public' => $cpub);
  440. if (!$this->dh->usingDefaultValues()) {
  441. $args = array_merge($args, array(
  442. 'dh_modulus' =>
  443. $math->longToBase64($this->dh->mod),
  444. 'dh_gen' =>
  445. $math->longToBase64($this->dh->gen)));
  446. }
  447. return $args;
  448. }
  449. function extractSecret($response)
  450. {
  451. if (!$response->hasKey(Auth_OpenID_OPENID_NS,
  452. 'dh_server_public')) {
  453. return null;
  454. }
  455. if (!$response->hasKey(Auth_OpenID_OPENID_NS,
  456. 'enc_mac_key')) {
  457. return null;
  458. }
  459. $math =& Auth_OpenID_getMathLib();
  460. $spub = $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS,
  461. 'dh_server_public'));
  462. $enc_mac_key = base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
  463. 'enc_mac_key'));
  464. return $this->dh->xorSecret($spub, $enc_mac_key, $this->hash_func);
  465. }
  466. }
  467. /**
  468. * A class implementing HMAC/DH-SHA256 consumer sessions.
  469. *
  470. * @package OpenID
  471. */
  472. class Auth_OpenID_DiffieHellmanSHA256ConsumerSession extends
  473. Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
  474. var $session_type = 'DH-SHA256';
  475. var $hash_func = 'Auth_OpenID_SHA256';
  476. var $secret_size = 32;
  477. var $allowed_assoc_types = array('HMAC-SHA256');
  478. }
  479. /**
  480. * A class implementing plaintext consumer sessions.
  481. *
  482. * @package OpenID
  483. */
  484. class Auth_OpenID_PlainTextConsumerSession {
  485. var $session_type = 'no-encryption';
  486. var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');
  487. function getRequest()
  488. {
  489. return array();
  490. }
  491. function extractSecret($response)
  492. {
  493. if (!$response->hasKey(Auth_OpenID_OPENID_NS, 'mac_key')) {
  494. return null;
  495. }
  496. return base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
  497. 'mac_key'));
  498. }
  499. }
  500. /**
  501. * Returns available session types.
  502. */
  503. function Auth_OpenID_getAvailableSessionTypes()
  504. {
  505. $types = array(
  506. 'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession',
  507. 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession',
  508. 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession');
  509. return $types;
  510. }
  511. /**
  512. * This class is the interface to the OpenID consumer logic.
  513. * Instances of it maintain no per-request state, so they can be
  514. * reused (or even used by multiple threads concurrently) as needed.
  515. *
  516. * @package OpenID
  517. */
  518. class Auth_OpenID_GenericConsumer {
  519. /**
  520. * @access private
  521. */
  522. var $discoverMethod = 'Auth_OpenID_discover';
  523. /**
  524. * This consumer's store object.
  525. */
  526. var $store;
  527. /**
  528. * @access private
  529. */
  530. var $_use_assocs;
  531. /**
  532. * @access private
  533. */
  534. var $openid1_nonce_query_arg_name = 'janrain_nonce';
  535. /**
  536. * Another query parameter that gets added to the return_to for
  537. * OpenID 1; if the user's session state is lost, use this claimed
  538. * identifier to do discovery when verifying the response.
  539. */
  540. var $openid1_return_to_identifier_name = 'openid1_claimed_id';
  541. /**
  542. * This method initializes a new {@link Auth_OpenID_Consumer}
  543. * instance to access the library.
  544. *
  545. * @param Auth_OpenID_OpenIDStore $store This must be an object
  546. * that implements the interface in {@link Auth_OpenID_OpenIDStore}.
  547. * Several concrete implementations are provided, to cover most common use
  548. * cases. For stores backed by MySQL, PostgreSQL, or SQLite, see
  549. * the {@link Auth_OpenID_SQLStore} class and its sublcasses. For a
  550. * filesystem-backed store, see the {@link Auth_OpenID_FileStore} module.
  551. * As a last resort, if it isn't possible for the server to store
  552. * state at all, an instance of {@link Auth_OpenID_DumbStore} can be used.
  553. *
  554. * @param bool $immediate This is an optional boolean value. It
  555. * controls whether the library uses immediate mode, as explained
  556. * in the module description. The default value is False, which
  557. * disables immediate mode.
  558. */
  559. function Auth_OpenID_GenericConsumer(&$store)
  560. {
  561. $this->store =& $store;
  562. $this->negotiator =& Auth_OpenID_getDefaultNegotiator();
  563. $this->_use_assocs = ($this->store ? true : false);
  564. $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
  565. $this->session_types = Auth_OpenID_getAvailableSessionTypes();
  566. }
  567. /**
  568. * Called to begin OpenID authentication using the specified
  569. * {@link Auth_OpenID_ServiceEndpoint}.
  570. *
  571. * @access private
  572. */
  573. function begin($service_endpoint)
  574. {
  575. $assoc = $this->_getAssociation($service_endpoint);
  576. $r = new Auth_OpenID_AuthRequest($service_endpoint, $assoc);
  577. $r->return_to_args[$this->openid1_nonce_query_arg_name] =
  578. Auth_OpenID_mkNonce();
  579. if ($r->message->isOpenID1()) {
  580. $r->return_to_args[$this->openid1_return_to_identifier_name] =
  581. $r->endpoint->claimed_id;
  582. }
  583. return $r;
  584. }
  585. /**
  586. * Given an {@link Auth_OpenID_Message}, {@link
  587. * Auth_OpenID_ServiceEndpoint} and optional return_to URL,
  588. * complete OpenID authentication.
  589. *
  590. * @access private
  591. */
  592. function complete($message, $endpoint, $return_to)
  593. {
  594. $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
  595. '<no mode set>');
  596. $mode_methods = array(
  597. 'cancel' => '_complete_cancel',
  598. 'error' => '_complete_error',
  599. 'setup_needed' => '_complete_setup_needed',
  600. 'id_res' => '_complete_id_res',
  601. );
  602. $method = Auth_OpenID::arrayGet($mode_methods, $mode,
  603. '_completeInvalid');
  604. return call_user_func_array(array(&$this, $method),
  605. array($message, $endpoint, $return_to));
  606. }
  607. /**
  608. * @access private
  609. */
  610. function _completeInvalid($message, &$endpoint, $unused)
  611. {
  612. $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
  613. '<No mode set>');
  614. return new Auth_OpenID_FailureResponse($endpoint,
  615. sprintf("Invalid openid.mode '%s'", $mode));
  616. }
  617. /**
  618. * @access private
  619. */
  620. function _complete_cancel($message, &$endpoint, $unused)
  621. {
  622. return new Auth_OpenID_CancelResponse($endpoint);
  623. }
  624. /**
  625. * @access private
  626. */
  627. function _complete_error($message, &$endpoint, $unused)
  628. {
  629. $error = $message->getArg(Auth_OpenID_OPENID_NS, 'error');
  630. $contact = $message->getArg(Auth_OpenID_OPENID_NS, 'contact');
  631. $reference = $message->getArg(Auth_OpenID_OPENID_NS, 'reference');
  632. return new Auth_OpenID_FailureResponse($endpoint, $error,
  633. $contact, $reference);
  634. }
  635. /**
  636. * @access private
  637. */
  638. function _complete_setup_needed($message, &$endpoint, $unused)
  639. {
  640. if (!$message->isOpenID2()) {
  641. return $this->_completeInvalid($message, $endpoint);
  642. }
  643. $user_setup_url = $message->getArg(Auth_OpenID_OPENID2_NS,
  644. 'user_setup_url');
  645. return new Auth_OpenID_SetupNeededResponse($endpoint, $user_setup_url);
  646. }
  647. /**
  648. * @access private
  649. */
  650. function _complete_id_res($message, &$endpoint, $return_to)
  651. {
  652. $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
  653. 'user_setup_url');
  654. if ($this->_checkSetupNeeded($message)) {
  655. return new Auth_OpenID_SetupNeededResponse(
  656. $endpoint, $user_setup_url);
  657. } else {
  658. return $this->_doIdRes($message, $endpoint, $return_to);
  659. }
  660. }
  661. /**
  662. * @access private
  663. */
  664. function _checkSetupNeeded($message)
  665. {
  666. // In OpenID 1, we check to see if this is a cancel from
  667. // immediate mode by the presence of the user_setup_url
  668. // parameter.
  669. if ($message->isOpenID1()) {
  670. $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
  671. 'user_setup_url');
  672. if ($user_setup_url !== null) {
  673. return true;
  674. }
  675. }
  676. return false;
  677. }
  678. /**
  679. * @access private
  680. */
  681. function _doIdRes($message, $endpoint, $return_to)
  682. {
  683. // Checks for presence of appropriate fields (and checks
  684. // signed list fields)
  685. $result = $this->_idResCheckForFields($message);
  686. if (Auth_OpenID::isFailure($result)) {
  687. return $result;
  688. }
  689. if (!$this->_checkReturnTo($message, $return_to)) {
  690. return new Auth_OpenID_FailureResponse(null,
  691. sprintf("return_to does not match return URL. Expected %s, got %s",
  692. $return_to,
  693. $message->getArg(Auth_OpenID_OPENID_NS, 'return_to')));
  694. }
  695. // Verify discovery information:
  696. $result = $this->_verifyDiscoveryResults($message, $endpoint);
  697. if (Auth_OpenID::isFailure($result)) {
  698. return $result;
  699. }
  700. $endpoint = $result;
  701. $result = $this->_idResCheckSignature($message,
  702. $endpoint->server_url);
  703. if (Auth_OpenID::isFailure($result)) {
  704. return $result;
  705. }
  706. $result = $this->_idResCheckNonce($message, $endpoint);
  707. if (Auth_OpenID::isFailure($result)) {
  708. return $result;
  709. }
  710. $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, 'signed',
  711. Auth_OpenID_NO_DEFAULT);
  712. if (Auth_OpenID::isFailure($signed_list_str)) {
  713. return $signed_list_str;
  714. }
  715. $signed_list = explode(',', $signed_list_str);
  716. $signed_fields = Auth_OpenID::addPrefix($signed_list, "openid.");
  717. return new Auth_OpenID_SuccessResponse($endpoint, $message,
  718. $signed_fields);
  719. }
  720. /**
  721. * @access private
  722. */
  723. function _checkReturnTo($message, $return_to)
  724. {
  725. // Check an OpenID message and its openid.return_to value
  726. // against a return_to URL from an application. Return True
  727. // on success, False on failure.
  728. // Check the openid.return_to args against args in the
  729. // original message.
  730. $result = Auth_OpenID_GenericConsumer::_verifyReturnToArgs(
  731. $message->toPostArgs());
  732. if (Auth_OpenID::isFailure($result)) {
  733. return false;
  734. }
  735. // Check the return_to base URL against the one in the
  736. // message.
  737. $msg_return_to = $message->getArg(Auth_OpenID_OPENID_NS,
  738. 'return_to');
  739. if (Auth_OpenID::isFailure($return_to)) {
  740. // XXX log me
  741. return false;
  742. }
  743. $return_to_parts = parse_url(Auth_OpenID_urinorm($return_to));
  744. $msg_return_to_parts = parse_url(Auth_OpenID_urinorm($msg_return_to));
  745. // If port is absent from both, add it so it's equal in the
  746. // check below.
  747. if ((!array_key_exists('port', $return_to_parts)) &&
  748. (!array_key_exists('port', $msg_return_to_parts))) {
  749. $return_to_parts['port'] = null;
  750. $msg_return_to_parts['port'] = null;
  751. }
  752. // If path is absent from both, add it so it's equal in the
  753. // check below.
  754. if ((!array_key_exists('path', $return_to_parts)) &&
  755. (!array_key_exists('path', $msg_return_to_parts))) {
  756. $return_to_parts['path'] = null;
  757. $msg_return_to_parts['path'] = null;
  758. }
  759. // The URL scheme, authority, and path MUST be the same
  760. // between the two URLs.
  761. foreach (array('scheme', 'host', 'port', 'path') as $component) {
  762. // If the url component is absent in either URL, fail.
  763. // There should always be a scheme, host, port, and path.
  764. if (!array_key_exists($component, $return_to_parts)) {
  765. return false;
  766. }
  767. if (!array_key_exists($component, $msg_return_to_parts)) {
  768. return false;
  769. }
  770. if (Auth_OpenID::arrayGet($return_to_parts, $component) !==
  771. Auth_OpenID::arrayGet($msg_return_to_parts, $component)) {
  772. return false;
  773. }
  774. }
  775. return true;
  776. }
  777. /**
  778. * @access private
  779. */
  780. function _verifyReturnToArgs($query)
  781. {
  782. // Verify that the arguments in the return_to URL are present in this
  783. // response.
  784. $message = Auth_OpenID_Message::fromPostArgs($query);
  785. $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 'return_to');
  786. if (Auth_OpenID::isFailure($return_to)) {
  787. return $return_to;
  788. }
  789. // XXX: this should be checked by _idResCheckForFields
  790. if (!$return_to) {
  791. return new Auth_OpenID_FailureResponse(null,
  792. "Response has no return_to");
  793. }
  794. $parsed_url = parse_url($return_to);
  795. $q = array();
  796. if (array_key_exists('query', $parsed_url)) {
  797. $rt_query = $parsed_url['query'];
  798. $q = Auth_OpenID::parse_str($rt_query);
  799. }
  800. foreach ($q as $rt_key => $rt_value) {
  801. if (!array_key_exists($rt_key, $query)) {
  802. return new Auth_OpenID_FailureResponse(null,
  803. sprintf("return_to parameter %s absent from query", $rt_key));
  804. } else {
  805. $value = $query[$rt_key];
  806. if ($rt_value != $value) {
  807. return new Auth_OpenID_FailureResponse(null,
  808. sprintf("parameter %s value %s does not match " .
  809. "return_to value %s", $rt_key,
  810. $value, $rt_value));
  811. }
  812. }
  813. }
  814. // Make sure all non-OpenID arguments in the response are also
  815. // in the signed return_to.
  816. $bare_args = $message->getArgs(Auth_OpenID_BARE_NS);
  817. foreach ($bare_args as $key => $value) {
  818. if (Auth_OpenID::arrayGet($q, $key) != $value) {
  819. return new Auth_OpenID_FailureResponse(null,
  820. sprintf("Parameter %s = %s not in return_to URL",
  821. $key, $value));
  822. }
  823. }
  824. return true;
  825. }
  826. /**
  827. * @access private
  828. */
  829. function _idResCheckSignature($message, $server_url)
  830. {
  831. $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
  832. 'assoc_handle');
  833. if (Auth_OpenID::isFailure($assoc_handle)) {
  834. return $assoc_handle;
  835. }
  836. $assoc = $this->store->getAssociation($server_url, $assoc_handle);
  837. if ($assoc) {
  838. if ($assoc->getExpiresIn() <= 0) {
  839. // XXX: It might be a good idea sometimes to re-start
  840. // the authentication with a new association. Doing it
  841. // automatically opens the possibility for
  842. // denial-of-service by a server that just returns
  843. // expired associations (or really short-lived
  844. // associations)
  845. return new Auth_OpenID_FailureResponse(null,
  846. 'Association with ' . $server_url . ' expired');
  847. }
  848. if (!$assoc->checkMessageSignature($message)) {
  849. return new Auth_OpenID_FailureResponse(null,
  850. "Bad signature");
  851. }
  852. } else {
  853. // It's not an association we know about. Stateless mode
  854. // is our only possible path for recovery. XXX - async
  855. // framework will not want to block on this call to
  856. // _checkAuth.
  857. if (!$this->_checkAuth($message, $server_url)) {
  858. return new Auth_OpenID_FailureResponse(null,
  859. "Server denied check_authentication");
  860. }
  861. }
  862. return null;
  863. }
  864. /**
  865. * @access private
  866. */
  867. function _verifyDiscoveryResults($message, $endpoint=null)
  868. {
  869. if ($message->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS) {
  870. return $this->_verifyDiscoveryResultsOpenID2($message,
  871. $endpoint);
  872. } else {
  873. return $this->_verifyDiscoveryResultsOpenID1($message,
  874. $endpoint);
  875. }
  876. }
  877. /**
  878. * @access private
  879. */
  880. function _verifyDiscoveryResultsOpenID1($message, $endpoint)
  881. {
  882. $claimed_id = $message->getArg(Auth_OpenID_BARE_NS,
  883. $this->openid1_return_to_identifier_name);
  884. if (($endpoint === null) && ($claimed_id === null)) {
  885. return new Auth_OpenID_FailureResponse($endpoint,
  886. 'When using OpenID 1, the claimed ID must be supplied, ' .
  887. 'either by passing it through as a return_to parameter ' .
  888. 'or by using a session, and supplied to the GenericConsumer ' .
  889. 'as the argument to complete()');
  890. } else if (($endpoint !== null) && ($claimed_id === null)) {
  891. $claimed_id = $endpoint->claimed_id;
  892. }
  893. $to_match = new Auth_OpenID_ServiceEndpoint();
  894. $to_match->type_uris = array(Auth_OpenID_TYPE_1_1);
  895. $to_match->local_id = $message->getArg(Auth_OpenID_OPENID1_NS,
  896. 'identity');
  897. // Restore delegate information from the initiation phase
  898. $to_match->claimed_id = $claimed_id;
  899. if ($to_match->local_id === null) {
  900. return new Auth_OpenID_FailureResponse($endpoint,
  901. "Missing required field openid.identity");
  902. }
  903. $to_match_1_0 = $to_match->copy();
  904. $to_match_1_0->type_uris = array(Auth_OpenID_TYPE_1_0);
  905. if ($endpoint !== null) {
  906. $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
  907. if (is_a($result, 'Auth_OpenID_TypeURIMismatch')) {
  908. $result = $this->_verifyDiscoverySingle($endpoint,
  909. $to_match_1_0);
  910. }
  911. if (Auth_OpenID::isFailure($result)) {
  912. // oidutil.log("Error attempting to use stored
  913. // discovery information: " + str(e))
  914. // oidutil.log("Attempting discovery to
  915. // verify endpoint")
  916. } else {
  917. return $endpoint;
  918. }
  919. }
  920. // Endpoint is either bad (failed verification) or None
  921. return $this->_discoverAndVerify($to_match->claimed_id,
  922. array($to_match, $to_match_1_0));
  923. }
  924. /**
  925. * @access private
  926. */
  927. function _verifyDiscoverySingle($endpoint, $to_match)
  928. {
  929. // Every type URI that's in the to_match endpoint has to be
  930. // present in the discovered endpoint.
  931. foreach ($to_match->type_uris as $type_uri) {
  932. if (!$endpoint->usesExtension($type_uri)) {
  933. return new Auth_OpenID_TypeURIMismatch($endpoint,
  934. "Required type ".$type_uri." not present");
  935. }
  936. }
  937. // Fragments do not influence discovery, so we can't compare a
  938. // claimed identifier with a fragment to discovered
  939. // information.
  940. list($defragged_claimed_id, $_) =
  941. Auth_OpenID::urldefrag($to_match->claimed_id);
  942. if ($defragged_claimed_id != $endpoint->claimed_id) {
  943. return new Auth_OpenID_FailureResponse($endpoint,
  944. sprintf('Claimed ID does not match (different subjects!), ' .
  945. 'Expected %s, got %s', $defragged_claimed_id,
  946. $endpoint->claimed_id));
  947. }
  948. if ($to_match->getLocalID() != $endpoint->getLocalID()) {
  949. return new Auth_OpenID_FailureResponse($endpoint,
  950. sprintf('local_id mismatch. Expected %s, got %s',
  951. $to_match->getLocalID(), $endpoint->getLocalID()));
  952. }
  953. // If the server URL is None, this must be an OpenID 1
  954. // response, because op_endpoint is a required parameter in
  955. // OpenID 2. In that case, we don't actually care what the
  956. // discovered server_url is, because signature checking or
  957. // check_auth should take care of that check for us.
  958. if ($to_match->server_url === null) {
  959. if ($to_match->preferredNamespace() != Auth_OpenID_OPENID1_NS) {
  960. return new Auth_OpenID_FailureResponse($endpoint,
  961. "Preferred namespace mismatch (bug)");
  962. }
  963. } else if ($to_match->server_url != $endpoint->server_url) {
  964. return new Auth_OpenID_FailureResponse($endpoint,
  965. sprintf('OP Endpoint mismatch. Expected %s, got %s',
  966. $to_match->server_url, $endpoint->server_url));
  967. }
  968. return null;
  969. }
  970. /**
  971. * @access private
  972. */
  973. function _verifyDiscoveryResultsOpenID2($message, $endpoint)
  974. {
  975. $to_match = new Auth_OpenID_ServiceEndpoint();
  976. $to_match->type_uris = array(Auth_OpenID_TYPE_2_0);
  977. $to_match->claimed_id = $message->getArg(Auth_OpenID_OPENID2_NS,
  978. 'claimed_id');
  979. $to_match->local_id = $message->getArg(Auth_OpenID_OPENID2_NS,
  980. 'identity');
  981. $to_match->server_url = $message->getArg(Auth_OpenID_OPENID2_NS,
  982. 'op_endpoint');
  983. if ($to_match->server_url === null) {
  984. return new Auth_OpenID_FailureResponse($endpoint,
  985. "OP Endpoint URL missing");
  986. }
  987. // claimed_id and identifier must both be present or both be
  988. // absent
  989. if (($to_match->claimed_id === null) &&
  990. ($to_match->local_id !== null)) {
  991. return new Auth_OpenID_FailureResponse($endpoint,
  992. 'openid.identity is present without openid.claimed_id');
  993. }
  994. if (($to_match->claimed_id !== null) &&
  995. ($to_match->local_id === null)) {
  996. return new Auth_OpenID_FailureResponse($endpoint,
  997. 'openid.claimed_id is present without openid.identity');
  998. }
  999. if ($to_match->claimed_id === null) {
  1000. // This is a response without identifiers, so there's
  1001. // really no checking that we can do, so return an
  1002. // endpoint that's for the specified `openid.op_endpoint'
  1003. return Auth_OpenID_ServiceEndpoint::fromOPEndpointURL(
  1004. $to_match->server_url);
  1005. }
  1006. if (!$endpoint) {
  1007. // The claimed ID doesn't match, so we have to do
  1008. // discovery again. This covers not using sessions, OP
  1009. // identifier endpoints and responses that didn't match
  1010. // the original request.
  1011. // oidutil.log('No pre-discovered information supplied.')
  1012. return $this->_discoverAndVerify($to_match->claimed_id,
  1013. array($to_match));
  1014. } else {
  1015. // The claimed ID matches, so we use the endpoint that we
  1016. // discovered in initiation. This should be the most
  1017. // common case.
  1018. $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
  1019. if (Auth_OpenID::isFailure($result)) {
  1020. $endpoint = $this->_discoverAndVerify($to_match->claimed_id,
  1021. array($to_match));
  1022. if (Auth_OpenID::isFailure($endpoint)) {
  1023. return $endpoint;
  1024. }
  1025. }
  1026. }
  1027. // The endpoint we return should have the claimed ID from the
  1028. // message we just verified, fragment and all.
  1029. if ($endpoint->claimed_id != $to_match->claimed_id) {
  1030. $endpoint->claimed_id = $to_match->claimed_id;
  1031. }
  1032. return $endpoint;
  1033. }
  1034. /**
  1035. * @access private
  1036. */
  1037. function _discoverAndVerify($claimed_id, $to_match_endpoints)
  1038. {
  1039. // oidutil.log('Performing discovery on %s' % (claimed_id,))
  1040. list($unused, $services) = call_user_func($this->discoverMethod,
  1041. $claimed_id,
  1042. $this->fetcher);
  1043. if (!$services) {
  1044. return new Auth_OpenID_FailureResponse(null,
  1045. sprintf("No OpenID information found at %s",
  1046. $claimed_id));
  1047. }
  1048. return $this->_verifyDiscoveryServices($claimed_id, $services,
  1049. $to_match_endpoints);
  1050. }
  1051. /**
  1052. * @access private
  1053. */
  1054. function _verifyDiscoveryServices($claimed_id,
  1055. &$services, &$to_match_endpoints)
  1056. {
  1057. // Search the services resulting from discovery to find one
  1058. // that matches the information from the assertion
  1059. foreach ($services as $endpoint) {
  1060. foreach ($to_match_endpoints as $to_match_endpoint) {
  1061. $result = $this->_verifyDiscoverySingle($endpoint,
  1062. $to_match_endpoint);
  1063. if (!Auth_OpenID::isFailure($result)) {
  1064. // It matches, so discover verification has
  1065. // succeeded. Return this endpoint.
  1066. return $endpoint;
  1067. }
  1068. }
  1069. }
  1070. return new Auth_OpenID_FailureResponse(null,
  1071. sprintf('No matching endpoint found after discovering %s',
  1072. $claimed_id));
  1073. }
  1074. /**
  1075. * Extract the nonce from an OpenID 1 response. Return the nonce
  1076. * from the BARE_NS since we independently check the return_to
  1077. * arguments are the same as those in the response message.
  1078. *
  1079. * See the openid1_nonce_query_arg_name class variable
  1080. *
  1081. * @returns $nonce The nonce as a string or null
  1082. *
  1083. * @access private
  1084. */
  1085. function _idResGetNonceOpenID1($message, $endpoint)
  1086. {
  1087. return $message->getArg(Auth_OpenID_BARE_NS,
  1088. $this->openid1_nonce_query_arg_name);
  1089. }
  1090. /**
  1091. * @access private
  1092. */
  1093. function _idResCheckNonce($message, $endpoint)
  1094. {
  1095. if ($message->isOpenID1()) {
  1096. // This indicates that the nonce was generated by the consumer
  1097. $nonce = $this->_idResGetNonceOpenID1($message, $endpoint);
  1098. $server_url = '';
  1099. } else {
  1100. $nonce = $message->getArg(Auth_OpenID_OPENID2_NS,
  1101. 'response_nonce');
  1102. $server_url = $endpoint->server_url;
  1103. }
  1104. if ($nonce === null) {
  1105. return new Auth_OpenID_FailureResponse($endpoint,
  1106. "Nonce missing from response");
  1107. }
  1108. $parts = Auth_OpenID_splitNonce($nonce);
  1109. if ($parts === null) {
  1110. return new Auth_OpenID_FailureResponse($endpoint,
  1111. "Malformed nonce in response");
  1112. }
  1113. list($timestamp, $salt) = $parts;
  1114. if (!$this->store->useNonce($server_url, $timestamp, $salt)) {
  1115. return new Auth_OpenID_FailureResponse($endpoint,
  1116. "Nonce already used or out of range");
  1117. }
  1118. return null;
  1119. }
  1120. /**
  1121. * @access private
  1122. */
  1123. function _idResCheckForFields($message)
  1124. {
  1125. $basic_fields = array('return_to', 'assoc_handle', 'sig', 'signed');
  1126. $basic_sig_fields = array('return_to', 'identity');
  1127. $require_fields = array(
  1128. Auth_OpenID_OPENID2_NS => array_merge($basic_fields,
  1129. array('op_endpoint')),
  1130. Auth_OpenID_OPENID1_NS => array_merge($basic_fields,
  1131. array('identity'))
  1132. );
  1133. $require_sigs = array(
  1134. Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields,
  1135. array('response_nonce',
  1136. 'claimed_id',
  1137. 'assoc_handle')),
  1138. Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields,
  1139. array('nonce'))
  1140. );
  1141. foreach ($require_fields[$message->getOpenIDNamespace()] as $field) {
  1142. if (!$message->hasKey(Auth_OpenID_OPENID_NS, $field)) {
  1143. return new Auth_OpenID_FailureResponse(null,
  1144. "Missing required field '".$field."'");
  1145. }
  1146. }
  1147. $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS,
  1148. 'signed',
  1149. Auth_OpenID_NO_DEFAULT);
  1150. if (Auth_OpenID::isFailure($signed_list_str)) {
  1151. return $signed_list_str;
  1152. }
  1153. $signed_list = explode(',', $signed_list_str);
  1154. foreach ($require_sigs[$message->getOpenIDNamespace()] as $field) {
  1155. // Field is present and not in signed list
  1156. if ($message->hasKey(Auth_OpenID_OPENID_NS, $field) &&
  1157. (!in_array($field, $signed_list))) {
  1158. return new Auth_OpenID_FailureResponse(null,
  1159. "'".$field."' not signed");
  1160. }
  1161. }
  1162. return null;
  1163. }
  1164. /**
  1165. * @access private
  1166. */
  1167. function _checkAuth($message, $server_url)
  1168. {
  1169. $request = $this->_createCheckAuthRequest($message);
  1170. if ($request === null) {
  1171. return false;
  1172. }
  1173. $resp_message = $this->_makeKVPost($request, $server_url);
  1174. if (($resp_message === null) ||
  1175. (is_a($resp_message, 'Auth_OpenID_ServerErrorContainer'))) {
  1176. return false;
  1177. }
  1178. return $this->_processCheckAuthResponse($resp_message, $server_url);
  1179. }
  1180. /**
  1181. * @access private
  1182. */
  1183. function _createCheckAuthRequest($message)
  1184. {
  1185. $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
  1186. if ($signed) {
  1187. foreach (explode(',', $signed) as $k) {
  1188. $value = $message->getAliasedArg($k);
  1189. if ($value === null) {
  1190. return null;
  1191. }
  1192. }
  1193. }
  1194. $ca_message = $message->copy();
  1195. $ca_message->setArg(Auth_OpenID_OPENID_NS, 'mode',
  1196. 'check_authentication');
  1197. return $ca_message;
  1198. }
  1199. /**
  1200. * @access private
  1201. */
  1202. function _processCheckAuthResponse(

Large files files are truncated, but you can click here to view the full file