PageRenderTime 50ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Network/Http/Auth/Oauth.php

https://gitlab.com/0072016/0072016-fbphp
PHP | 352 lines | 213 code | 32 blank | 107 comment | 22 complexity | 7325bb4e634b2c95e99048dac1a5446a MD5 | raw file
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  10. * @link http://cakephp.org CakePHP(tm) Project
  11. * @since 3.0.0
  12. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  13. */
  14. namespace Cake\Network\Http\Auth;
  15. use Cake\Core\Exception\Exception;
  16. use Cake\Network\Http\Request;
  17. use Cake\Utility\Security;
  18. /**
  19. * Oauth 1 authentication strategy for Cake\Network\Http\Client
  20. *
  21. * This object does not handle getting Oauth access tokens from the service
  22. * provider. It only handles make client requests *after* you have obtained the Oauth
  23. * tokens.
  24. *
  25. * Generally not directly constructed, but instead used by Cake\Network\Http\Client
  26. * when $options['auth']['type'] is 'oauth'
  27. */
  28. class Oauth
  29. {
  30. /**
  31. * Add headers for Oauth authorization.
  32. *
  33. * @param \Cake\Network\Http\Request $request The request object.
  34. * @param array $credentials Authentication credentials.
  35. * @return void
  36. * @throws \Cake\Core\Exception\Exception On invalid signature types.
  37. */
  38. public function authentication(Request $request, array $credentials)
  39. {
  40. if (!isset($credentials['consumerKey'])) {
  41. return;
  42. }
  43. if (empty($credentials['method'])) {
  44. $credentials['method'] = 'hmac-sha1';
  45. }
  46. $credentials['method'] = strtoupper($credentials['method']);
  47. switch ($credentials['method']) {
  48. case 'HMAC-SHA1':
  49. $hasKeys = isset(
  50. $credentials['consumerSecret'],
  51. $credentials['token'],
  52. $credentials['tokenSecret']
  53. );
  54. if (!$hasKeys) {
  55. return;
  56. }
  57. $value = $this->_hmacSha1($request, $credentials);
  58. break;
  59. case 'RSA-SHA1':
  60. if (!isset($credentials['privateKey'])) {
  61. return;
  62. }
  63. $value = $this->_rsaSha1($request, $credentials);
  64. break;
  65. case 'PLAINTEXT':
  66. $hasKeys = isset(
  67. $credentials['consumerSecret'],
  68. $credentials['token'],
  69. $credentials['tokenSecret']
  70. );
  71. if (!$hasKeys) {
  72. return;
  73. }
  74. $value = $this->_plaintext($request, $credentials);
  75. break;
  76. default:
  77. throw new Exception(sprintf('Unknown Oauth signature method %s', $credentials['method']));
  78. }
  79. $request->header('Authorization', $value);
  80. }
  81. /**
  82. * Plaintext signing
  83. *
  84. * This method is **not** suitable for plain HTTP.
  85. * You should only ever use PLAINTEXT when dealing with SSL
  86. * services.
  87. *
  88. * @param \Cake\Network\Http\Request $request The request object.
  89. * @param array $credentials Authentication credentials.
  90. * @return string Authorization header.
  91. */
  92. protected function _plaintext($request, $credentials)
  93. {
  94. $values = [
  95. 'oauth_version' => '1.0',
  96. 'oauth_nonce' => Security::randomBytes(16),
  97. 'oauth_timestamp' => time(),
  98. 'oauth_signature_method' => 'PLAINTEXT',
  99. 'oauth_token' => $credentials['token'],
  100. 'oauth_consumer_key' => $credentials['consumerKey'],
  101. ];
  102. if (isset($credentials['realm'])) {
  103. $values['oauth_realm'] = $credentials['realm'];
  104. }
  105. $key = [$credentials['consumerSecret'], $credentials['tokenSecret']];
  106. $key = implode('&', $key);
  107. $values['oauth_signature'] = $key;
  108. return $this->_buildAuth($values);
  109. }
  110. /**
  111. * Use HMAC-SHA1 signing.
  112. *
  113. * This method is suitable for plain HTTP or HTTPS.
  114. *
  115. * @param \Cake\Network\Http\Request $request The request object.
  116. * @param array $credentials Authentication credentials.
  117. * @return string
  118. */
  119. protected function _hmacSha1($request, $credentials)
  120. {
  121. $nonce = isset($credentials['nonce']) ? $credentials['nonce'] : bin2hex(Security::randomBytes(16));
  122. $timestamp = isset($credentials['timestamp']) ? $credentials['timestamp'] : time();
  123. $values = [
  124. 'oauth_version' => '1.0',
  125. 'oauth_nonce' => $nonce,
  126. 'oauth_timestamp' => $timestamp,
  127. 'oauth_signature_method' => 'HMAC-SHA1',
  128. 'oauth_token' => $credentials['token'],
  129. 'oauth_consumer_key' => $credentials['consumerKey'],
  130. ];
  131. $baseString = $this->baseString($request, $values);
  132. if (isset($credentials['realm'])) {
  133. $values['oauth_realm'] = $credentials['realm'];
  134. }
  135. $key = [$credentials['consumerSecret'], $credentials['tokenSecret']];
  136. $key = array_map([$this, '_encode'], $key);
  137. $key = implode('&', $key);
  138. $values['oauth_signature'] = base64_encode(
  139. hash_hmac('sha1', $baseString, $key, true)
  140. );
  141. return $this->_buildAuth($values);
  142. }
  143. /**
  144. * Use RSA-SHA1 signing.
  145. *
  146. * This method is suitable for plain HTTP or HTTPS.
  147. *
  148. * @param \Cake\Network\Http\Request $request The request object.
  149. * @param array $credentials Authentication credentials.
  150. * @return string
  151. *
  152. * @throws \RuntimeException
  153. */
  154. protected function _rsaSha1($request, $credentials)
  155. {
  156. if (!function_exists('openssl_pkey_get_private')) {
  157. throw new \RuntimeException('RSA-SHA1 signature method requires the OpenSSL extension.');
  158. }
  159. $nonce = isset($credentials['nonce']) ? $credentials['nonce'] : bin2hex(Security::randomBytes(16));
  160. $timestamp = isset($credentials['timestamp']) ? $credentials['timestamp'] : time();
  161. $values = [
  162. 'oauth_version' => '1.0',
  163. 'oauth_nonce' => $nonce,
  164. 'oauth_timestamp' => $timestamp,
  165. 'oauth_signature_method' => 'RSA-SHA1',
  166. 'oauth_consumer_key' => $credentials['consumerKey'],
  167. ];
  168. if (isset($credentials['consumerSecret'])) {
  169. $values['oauth_consumer_secret'] = $credentials['consumerSecret'];
  170. }
  171. if (isset($credentials['token'])) {
  172. $values['oauth_token'] = $credentials['token'];
  173. }
  174. if (isset($credentials['tokenSecret'])) {
  175. $values['oauth_token_secret'] = $credentials['tokenSecret'];
  176. }
  177. $baseString = $this->baseString($request, $values);
  178. if (isset($credentials['realm'])) {
  179. $values['oauth_realm'] = $credentials['realm'];
  180. }
  181. if (is_resource($credentials['privateKey'])) {
  182. $resource = $credentials['privateKey'];
  183. $privateKey = stream_get_contents($resource);
  184. rewind($resource);
  185. $credentials['privateKey'] = $privateKey;
  186. }
  187. $credentials += [
  188. 'privateKeyPassphrase' => null,
  189. ];
  190. if (is_resource($credentials['privateKeyPassphrase'])) {
  191. $resource = $credentials['privateKeyPassphrase'];
  192. $passphrase = stream_get_line($resource, 0, PHP_EOL);
  193. rewind($resource);
  194. $credentials['privateKeyPassphrase'] = $passphrase;
  195. }
  196. $privateKey = openssl_pkey_get_private($credentials['privateKey'], $credentials['privateKeyPassphrase']);
  197. $signature = '';
  198. openssl_sign($baseString, $signature, $privateKey);
  199. openssl_free_key($privateKey);
  200. $values['oauth_signature'] = base64_encode($signature);
  201. return $this->_buildAuth($values);
  202. }
  203. /**
  204. * Generate the Oauth basestring
  205. *
  206. * - Querystring, request data and oauth_* parameters are combined.
  207. * - Values are sorted by name and then value.
  208. * - Request values are concatenated and urlencoded.
  209. * - The request URL (without querystring) is normalized.
  210. * - The HTTP method, URL and request parameters are concatenated and returned.
  211. *
  212. * @param \Cake\Network\Http\Request $request The request object.
  213. * @param array $oauthValues Oauth values.
  214. * @return string
  215. */
  216. public function baseString($request, $oauthValues)
  217. {
  218. $parts = [
  219. $request->method(),
  220. $this->_normalizedUrl($request->url()),
  221. $this->_normalizedParams($request, $oauthValues),
  222. ];
  223. $parts = array_map([$this, '_encode'], $parts);
  224. return implode('&', $parts);
  225. }
  226. /**
  227. * Builds a normalized URL
  228. *
  229. * Section 9.1.2. of the Oauth spec
  230. *
  231. * @param string $url URL
  232. * @return string Normalized URL
  233. * @throws \Cake\Core\Exception\Exception On invalid URLs
  234. */
  235. protected function _normalizedUrl($url)
  236. {
  237. $parts = parse_url($url);
  238. if (!$parts) {
  239. throw new Exception('Unable to parse URL');
  240. }
  241. $scheme = strtolower($parts['scheme'] ?: 'http');
  242. $defaultPorts = [
  243. 'http' => 80,
  244. 'https' => 443
  245. ];
  246. if (isset($parts['port']) && $parts['port'] != $defaultPorts[$scheme]) {
  247. $parts['host'] .= ':' . $parts['port'];
  248. }
  249. $out = $scheme . '://';
  250. $out .= strtolower($parts['host']);
  251. $out .= $parts['path'];
  252. return $out;
  253. }
  254. /**
  255. * Sorts and normalizes request data and oauthValues
  256. *
  257. * Section 9.1.1 of Oauth spec.
  258. *
  259. * - URL encode keys + values.
  260. * - Sort keys & values by byte value.
  261. *
  262. * @param \Cake\Network\Http\Request $request The request object.
  263. * @param array $oauthValues Oauth values.
  264. * @return string sorted and normalized values
  265. */
  266. protected function _normalizedParams($request, $oauthValues)
  267. {
  268. $query = parse_url($request->url(), PHP_URL_QUERY);
  269. parse_str($query, $queryArgs);
  270. $post = [];
  271. $body = $request->body();
  272. if (is_array($body)) {
  273. $post = $body;
  274. }
  275. $args = array_merge($queryArgs, $oauthValues, $post);
  276. uksort($args, 'strcmp');
  277. $pairs = [];
  278. foreach ($args as $k => $val) {
  279. if (is_array($val)) {
  280. sort($val, SORT_STRING);
  281. foreach ($val as $nestedVal) {
  282. $pairs[] = "$k=$nestedVal";
  283. }
  284. } else {
  285. $pairs[] = "$k=$val";
  286. }
  287. }
  288. return implode('&', $pairs);
  289. }
  290. /**
  291. * Builds the Oauth Authorization header value.
  292. *
  293. * @param array $data The oauth_* values to build
  294. * @return string
  295. */
  296. protected function _buildAuth($data)
  297. {
  298. $out = 'OAuth ';
  299. $params = [];
  300. foreach ($data as $key => $value) {
  301. $params[] = $key . '="' . $this->_encode($value) . '"';
  302. }
  303. $out .= implode(',', $params);
  304. return $out;
  305. }
  306. /**
  307. * URL Encodes a value based on rules of rfc3986
  308. *
  309. * @param string $value Value to encode.
  310. * @return string
  311. */
  312. protected function _encode($value)
  313. {
  314. return str_replace(
  315. '+',
  316. ' ',
  317. str_replace('%7E', '~', rawurlencode($value))
  318. );
  319. }
  320. }