PageRenderTime 39ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/Vendor/http_socket_oauth.php

http://github.com/mishudark/CakePHP-2.x-Twitter-Plugin
PHP | 299 lines | 118 code | 34 blank | 147 comment | 24 complexity | b92a5e41adc7350193df67369213473b MD5 | raw file
  1. <?php
  2. //Extension for the HttpSocket Class
  3. /**
  4. * Extension to CakePHP core HttpSocket class that overrides the request method
  5. * and intercepts requests whose $request['auth']['method'] param is 'OAuth'.
  6. *
  7. * The correct OAuth Authorization header is determined from the request params
  8. * and then set in the $request['header']['Authorization'] param of the request
  9. * array before passing it back to HttpSocket::request() to send the request and
  10. * parse the response.
  11. *
  12. * So to trigger OAuth, add $request['auth']['method'] = 'OAuth' to your
  13. * request. In addition, you'll need to add your consumer key in the
  14. * $request['auth']['oauth_consumer_key'] and your consumer secret in the
  15. * $request['auth']['oauth_consumer_secret'] param. These are given to you by
  16. * the OAuth provider. And once you have them, $request['auth']['oauth_token']
  17. * and $request['auth']['oauth_token_secret'] params. Your OAuth provider may
  18. * require you to send additional params too. Include them in the
  19. * $request['auth'] array and they'll be passed on in the Authorization header
  20. * and considered when signing the request.
  21. *
  22. * @author Neil Crookes <neil@neilcrookes.com>
  23. * @link http://www.neilcrookes.com
  24. * @copyright (c) 2010 Neil Crookes
  25. * @license MIT License - http://www.opensource.org/licenses/mit-license.php
  26. */
  27. App::uses('HttpSocket', 'Network/Http');
  28. class HttpSocketOauth extends HttpSocket {
  29. /**
  30. * Default OAuth parameters. These get merged into the $request['auth'] param.
  31. *
  32. * @var array
  33. */
  34. var $defaults = array(
  35. 'oauth_version' => '1.0',
  36. 'oauth_signature_method' => 'HMAC-SHA1',
  37. );
  38. /**
  39. * Overrides HttpSocket::request() to handle cases where
  40. * $request['auth']['method'] is 'OAuth'.
  41. *
  42. * @param array $request As required by HttpSocket::request(). NOTE ONLY
  43. * THE ARRAY TYPE OF REQUEST IS SUPPORTED
  44. * @return array
  45. */
  46. function request($request = array()) {
  47. // If the request does not need OAuth Authorization header, let the parent
  48. // deal with it.
  49. if (!isset($request['auth']['method']) || $request['auth']['method'] != 'OAuth') {
  50. return parent::request($request);
  51. }
  52. // Generate the OAuth Authorization Header content for this request from the
  53. // request data and add it into the request's Authorization Header. Note, we
  54. // don't just add the header directly in the request variable and return the
  55. // whole thing from the authorizationHeader() method because in some cases
  56. // we may not want the authorization header content in the request's
  57. // authorization header, for example, OAuth Echo as used by Twitpic and
  58. // Twitter includes an Authorization Header as required by twitter's verify
  59. // credentials API in the X-Verify-Credentials-Authorization header.
  60. $request['header']['Authorization'] = $this->authorizationHeader($request);
  61. // Now the Authorization header is built, fire the request off to the parent
  62. // HttpSocket class request method that we intercepted earlier.
  63. return parent::request($request);
  64. }
  65. /**
  66. * Returns the OAuth Authorization Header string for a given request array.
  67. *
  68. * This method is called by request but can also be called directly, which is
  69. * useful if you need to get the OAuth Authorization Header string, such as
  70. * when integrating with a service that uses OAuth Echo (Authorization
  71. * Delegation) e.g. Twitpic. In this case you send a normal unauthenticated
  72. * request to the service e.g. Twitpic along with 2 extra headers:
  73. * - X-Auth-Service-Provider - effectively, this is the realm that identity
  74. * delegation should be sent to - in the case of Twitter, just set this to
  75. * https://api.twitter.com/1/account/verify_credentials.json;
  76. * - X-Verify-Credentials-Authorization - Consumer should create all the OAuth
  77. * parameters necessary so it could call
  78. * https://api.twitter.com/1/account/verify_credentials.json using OAuth in
  79. * the HTTP header (e.g. it should look like OAuth oauth_consumer_key="...",
  80. * oauth_token="...", oauth_signature_method="...", oauth_signature="...",
  81. * oauth_timestamp="...", oauth_nonce="...", oauth_version="...".
  82. *
  83. * @param array $request As required by HttpSocket::request(). NOTE ONLY
  84. * THE ARRAY TYPE OF REQUEST IS SUPPORTED
  85. * @return String
  86. */
  87. function authorizationHeader($request) {
  88. $request['auth'] = array_merge($this->defaults, $request['auth']);
  89. // Nonce, or number used once is used to distinguish between different
  90. // requests to the OAuth provider
  91. if (!isset($request['auth']['oauth_nonce'])) {
  92. $request['auth']['oauth_nonce'] = md5(uniqid(rand(), true));
  93. }
  94. if (!isset($request['auth']['oauth_timestamp'])) {
  95. $request['auth']['oauth_timestamp'] = time();
  96. }
  97. // Now starts the process of signing the request. The signature is a hash of
  98. // a signature base string with the secret keys. The signature base string
  99. // is made up of the request http verb, the request uri and the request
  100. // params, and the secret keys are the consumer secret (for your
  101. // application) and the access token secret generated for the user by the
  102. // provider, e.g. twitter, when the user authorizes your app to access their
  103. // details.
  104. // Building the request uri, note we don't include the query string or
  105. // fragment. Standard ports must not be included but non standard ones must.
  106. $uriFormat = '%scheme://%host';
  107. if (isset($request['uri']['port']) && !in_array($request['uri']['port'], array(80, 443))) {
  108. $uriFormat .= ':' . $request['uri']['port'];
  109. }
  110. $uriFormat .= '/%path';
  111. $requestUrl = $this->_buildUri($request['uri'], $uriFormat);
  112. // OAuth reference states that the request params, i.e. oauth_ params, body
  113. // params and query string params need to be normalised, i.e. combined in a
  114. // single string, separated by '&' in the format name=value. But they also
  115. // need to be sorted by key, then by value. You can't just merge the auth,
  116. // body and query arrays together then do a ksort because there may be
  117. // parameters with the same name. Instead we've got to get them into an
  118. // array of array('name' => '<name>', 'value' => '<value>') elements, then
  119. // sort those elements.
  120. // Let's start with the auth params - however, we shouldn't include the auth
  121. // method (OAuth), and OAuth reference says not to include the realm or the
  122. // consumer or token secrets
  123. $requestParams = $this->assocToNumericNameValue(array_diff_key(
  124. $request['auth'],
  125. array_flip(array('realm', 'method', 'oauth_consumer_secret', 'oauth_token_secret'))
  126. ));
  127. // Next add the body params if there are any and the content type header is
  128. // not set, or it's application/x-www-form-urlencoded
  129. if (isset($request['body']) && (!isset($request['header']['Content-Type']) || stristr($request['header']['Content-Type'], 'application/x-www-form-urlencoded'))) {
  130. $requestParams = array_merge($requestParams, $this->assocToNumericNameValue($request['body']));
  131. }
  132. // Finally the query params
  133. if (isset($request['uri']['query'])) {
  134. $requestParams = array_merge($requestParams, $this->assocToNumericNameValue($request['uri']['query']));
  135. }
  136. // Now we can sort them by name then value
  137. usort($requestParams, array($this, 'sortByNameThenByValue'));
  138. // Now we concatenate them together in name=value pairs separated by &
  139. $normalisedRequestParams = '';
  140. foreach ($requestParams as $k => $requestParam) {
  141. if ($k) {
  142. $normalisedRequestParams .= '&';
  143. }
  144. $normalisedRequestParams .= $requestParam['name'] . '=' . $this->parameterEncode($requestParam['value']);
  145. }
  146. // The signature base string consists of the request method (uppercased) and
  147. // concatenated with the request URL and normalised request parameters
  148. // string, both encoded, and separated by &
  149. $signatureBaseString = strtoupper($request['method']) . '&'
  150. . $this->parameterEncode($requestUrl) . '&'
  151. . $this->parameterEncode($normalisedRequestParams);
  152. // The signature base string is hashed with a key which is the consumer
  153. // secret (assigned to your application by the provider) and the token
  154. // secret (also known as the access token secret, if you've got it yet),
  155. // both encoded and separated by an &
  156. $key = '';
  157. if (isset($request['auth']['oauth_consumer_secret'])) {
  158. $key .= $this->parameterEncode($request['auth']['oauth_consumer_secret']);
  159. }
  160. $key .= '&';
  161. if (isset($request['auth']['oauth_token_secret'])) {
  162. $key .= $this->parameterEncode($request['auth']['oauth_token_secret']);
  163. }
  164. // Finally construct the signature according to the value of the
  165. // oauth_signature_method auth param in the request array.
  166. switch ($request['auth']['oauth_signature_method']) {
  167. case 'HMAC-SHA1':
  168. $request['auth']['oauth_signature'] = base64_encode(hash_hmac('sha1', $signatureBaseString, $key, true));
  169. break;
  170. default:
  171. // @todo implement the other 2 hashing methods
  172. break;
  173. }
  174. // Finally, we have all the Authorization header parameters so we can build
  175. // the header string.
  176. $authorizationHeader = 'OAuth';
  177. // We don't want to include the realm, method or secrets though
  178. $authorizationHeaderParams = array_diff_key(
  179. $request['auth'],
  180. array_flip(array('method', 'oauth_consumer_secret', 'oauth_token_secret', 'realm'))
  181. );
  182. // Add the Authorization header params to the Authorization header string,
  183. // properly encoded.
  184. $first = true;
  185. if (isset($request['auth']['realm'])) {
  186. $authorizationHeader .= ' realm="' . $request['auth']['realm'] . '"';
  187. $first = false;
  188. }
  189. foreach ($authorizationHeaderParams as $name => $value) {
  190. if (!$first) {
  191. $authorizationHeader .= ',';
  192. } else {
  193. $authorizationHeader .= ' ';
  194. $first = false;
  195. }
  196. $authorizationHeader .= $this->authorizationHeaderParamEncode($name, $value);
  197. }
  198. return $authorizationHeader;
  199. }
  200. /**
  201. * Builds an Authorization header param string from the supplied name and
  202. * value. See below for example:
  203. *
  204. * @param string $name E.g. 'oauth_signature_method'
  205. * @param string $value E.g. 'HMAC-SHA1'
  206. * @return string E.g. 'oauth_signature_method="HMAC-SHA1"'
  207. */
  208. function authorizationHeaderParamEncode($name, $value) {
  209. return $this->parameterEncode($name) . '="' . $this->parameterEncode($value) . '"';
  210. }
  211. /**
  212. * Converts an associative array of name => value pairs to a numerically
  213. * indexed array of array('name' => '<name>', 'value' => '<value>') elements.
  214. *
  215. * @param array $array Associative array
  216. * @return array
  217. */
  218. function assocToNumericNameValue($array) {
  219. $return = array();
  220. foreach ($array as $name => $value) {
  221. $return[] = array(
  222. 'name' => $name,
  223. 'value' => $value,
  224. );
  225. }
  226. return $return;
  227. }
  228. /**
  229. * User defined function to lexically sort an array of
  230. * array('name' => '<name>', 'value' => '<value>') elements by the value of
  231. * the name key, and if they're the same, then by the value of the value key.
  232. *
  233. * @param array $a Array with key for 'name' and one for 'value'
  234. * @param array $b Array with key for 'name' and one for 'value'
  235. * @return integer 1, 0 or -1 depending on whether a greater than b, less than
  236. * or the same.
  237. */
  238. function sortByNameThenByValue($a, $b) {
  239. if ($a['name'] == $b['name']) {
  240. if ($a['value'] == $b['value']) {
  241. return 0;
  242. }
  243. return ($a['value'] > $b['value']) ? 1 : -1;
  244. }
  245. return ($a['name'] > $b['name']) ? 1 : -1;
  246. }
  247. /**
  248. * Encodes paramters as per the OAuth spec by utf 8 encoding the param (if it
  249. * is not already utf 8 encoded) and then percent encoding it according to
  250. * RFC3986
  251. *
  252. * @param string $param
  253. * @return string
  254. */
  255. function parameterEncode($param) {
  256. $encoding = mb_detect_encoding($param);
  257. if ($encoding != 'UTF-8') {
  258. $param = mb_convert_encoding($param, 'UTF-8', $encoding);
  259. }
  260. $param = rawurlencode($param);
  261. $param = str_replace('%7E', '~', $param);
  262. return $param;
  263. }
  264. }
  265. ?>