PageRenderTime 114ms CodeModel.GetById 40ms RepoModel.GetById 2ms app.codeStats 0ms

/src/Joomla/OAuth1/Client.php

https://github.com/piotr-cz/joomla-framework
PHP | 609 lines | 299 code | 69 blank | 241 comment | 24 complexity | ae9ffd10010a497aec837953c19cf984 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Part of the Joomla Framework OAuth1 Package
  4. *
  5. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  6. * @license GNU General Public License version 2 or later; see LICENSE
  7. */
  8. namespace Joomla\OAuth1;
  9. use Joomla\Http\Http;
  10. use Joomla\Http\Response;
  11. use Joomla\Input\Input;
  12. use Joomla\Application\AbstractWebApplication;
  13. /**
  14. * Joomla Framework class for interacting with an OAuth 1.0 and 1.0a server.
  15. *
  16. * @since 1.0
  17. */
  18. abstract class Client
  19. {
  20. /**
  21. * @var array Options for the Client object.
  22. * @since 1.0
  23. */
  24. protected $options;
  25. /**
  26. * @var array Contains access token key, secret and verifier.
  27. * @since 1.0
  28. */
  29. protected $token = array();
  30. /**
  31. * @var Http The HTTP client object to use in sending HTTP requests.
  32. * @since 1.0
  33. */
  34. protected $client;
  35. /**
  36. * @var Input The input object to use in retrieving GET/POST data.
  37. * @since 1.0
  38. */
  39. protected $input;
  40. /**
  41. * @var AbstractWebApplication The application object to send HTTP headers for redirects.
  42. * @since 1.0
  43. */
  44. protected $application;
  45. /**
  46. * @var string Selects which version of OAuth to use: 1.0 or 1.0a.
  47. * @since 1.0
  48. */
  49. protected $version;
  50. /**
  51. * Constructor.
  52. *
  53. * @param array $options OAuth1 Client options array.
  54. * @param Http $client The HTTP client object.
  55. * @param Input $input The input object
  56. * @param AbstractWebApplication $application The application object
  57. * @param string $version Specify the OAuth version. By default we are using 1.0a.
  58. *
  59. * @since 1.0
  60. */
  61. public function __construct($options, Http $client, Input $input, AbstractWebApplication $application, $version = '1.0a')
  62. {
  63. $this->options = $options;
  64. $this->client = $client;
  65. $this->input = $input;
  66. $this->application = $application;
  67. $this->version = $version;
  68. }
  69. /**
  70. * Method to form the oauth flow.
  71. *
  72. * @return string The access token.
  73. *
  74. * @since 1.0
  75. *
  76. * @throws \DomainException
  77. */
  78. public function authenticate()
  79. {
  80. // Already got some credentials stored?
  81. if ($this->token)
  82. {
  83. $response = $this->verifyCredentials();
  84. if ($response)
  85. {
  86. return $this->token;
  87. }
  88. else
  89. {
  90. $this->token = null;
  91. }
  92. }
  93. // Check for callback.
  94. if (strcmp($this->version, '1.0a') === 0)
  95. {
  96. $verifier = $this->input->get('oauth_verifier');
  97. }
  98. else
  99. {
  100. $verifier = $this->input->get('oauth_token');
  101. }
  102. if (empty($verifier))
  103. {
  104. // Generate a request token.
  105. $this->_generateRequestToken();
  106. // Authenticate the user and authorise the app.
  107. $this->_authorise();
  108. }
  109. // Callback
  110. else
  111. {
  112. $session = $this->application->getSession();
  113. // Get token form session.
  114. $this->token = array('key' => $session->get('key', null, 'oauth_token'), 'secret' => $session->get('secret', null, 'oauth_token'));
  115. // Verify the returned request token.
  116. if (strcmp($this->token['key'], $this->input->get('oauth_token')) !== 0)
  117. {
  118. throw new \DomainException('Bad session!');
  119. }
  120. // Set token verifier for 1.0a.
  121. if (strcmp($this->version, '1.0a') === 0)
  122. {
  123. $this->token['verifier'] = $this->input->get('oauth_verifier');
  124. }
  125. // Generate access token.
  126. $this->_generateAccessToken();
  127. // Return the access token.
  128. return $this->token;
  129. }
  130. }
  131. /**
  132. * Method used to get a request token.
  133. *
  134. * @return void
  135. *
  136. * @since 1.0
  137. * @throws \DomainException
  138. */
  139. private function _generateRequestToken()
  140. {
  141. // Set the callback URL.
  142. if ($this->getOption('callback'))
  143. {
  144. $parameters = array(
  145. 'oauth_callback' => $this->getOption('callback')
  146. );
  147. }
  148. else
  149. {
  150. $parameters = array();
  151. }
  152. // Make an OAuth request for the Request Token.
  153. $response = $this->oauthRequest($this->getOption('requestTokenURL'), 'POST', $parameters);
  154. parse_str($response->body, $params);
  155. if (strcmp($this->version, '1.0a') === 0 && strcmp($params['oauth_callback_confirmed'], 'true') !== 0)
  156. {
  157. throw new \DomainException('Bad request token!');
  158. }
  159. // Save the request token.
  160. $this->token = array('key' => $params['oauth_token'], 'secret' => $params['oauth_token_secret']);
  161. // Save the request token in session
  162. $session = $this->application->getSession();
  163. $session->set('key', $this->token['key'], 'oauth_token');
  164. $session->set('secret', $this->token['secret'], 'oauth_token');
  165. }
  166. /**
  167. * Method used to authorise the application.
  168. *
  169. * @return void
  170. *
  171. * @since 1.0
  172. */
  173. private function _authorise()
  174. {
  175. $url = $this->getOption('authoriseURL') . '?oauth_token=' . $this->token['key'];
  176. if ($this->getOption('scope'))
  177. {
  178. $scope = is_array($this->getOption('scope')) ? implode(' ', $this->getOption('scope')) : $this->getOption('scope');
  179. $url .= '&scope=' . urlencode($scope);
  180. }
  181. if ($this->getOption('sendheaders'))
  182. {
  183. $this->application->redirect($url);
  184. }
  185. }
  186. /**
  187. * Method used to get an access token.
  188. *
  189. * @return void
  190. *
  191. * @since 1.0
  192. */
  193. private function _generateAccessToken()
  194. {
  195. // Set the parameters.
  196. $parameters = array(
  197. 'oauth_token' => $this->token['key']
  198. );
  199. if (strcmp($this->version, '1.0a') === 0)
  200. {
  201. $parameters = array_merge($parameters, array('oauth_verifier' => $this->token['verifier']));
  202. }
  203. // Make an OAuth request for the Access Token.
  204. $response = $this->oauthRequest($this->getOption('accessTokenURL'), 'POST', $parameters);
  205. parse_str($response->body, $params);
  206. // Save the access token.
  207. $this->token = array('key' => $params['oauth_token'], 'secret' => $params['oauth_token_secret']);
  208. }
  209. /**
  210. * Method used to make an OAuth request.
  211. *
  212. * @param string $url The request URL.
  213. * @param string $method The request method.
  214. * @param array $parameters Array containing request parameters.
  215. * @param mixed $data The POST request data.
  216. * @param array $headers An array of name-value pairs to include in the header of the request
  217. *
  218. * @return object The Response object.
  219. *
  220. * @since 1.0
  221. * @throws \DomainException
  222. */
  223. public function oauthRequest($url, $method, $parameters, $data = array(), $headers = array())
  224. {
  225. // Set the parameters.
  226. $defaults = array(
  227. 'oauth_consumer_key' => $this->getOption('consumer_key'),
  228. 'oauth_signature_method' => 'HMAC-SHA1',
  229. 'oauth_version' => '1.0',
  230. 'oauth_nonce' => $this->generateNonce(),
  231. 'oauth_timestamp' => time()
  232. );
  233. $parameters = array_merge($parameters, $defaults);
  234. // Do not encode multipart parameters. Do not include $data in the signature if $data is not array.
  235. if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart/form-data') !== false || !is_array($data))
  236. {
  237. $oauth_headers = $parameters;
  238. }
  239. else
  240. {
  241. // Use all parameters for the signature.
  242. $oauth_headers = array_merge($parameters, $data);
  243. }
  244. // Sign the request.
  245. $oauth_headers = $this->_signRequest($url, $method, $oauth_headers);
  246. // Get parameters for the Authorisation header.
  247. if (is_array($data))
  248. {
  249. $oauth_headers = array_diff_key($oauth_headers, $data);
  250. }
  251. // Send the request.
  252. switch ($method)
  253. {
  254. case 'GET':
  255. $url = $this->toUrl($url, $data);
  256. $response = $this->client->get($url, array('Authorization' => $this->_createHeader($oauth_headers)));
  257. break;
  258. case 'POST':
  259. $headers = array_merge($headers, array('Authorization' => $this->_createHeader($oauth_headers)));
  260. $response = $this->client->post($url, $data, $headers);
  261. break;
  262. case 'PUT':
  263. $headers = array_merge($headers, array('Authorization' => $this->_createHeader($oauth_headers)));
  264. $response = $this->client->put($url, $data, $headers);
  265. break;
  266. case 'DELETE':
  267. $headers = array_merge($headers, array('Authorization' => $this->_createHeader($oauth_headers)));
  268. $response = $this->client->delete($url, $headers);
  269. break;
  270. }
  271. // Validate the response code.
  272. $this->validateResponse($url, $response);
  273. return $response;
  274. }
  275. /**
  276. * Method to validate a response.
  277. *
  278. * @param string $url The request URL.
  279. * @param Response $response The response to validate.
  280. *
  281. * @return void
  282. *
  283. * @since 1.0
  284. * @throws \DomainException
  285. */
  286. abstract public function validateResponse($url, $response);
  287. /**
  288. * Method used to create the header for the POST request.
  289. *
  290. * @param array $parameters Array containing request parameters.
  291. *
  292. * @return string The header.
  293. *
  294. * @since 1.0
  295. */
  296. private function _createHeader($parameters)
  297. {
  298. $header = 'OAuth ';
  299. foreach ($parameters as $key => $value)
  300. {
  301. if (!strcmp($header, 'OAuth '))
  302. {
  303. $header .= $key . '="' . $this->safeEncode($value) . '"';
  304. }
  305. else
  306. {
  307. $header .= ', ' . $key . '="' . $value . '"';
  308. }
  309. }
  310. return $header;
  311. }
  312. /**
  313. * Method to create the URL formed string with the parameters.
  314. *
  315. * @param string $url The request URL.
  316. * @param array $parameters Array containing request parameters.
  317. *
  318. * @return string The formed URL.
  319. *
  320. * @since 1.0
  321. */
  322. public function toUrl($url, $parameters)
  323. {
  324. foreach ($parameters as $key => $value)
  325. {
  326. if (is_array($value))
  327. {
  328. foreach ($value as $k => $v)
  329. {
  330. if (strpos($url, '?') === false)
  331. {
  332. $url .= '?' . $key . '=' . $v;
  333. }
  334. else
  335. {
  336. $url .= '&' . $key . '=' . $v;
  337. }
  338. }
  339. }
  340. else
  341. {
  342. if (strpos($value, ' ') !== false)
  343. {
  344. $value = $this->safeEncode($value);
  345. }
  346. if (strpos($url, '?') === false)
  347. {
  348. $url .= '?' . $key . '=' . $value;
  349. }
  350. else
  351. {
  352. $url .= '&' . $key . '=' . $value;
  353. }
  354. }
  355. }
  356. return $url;
  357. }
  358. /**
  359. * Method used to sign requests.
  360. *
  361. * @param string $url The URL to sign.
  362. * @param string $method The request method.
  363. * @param array $parameters Array containing request parameters.
  364. *
  365. * @return array The array containing the request parameters, including signature.
  366. *
  367. * @since 1.0
  368. */
  369. private function _signRequest($url, $method, $parameters)
  370. {
  371. // Create the signature base string.
  372. $base = $this->_baseString($url, $method, $parameters);
  373. $parameters['oauth_signature'] = $this->safeEncode(
  374. base64_encode(
  375. hash_hmac('sha1', $base, $this->_prepareSigningKey(), true)
  376. )
  377. );
  378. return $parameters;
  379. }
  380. /**
  381. * Prepare the signature base string.
  382. *
  383. * @param string $url The URL to sign.
  384. * @param string $method The request method.
  385. * @param array $parameters Array containing request parameters.
  386. *
  387. * @return string The base string.
  388. *
  389. * @since 1.0
  390. */
  391. private function _baseString($url, $method, $parameters)
  392. {
  393. // Sort the parameters alphabetically
  394. uksort($parameters, 'strcmp');
  395. // Encode parameters.
  396. foreach ($parameters as $key => $value)
  397. {
  398. $key = $this->safeEncode($key);
  399. if (is_array($value))
  400. {
  401. foreach ($value as $k => $v)
  402. {
  403. $v = $this->safeEncode($v);
  404. $kv[] = "{$key}={$v}";
  405. }
  406. }
  407. else
  408. {
  409. $value = $this->safeEncode($value);
  410. $kv[] = "{$key}={$value}";
  411. }
  412. }
  413. // Form the parameter string.
  414. $params = implode('&', $kv);
  415. // Signature base string elements.
  416. $base = array(
  417. $method,
  418. $url,
  419. $params
  420. );
  421. // Return the base string.
  422. return implode('&', $this->safeEncode($base));
  423. }
  424. /**
  425. * Encodes the string or array passed in a way compatible with OAuth.
  426. * If an array is passed each array value will will be encoded.
  427. *
  428. * @param mixed $data The scalar or array to encode.
  429. *
  430. * @return string $data encoded in a way compatible with OAuth.
  431. *
  432. * @since 1.0
  433. */
  434. public function safeEncode($data)
  435. {
  436. if (is_array($data))
  437. {
  438. return array_map(array($this, 'safeEncode'), $data);
  439. }
  440. elseif (is_scalar($data))
  441. {
  442. return str_ireplace(
  443. array('+', '%7E'),
  444. array(' ', '~'),
  445. rawurlencode($data)
  446. );
  447. }
  448. else
  449. {
  450. return '';
  451. }
  452. }
  453. /**
  454. * Method used to generate the current nonce.
  455. *
  456. * @return string The current nonce.
  457. *
  458. * @since 1.0
  459. */
  460. public static function generateNonce()
  461. {
  462. $mt = microtime();
  463. $rand = mt_rand();
  464. // The md5s look nicer than numbers.
  465. return md5($mt . $rand);
  466. }
  467. /**
  468. * Prepares the OAuth signing key.
  469. *
  470. * @return string The prepared signing key.
  471. *
  472. * @since 1.0
  473. */
  474. private function _prepareSigningKey()
  475. {
  476. return $this->safeEncode($this->getOption('consumer_secret')) . '&' . $this->safeEncode(($this->token) ? $this->token['secret'] : '');
  477. }
  478. /**
  479. * Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful;
  480. * returns a 401 status code and an error message if not.
  481. *
  482. * @return array The decoded JSON response
  483. *
  484. * @since 1.0
  485. */
  486. abstract public function verifyCredentials();
  487. /**
  488. * Get an option from the OAuth1 Client instance.
  489. *
  490. * @param string $key The name of the option to get
  491. *
  492. * @return mixed The option value
  493. *
  494. * @since 1.0
  495. */
  496. public function getOption($key)
  497. {
  498. return isset($this->options[$key]) ? $this->options[$key] : null;
  499. }
  500. /**
  501. * Set an option for the OAuth1 Client instance.
  502. *
  503. * @param string $key The name of the option to set
  504. * @param mixed $value The option value to set
  505. *
  506. * @return Client This object for method chaining
  507. *
  508. * @since 1.0
  509. */
  510. public function setOption($key, $value)
  511. {
  512. $this->options[$key] = $value;
  513. return $this;
  514. }
  515. /**
  516. * Get the oauth token key or secret.
  517. *
  518. * @return array The oauth token key and secret.
  519. *
  520. * @since 1.0
  521. */
  522. public function getToken()
  523. {
  524. return $this->token;
  525. }
  526. /**
  527. * Set the oauth token.
  528. *
  529. * @param array $token The access token key and secret.
  530. *
  531. * @return Client This object for method chaining.
  532. *
  533. * @since 1.0
  534. */
  535. public function setToken($token)
  536. {
  537. $this->token = $token;
  538. return $this;
  539. }
  540. }