OAuthSimple.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. <?php
  2. /**
  3. * OAuthSimple - A simpler version of OAuth
  4. *
  5. * https://github.com/jrconlin/oauthsimple
  6. *
  7. * @author jr conlin <src@jrconlin.com>
  8. * @copyright unitedHeroes.net 2011
  9. * @version 1.3
  10. * @license BSD licence.
  11. */
  12. class OAuthSimple
  13. {
  14. private $_secrets;
  15. private $_default_signature_method;
  16. private $_action;
  17. private $_nonce_chars;
  18. /**
  19. * OAuthSimple constructor.
  20. *
  21. * @param string $APIKey The API Key (sometimes referred to as the consumer key). This value is usually
  22. * supplied by the site you wish to use.
  23. * @param string $sharedSecret The shared secret. This value is also usually provided by the site you wish to use.
  24. *
  25. * @return OAuthSimple
  26. */
  27. function __construct($APIKey = "", $sharedSecret = "")
  28. {
  29. if (!empty($APIKey)) {
  30. $this->_secrets['consumer_key'] = $APIKey;
  31. }
  32. if (!empty($sharedSecret)) {
  33. $this->_secrets['shared_secret'] = $sharedSecret;
  34. }
  35. $this->_default_signature_method = "HMAC-SHA1";
  36. $this->_action = "GET";
  37. $this->_nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  38. return $this;
  39. }
  40. /**
  41. * Reset the parameters and URL.
  42. *
  43. * @return OAuthSimple
  44. */
  45. public function reset()
  46. {
  47. $this->_parameters = Array();
  48. $this->path = null;
  49. $this->sbs = null;
  50. return $this;
  51. }
  52. /**
  53. * Set the parameters either from a hash or a string.
  54. *
  55. * @param array $parameters List of parameters for the call,
  56. * this can either be a URI string (e.g."foo=bar&gorp=banana" or an object/hash)
  57. *
  58. * @throws OAuthSimpleException
  59. *
  60. * @return OAuthSimple
  61. */
  62. public function setParameters($parameters = [])
  63. {
  64. if (is_string($parameters)) {
  65. $parameters = $this->_parseParameterString($parameters);
  66. }
  67. if (empty($this->_parameters)) {
  68. $this->_parameters = $parameters;
  69. } elseif (!empty($parameters)) {
  70. $this->_parameters = array_merge($this->_parameters, $parameters);
  71. }
  72. if (empty($this->_parameters['oauth_nonce'])) {
  73. $this->_getNonce();
  74. }
  75. if (empty($this->_parameters['oauth_timestamp'])) {
  76. $this->_getTimeStamp();
  77. }
  78. if (empty($this->_parameters['oauth_consumer_key'])) {
  79. $this->_getApiKey();
  80. }
  81. if (empty($this->_parameters['oauth_token'])) {
  82. $this->_getAccessToken();
  83. }
  84. if (empty($this->_parameters['oauth_signature_method'])) {
  85. $this->setSignatureMethod();
  86. }
  87. if (empty($this->_parameters['oauth_version'])) {
  88. $this->_parameters['oauth_version'] = "1.0";
  89. }
  90. return $this;
  91. }
  92. /**
  93. * Convenience method for setParameters.
  94. *
  95. * @param array $parameters
  96. *
  97. * @throws OAuthSimpleException
  98. *
  99. * @return OAuthSimple
  100. */
  101. public function setQueryString($parameters)
  102. {
  103. return $this->setParameters($parameters);
  104. }
  105. /**
  106. * Set the target URL (does not include the parameters).
  107. *
  108. * @param string $path The fully qualified URI (excluding query arguments) (e.g "http://example.org/foo")
  109. *
  110. * @throws OAuthSimpleException
  111. *
  112. * @return OAuthSimple
  113. */
  114. public function setURL($path)
  115. {
  116. if (empty($path)) {
  117. throw new OAuthSimpleException('No path specified for OAuthSimple.setURL');
  118. }
  119. $this->_path = $path;
  120. return $this;
  121. }
  122. /**
  123. * Convenience method for setURL.
  124. *
  125. * @param string $path
  126. *
  127. * @return mixed
  128. */
  129. public function setPath($path)
  130. {
  131. return $this->_path = $path;
  132. }
  133. /**
  134. * Set the "action" for the url, (e.g. GET,POST, DELETE, etc.).
  135. *
  136. * @param string $action HTTP Action word.
  137. *
  138. * @throws OAuthSimpleException
  139. *
  140. * @return $this
  141. */
  142. public function setAction($action)
  143. {
  144. if (empty($action)) {
  145. $action = 'GET';
  146. }
  147. $action = strtoupper($action);
  148. if (preg_match('/[^A-Z]/', $action)) {
  149. throw new OAuthSimpleException('Invalid action specified for OAuthSimple.setAction');
  150. }
  151. $this->_action = $action;
  152. return $this;
  153. }
  154. /**
  155. * Set the signatures (as well as validate the ones you have).
  156. *
  157. * @param array $signatures object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token:
  158. * oauth_secret:}
  159. *
  160. * @throws OAuthSimpleException
  161. *
  162. * @return $this
  163. */
  164. public function signatures($signatures)
  165. {
  166. if (!empty($signatures) && !is_array($signatures)) {
  167. throw new OAuthSimpleException('Must pass dictionary array to OAuthSimple.signatures');
  168. }
  169. if (!empty($signatures)) {
  170. if (empty($this->_secrets)) {
  171. $this->_secrets = Array();
  172. }
  173. $this->_secrets = array_merge($this->_secrets, $signatures);
  174. }
  175. if (isset($this->_secrets['api_key'])) {
  176. $this->_secrets['consumer_key'] = $this->_secrets['api_key'];
  177. }
  178. if (isset($this->_secrets['access_token'])) {
  179. $this->_secrets['oauth_token'] = $this->_secrets['access_token'];
  180. }
  181. if (isset($this->_secrets['access_secret'])) {
  182. $this->_secrets['shared_secret'] = $this->_secrets['access_secret'];
  183. }
  184. if (isset($this->_secrets['oauth_token_secret'])) {
  185. $this->_secrets['oauth_secret'] = $this->_secrets['oauth_token_secret'];
  186. }
  187. if (empty($this->_secrets['consumer_key'])) {
  188. throw new OAuthSimpleException('Missing required consumer_key in OAuthSimple.signatures');
  189. }
  190. if (empty($this->_secrets['shared_secret'])) {
  191. throw new OAuthSimpleException('Missing requires shared_secret in OAuthSimple.signatures');
  192. }
  193. if (!empty($this->_secrets['oauth_token']) && empty($this->_secrets['oauth_secret'])) {
  194. throw new OAuthSimpleException('Missing oauth_secret for supplied oauth_token in OAuthSimple.signatures');
  195. }
  196. return $this;
  197. }
  198. /**
  199. * @param array $signatures
  200. *
  201. * @throws OAuthSimpleException
  202. *
  203. * @return OAuthSimple
  204. */
  205. public function setTokensAndSecrets($signatures)
  206. {
  207. return $this->signatures($signatures);
  208. }
  209. /**
  210. * Set the signature method (currently only Plaintext or SHA-MAC1).
  211. *
  212. * @param string $method Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now).
  213. *
  214. * @throws OAuthSimpleException
  215. *
  216. * @return $this
  217. */
  218. public function setSignatureMethod($method = "")
  219. {
  220. if (empty($method)) {
  221. $method = $this->_default_signature_method;
  222. }
  223. $method = strtoupper($method);
  224. switch ($method) {
  225. case 'PLAINTEXT':
  226. case 'HMAC-SHA1':
  227. $this->_parameters['oauth_signature_method'] = $method;
  228. break;
  229. default:
  230. throw new OAuthSimpleException (
  231. "Unknown signing method $method specified for OAuthSimple.setSignatureMethod"
  232. );
  233. break;
  234. }
  235. return $this;
  236. }
  237. /**
  238. * Sign the request.
  239. *
  240. * note: all arguments are optional, provided you've set them using the
  241. * other helper functions.
  242. *
  243. * @param array $args Optional.
  244. * Hash of arguments for the call {action, path, parameters (array), method, signatures, (array)}
  245. *
  246. * @throws OAuthSimpleException
  247. *
  248. * @return array
  249. */
  250. public function sign($args = array())
  251. {
  252. if (!empty($args['action'])) {
  253. $this->setAction($args['action']);
  254. }
  255. if (!empty($args['path'])) {
  256. $this->setPath($args['path']);
  257. }
  258. if (!empty($args['method'])) {
  259. $this->setSignatureMethod($args['method']);
  260. }
  261. if (!empty($args['signatures'])) {
  262. $this->signatures($args['signatures']);
  263. }
  264. if (empty($args['parameters'])) {
  265. $args['parameters'] = array();
  266. }
  267. $this->setParameters($args['parameters']);
  268. $normParams = $this->_normalizedParameters();
  269. return Array(
  270. 'parameters' => $this->_parameters,
  271. 'signature' => self::_oauthEscape($this->_parameters['oauth_signature']),
  272. 'signed_url' => $this->_path.'?'.$normParams,
  273. 'header' => $this->getHeaderString(),
  274. 'sbs' => $this->sbs,
  275. );
  276. }
  277. /**
  278. * Return a formatted "header" string.
  279. *
  280. * NOTE: This doesn't set the "Authorization: " prefix, which is required.
  281. * It's not set because various set header functions prefer different
  282. * ways to do that.
  283. *
  284. * @param array $args
  285. *
  286. * @throws OAuthSimpleException
  287. *
  288. * @return null|string|string[]
  289. */
  290. public function getHeaderString($args = array())
  291. {
  292. if (empty($this->_parameters['oauth_signature'])) {
  293. $this->sign($args);
  294. }
  295. $result = 'OAuth ';
  296. foreach ($this->_parameters as $pName => $pValue) {
  297. if (strpos($pName, 'oauth_') !== 0) {
  298. continue;
  299. }
  300. if (is_array($pValue)) {
  301. foreach ($pValue as $val) {
  302. $result .= $pName.'="'.self::_oauthEscape($val).'", ';
  303. }
  304. } else {
  305. $result .= $pName.'="'.self::_oauthEscape($pValue).'", ';
  306. }
  307. }
  308. return preg_replace('/, $/', '', $result);
  309. }
  310. /**
  311. * @param string $paramString
  312. *
  313. * @return array
  314. */
  315. private function _parseParameterString($paramString)
  316. {
  317. $elements = explode('&', $paramString);
  318. $result = array();
  319. foreach ($elements as $element) {
  320. list ($key, $token) = explode('=', $element);
  321. if ($token) {
  322. $token = urldecode($token);
  323. }
  324. if (!empty($result[$key])) {
  325. if (!is_array($result[$key])) {
  326. $result[$key] = array($result[$key], $token);
  327. } else {
  328. array_push($result[$key], $token);
  329. }
  330. } else {
  331. $result[$key] = $token;
  332. }
  333. }
  334. return $result;
  335. }
  336. /**
  337. * @param string $string
  338. *
  339. * @throws OAuthSimpleException
  340. *
  341. * @return int|mixed|string
  342. */
  343. private static function _oauthEscape($string)
  344. {
  345. if ($string === 0) {
  346. return 0;
  347. }
  348. if ($string == '0') {
  349. return '0';
  350. }
  351. if (strlen($string) == 0) {
  352. return '';
  353. }
  354. if (is_array($string)) {
  355. throw new OAuthSimpleException('Array passed to _oauthEscape');
  356. }
  357. $string = urlencode($string);
  358. //FIX: urlencode of ~ and '+'
  359. $string = str_replace(
  360. Array('%7E', '+'), // Replace these
  361. Array('~', '%20'), // with these
  362. $string
  363. );
  364. return $string;
  365. }
  366. /**
  367. * @param int $length
  368. *
  369. * @return string
  370. */
  371. private function _getNonce($length = 5)
  372. {
  373. $result = '';
  374. $cLength = strlen($this->_nonce_chars);
  375. for ($i = 0; $i < $length; $i++) {
  376. $rnum = rand(0, $cLength - 1);
  377. $result .= substr($this->_nonce_chars, $rnum, 1);
  378. }
  379. $this->_parameters['oauth_nonce'] = $result;
  380. return $result;
  381. }
  382. /**
  383. * @throws OAuthSimpleException
  384. *
  385. * @return mixed
  386. */
  387. private function _getApiKey()
  388. {
  389. if (empty($this->_secrets['consumer_key'])) {
  390. throw new OAuthSimpleException('No consumer_key set for OAuthSimple');
  391. }
  392. $this->_parameters['oauth_consumer_key'] = $this->_secrets['consumer_key'];
  393. return $this->_parameters['oauth_consumer_key'];
  394. }
  395. /**
  396. * @throws OAuthSimpleException
  397. *
  398. * @return string
  399. */
  400. private function _getAccessToken()
  401. {
  402. if (!isset($this->_secrets['oauth_secret'])) {
  403. return '';
  404. }
  405. if (!isset($this->_secrets['oauth_token'])) {
  406. throw new OAuthSimpleException('No access token (oauth_token) set for OAuthSimple.');
  407. }
  408. $this->_parameters['oauth_token'] = $this->_secrets['oauth_token'];
  409. return $this->_parameters['oauth_token'];
  410. }
  411. /**
  412. * @return int
  413. */
  414. private function _getTimeStamp()
  415. {
  416. return $this->_parameters['oauth_timestamp'] = time();
  417. }
  418. /**
  419. * @throws OAuthSimpleException
  420. *
  421. * @return string
  422. */
  423. private function _normalizedParameters()
  424. {
  425. $normalized_keys = array();
  426. $return_array = array();
  427. foreach ($this->_parameters as $paramName => $paramValue) {
  428. if (preg_match('/w+_secret/', $paramName) OR
  429. $paramName == "oauth_signature") {
  430. continue;
  431. }
  432. // Read parameters from a file. Hope you're practicing safe PHP.
  433. //if (strpos($paramValue, '@') !== 0 && !file_exists(substr($paramValue, 1)))
  434. //{
  435. if (is_array($paramValue)) {
  436. $normalized_keys[self::_oauthEscape($paramName)] = array();
  437. foreach ($paramValue as $item) {
  438. array_push($normalized_keys[self::_oauthEscape($paramName)], self::_oauthEscape($item));
  439. }
  440. } else {
  441. $normalized_keys[self::_oauthEscape($paramName)] = self::_oauthEscape($paramValue);
  442. }
  443. //}
  444. }
  445. ksort($normalized_keys);
  446. foreach ($normalized_keys as $key => $val) {
  447. if (is_array($val)) {
  448. sort($val);
  449. foreach ($val as $element) {
  450. array_push($return_array, $key."=".$element);
  451. }
  452. } else {
  453. array_push($return_array, $key.'='.$val);
  454. }
  455. }
  456. $presig = join("&", $return_array);
  457. $sig = $this->_generateSignature($presig);
  458. $this->_parameters['oauth_signature'] = $sig;
  459. array_push($return_array, "oauth_signature=$sig");
  460. return join("&", $return_array);
  461. }
  462. /**
  463. * @param string $parameters
  464. *
  465. * @throws OAuthSimpleException
  466. *
  467. * @return string
  468. */
  469. private function _generateSignature($parameters = "")
  470. {
  471. $secretKey = '';
  472. if (isset($this->_secrets['shared_secret'])) {
  473. $secretKey = self::_oauthEscape($this->_secrets['shared_secret']);
  474. }
  475. $secretKey .= '&';
  476. if (isset($this->_secrets['oauth_secret'])) {
  477. $secretKey .= self::_oauthEscape($this->_secrets['oauth_secret']);
  478. }
  479. if (!empty($parameters)) {
  480. $parameters = urlencode($parameters);
  481. }
  482. switch ($this->_parameters['oauth_signature_method']) {
  483. case 'PLAINTEXT':
  484. return urlencode($secretKey);;
  485. case 'HMAC-SHA1':
  486. $this->sbs = self::_oauthEscape($this->_action).'&'.self::_oauthEscape($this->_path).'&'.$parameters;
  487. return base64_encode(hash_hmac('sha1', $this->sbs, $secretKey, true));
  488. default:
  489. throw new OAuthSimpleException('Unknown signature method for OAuthSimple');
  490. break;
  491. }
  492. }
  493. /**
  494. * @param $string
  495. *
  496. * @return string
  497. */
  498. public static function generateBodyHash($string)
  499. {
  500. $hash = sha1($string, true);
  501. return base64_encode($hash);
  502. }
  503. /**
  504. * @param string $authorizationHeader
  505. *
  506. * @return array
  507. */
  508. public static function getAuthorizationParams($authorizationHeader)
  509. {
  510. if ('OAuth ' !== substr($authorizationHeader, 0, 6)) {
  511. return [];
  512. }
  513. $params = [];
  514. $authString = str_replace('OAuth ', '', $authorizationHeader);
  515. $authParts = explode(',', $authString);
  516. foreach ($authParts as $authPart) {
  517. list($key, $value) = explode('=', $authPart, 2);
  518. $key = trim($key);
  519. $value = trim($value, " \"");
  520. $params[$key] = urldecode($value);
  521. }
  522. return $params;
  523. }
  524. }
  525. /**
  526. * Class OAuthSimpleException.
  527. */
  528. class OAuthSimpleException extends Exception
  529. {
  530. /**
  531. * OAuthSimpleException constructor.
  532. *
  533. * @param string $err
  534. * @param bool $isDebug
  535. */
  536. public function __construct($err, $isDebug = false)
  537. {
  538. self::log_error($err);
  539. if ($isDebug) {
  540. self::display_error($err, true);
  541. }
  542. }
  543. /**
  544. * @param string $err
  545. */
  546. public static function log_error($err)
  547. {
  548. error_log($err, 0);
  549. }
  550. /**
  551. * @param string $err
  552. * @param bool $kill
  553. */
  554. public static function display_error($err, $kill = false)
  555. {
  556. print_r($err);
  557. if ($kill === false) {
  558. die();
  559. }
  560. }
  561. }