OAuth.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  1. <?php
  2. // vim: foldmethod=marker
  3. /* Generic exception class
  4. */
  5. class OAuthException extends Exception {
  6. // pass
  7. }
  8. class OAuthConsumer {
  9. public $key;
  10. public $secret;
  11. function __construct($key, $secret, $callback_url=NULL) {
  12. $this->key = $key;
  13. $this->secret = $secret;
  14. $this->callback_url = $callback_url;
  15. }
  16. function __toString() {
  17. return "OAuthConsumer[key=$this->key,secret=$this->secret]";
  18. }
  19. }
  20. class OAuthToken {
  21. // access tokens and request tokens
  22. public $key;
  23. public $secret;
  24. /**
  25. * key = the token
  26. * secret = the token secret
  27. */
  28. function __construct($key, $secret) {
  29. $this->key = $key;
  30. $this->secret = $secret;
  31. }
  32. /**
  33. * generates the basic string serialization of a token that a server
  34. * would respond to request_token and access_token calls with
  35. */
  36. function to_string() {
  37. return "oauth_token=" .
  38. OAuthUtil::urlencode_rfc3986($this->key) .
  39. "&oauth_token_secret=" .
  40. OAuthUtil::urlencode_rfc3986($this->secret);
  41. }
  42. function __toString() {
  43. return $this->to_string();
  44. }
  45. }
  46. /**
  47. * A class for implementing a Signature Method
  48. * See section 9 ("Signing Requests") in the spec
  49. */
  50. abstract class OAuthSignatureMethod {
  51. /**
  52. * Needs to return the name of the Signature Method (ie HMAC-SHA1)
  53. * @return string
  54. */
  55. abstract public function get_name();
  56. /**
  57. * Build up the signature
  58. * NOTE: The output of this function MUST NOT be urlencoded.
  59. * the encoding is handled in OAuthRequest when the final
  60. * request is serialized
  61. * @param OAuthRequest $request
  62. * @param OAuthConsumer $consumer
  63. * @param OAuthToken $token
  64. * @return string
  65. */
  66. abstract public function build_signature($request, $consumer, $token);
  67. /**
  68. * Verifies that a given signature is correct
  69. * @param OAuthRequest $request
  70. * @param OAuthConsumer $consumer
  71. * @param OAuthToken $token
  72. * @param string $signature
  73. * @return bool
  74. */
  75. public function check_signature($request, $consumer, $token, $signature) {
  76. $built = $this->build_signature($request, $consumer, $token);
  77. return $built == $signature;
  78. }
  79. }
  80. /**
  81. * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
  82. * where the Signature Base String is the text and the key is the concatenated values (each first
  83. * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
  84. * character (ASCII code 38) even if empty.
  85. * - Chapter 9.2 ("HMAC-SHA1")
  86. */
  87. class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
  88. function get_name() {
  89. return "HMAC-SHA1";
  90. }
  91. public function build_signature($request, $consumer, $token) {
  92. $base_string = $request->get_signature_base_string();
  93. $request->base_string = $base_string;
  94. $key_parts = array(
  95. $consumer->secret,
  96. ($token) ? $token->secret : ""
  97. );
  98. $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
  99. $key = implode('&', $key_parts);
  100. return base64_encode(hash_hmac('sha1', $base_string, $key, true));
  101. }
  102. }
  103. /**
  104. * The PLAINTEXT method does not provide any security protection and SHOULD only be used
  105. * over a secure channel such as HTTPS. It does not use the Signature Base String.
  106. * - Chapter 9.4 ("PLAINTEXT")
  107. */
  108. class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
  109. public function get_name() {
  110. return "PLAINTEXT";
  111. }
  112. /**
  113. * oauth_signature is set to the concatenated encoded values of the Consumer Secret and
  114. * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is
  115. * empty. The result MUST be encoded again.
  116. * - Chapter 9.4.1 ("Generating Signatures")
  117. *
  118. * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
  119. * OAuthRequest handles this!
  120. */
  121. public function build_signature($request, $consumer, $token) {
  122. $key_parts = array(
  123. $consumer->secret,
  124. ($token) ? $token->secret : ""
  125. );
  126. $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
  127. $key = implode('&', $key_parts);
  128. $request->base_string = $key;
  129. return $key;
  130. }
  131. }
  132. /**
  133. * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in
  134. * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for
  135. * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a
  136. * verified way to the Service Provider, in a manner which is beyond the scope of this
  137. * specification.
  138. * - Chapter 9.3 ("RSA-SHA1")
  139. */
  140. abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
  141. public function get_name() {
  142. return "RSA-SHA1";
  143. }
  144. // Up to the SP to implement this lookup of keys. Possible ideas are:
  145. // (1) do a lookup in a table of trusted certs keyed off of consumer
  146. // (2) fetch via http using a url provided by the requester
  147. // (3) some sort of specific discovery code based on request
  148. //
  149. // Either way should return a string representation of the certificate
  150. protected abstract function fetch_public_cert(&$request);
  151. // Up to the SP to implement this lookup of keys. Possible ideas are:
  152. // (1) do a lookup in a table of trusted certs keyed off of consumer
  153. //
  154. // Either way should return a string representation of the certificate
  155. protected abstract function fetch_private_cert(&$request);
  156. public function build_signature($request, $consumer, $token) {
  157. $base_string = $request->get_signature_base_string();
  158. $request->base_string = $base_string;
  159. // Fetch the private key cert based on the request
  160. $cert = $this->fetch_private_cert($request);
  161. // Pull the private key ID from the certificate
  162. $privatekeyid = openssl_get_privatekey($cert);
  163. // Sign using the key
  164. $ok = openssl_sign($base_string, $signature, $privatekeyid);
  165. // Release the key resource
  166. openssl_free_key($privatekeyid);
  167. return base64_encode($signature);
  168. }
  169. public function check_signature($request, $consumer, $token, $signature) {
  170. $decoded_sig = base64_decode($signature);
  171. $base_string = $request->get_signature_base_string();
  172. // Fetch the public key cert based on the request
  173. $cert = $this->fetch_public_cert($request);
  174. // Pull the public key ID from the certificate
  175. $publickeyid = openssl_get_publickey($cert);
  176. // Check the computed signature against the one passed in the query
  177. $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
  178. // Release the key resource
  179. openssl_free_key($publickeyid);
  180. return $ok == 1;
  181. }
  182. }
  183. class OAuthRequest {
  184. protected $parameters;
  185. protected $http_method;
  186. protected $http_url;
  187. // for debug purposes
  188. public $base_string;
  189. public static $version = '1.0';
  190. public static $POST_INPUT = 'php://input';
  191. function __construct($http_method, $http_url, $parameters=NULL) {
  192. $parameters = ($parameters) ? $parameters : array();
  193. $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
  194. $this->parameters = $parameters;
  195. $this->http_method = $http_method;
  196. $this->http_url = $http_url;
  197. }
  198. /**
  199. * attempt to build up a request from what was passed to the server
  200. */
  201. public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
  202. $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
  203. ? 'http'
  204. : 'https';
  205. $http_url = ($http_url) ? $http_url : $scheme .
  206. '://' . $_SERVER['HTTP_HOST'] .
  207. ':' .
  208. $_SERVER['SERVER_PORT'] .
  209. $_SERVER['REQUEST_URI'];
  210. $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];
  211. // We weren't handed any parameters, so let's find the ones relevant to
  212. // this request.
  213. // If you run XML-RPC or similar you should use this to provide your own
  214. // parsed parameter-list
  215. if (!$parameters) {
  216. // Find request headers
  217. $request_headers = OAuthUtil::get_headers();
  218. // Parse the query-string to find GET parameters
  219. $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
  220. // It's a POST request of the proper content-type, so parse POST
  221. // parameters and add those overriding any duplicates from GET
  222. if ($http_method == "POST"
  223. && isset($request_headers['Content-Type'])
  224. && strstr($request_headers['Content-Type'],
  225. 'application/x-www-form-urlencoded')
  226. ) {
  227. $post_data = OAuthUtil::parse_parameters(
  228. file_get_contents(self::$POST_INPUT)
  229. );
  230. $parameters = array_merge($parameters, $post_data);
  231. }
  232. // We have a Authorization-header with OAuth data. Parse the header
  233. // and add those overriding any duplicates from GET or POST
  234. if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
  235. $header_parameters = OAuthUtil::split_header(
  236. $request_headers['Authorization']
  237. );
  238. $parameters = array_merge($parameters, $header_parameters);
  239. }
  240. }
  241. return new OAuthRequest($http_method, $http_url, $parameters);
  242. }
  243. /**
  244. * pretty much a helper function to set up the request
  245. */
  246. public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
  247. $parameters = ($parameters) ? $parameters : array();
  248. $defaults = array("oauth_version" => OAuthRequest::$version,
  249. "oauth_nonce" => OAuthRequest::generate_nonce(),
  250. "oauth_timestamp" => OAuthRequest::generate_timestamp(),
  251. "oauth_consumer_key" => $consumer->key);
  252. if ($token)
  253. $defaults['oauth_token'] = $token->key;
  254. $parameters = array_merge($defaults, $parameters);
  255. return new OAuthRequest($http_method, $http_url, $parameters);
  256. }
  257. public function set_parameter($name, $value, $allow_duplicates = true) {
  258. if ($allow_duplicates && isset($this->parameters[$name])) {
  259. // We have already added parameter(s) with this name, so add to the list
  260. if (is_scalar($this->parameters[$name])) {
  261. // This is the first duplicate, so transform scalar (string)
  262. // into an array so we can add the duplicates
  263. $this->parameters[$name] = array($this->parameters[$name]);
  264. }
  265. $this->parameters[$name][] = $value;
  266. } else {
  267. $this->parameters[$name] = $value;
  268. }
  269. }
  270. public function get_parameter($name) {
  271. return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
  272. }
  273. public function get_parameters() {
  274. return $this->parameters;
  275. }
  276. public function unset_parameter($name) {
  277. unset($this->parameters[$name]);
  278. }
  279. /**
  280. * The request parameters, sorted and concatenated into a normalized string.
  281. * @return string
  282. */
  283. public function get_signable_parameters() {
  284. // Grab all parameters
  285. $params = $this->parameters;
  286. // Remove oauth_signature if present
  287. // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
  288. if (isset($params['oauth_signature'])) {
  289. unset($params['oauth_signature']);
  290. }
  291. return OAuthUtil::build_http_query($params);
  292. }
  293. /**
  294. * Returns the base string of this request
  295. *
  296. * The base string defined as the method, the url
  297. * and the parameters (normalized), each urlencoded
  298. * and the concated with &.
  299. */
  300. public function get_signature_base_string() {
  301. $parts = array(
  302. $this->get_normalized_http_method(),
  303. $this->get_normalized_http_url(),
  304. $this->get_signable_parameters()
  305. );
  306. $parts = OAuthUtil::urlencode_rfc3986($parts);
  307. return implode('&', $parts);
  308. }
  309. /**
  310. * just uppercases the http method
  311. */
  312. public function get_normalized_http_method() {
  313. return strtoupper($this->http_method);
  314. }
  315. /**
  316. * parses the url and rebuilds it to be
  317. * scheme://host/path
  318. */
  319. public function get_normalized_http_url() {
  320. $parts = parse_url($this->http_url);
  321. $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
  322. $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
  323. $host = (isset($parts['host'])) ? $parts['host'] : '';
  324. $path = (isset($parts['path'])) ? $parts['path'] : '';
  325. if (($scheme == 'https' && $port != '443')
  326. || ($scheme == 'http' && $port != '80')) {
  327. $host = "$host:$port";
  328. }
  329. return "$scheme://$host$path";
  330. }
  331. /**
  332. * builds a url usable for a GET request
  333. */
  334. public function to_url() {
  335. $post_data = $this->to_postdata();
  336. $out = $this->get_normalized_http_url();
  337. if ($post_data) {
  338. $out .= '?'.$post_data;
  339. }
  340. return $out;
  341. }
  342. /**
  343. * builds the data one would send in a POST request
  344. */
  345. public function to_postdata() {
  346. return OAuthUtil::build_http_query($this->parameters);
  347. }
  348. /**
  349. * builds the Authorization: header
  350. */
  351. public function to_header($realm=null) {
  352. $first = true;
  353. if($realm) {
  354. $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
  355. $first = false;
  356. } else
  357. $out = 'Authorization: OAuth';
  358. $total = array();
  359. foreach ($this->parameters as $k => $v) {
  360. if (substr($k, 0, 5) != "oauth") continue;
  361. if (is_array($v)) {
  362. throw new OAuthException('Arrays not supported in headers');
  363. }
  364. $out .= ($first) ? ' ' : ',';
  365. $out .= OAuthUtil::urlencode_rfc3986($k) .
  366. '="' .
  367. OAuthUtil::urlencode_rfc3986($v) .
  368. '"';
  369. $first = false;
  370. }
  371. return $out;
  372. }
  373. public function __toString() {
  374. return $this->to_url();
  375. }
  376. public function sign_request($signature_method, $consumer, $token) {
  377. $this->set_parameter(
  378. "oauth_signature_method",
  379. $signature_method->get_name(),
  380. false
  381. );
  382. $signature = $this->build_signature($signature_method, $consumer, $token);
  383. $this->set_parameter("oauth_signature", $signature, false);
  384. }
  385. public function build_signature($signature_method, $consumer, $token) {
  386. $signature = $signature_method->build_signature($this, $consumer, $token);
  387. return $signature;
  388. }
  389. /**
  390. * util function: current timestamp
  391. */
  392. private static function generate_timestamp() {
  393. return time();
  394. }
  395. /**
  396. * util function: current nonce
  397. */
  398. private static function generate_nonce() {
  399. $mt = microtime();
  400. $rand = mt_rand();
  401. return md5($mt . $rand); // md5s look nicer than numbers
  402. }
  403. }
  404. class OAuthServer {
  405. protected $timestamp_threshold = 300; // in seconds, five minutes
  406. protected $version = '1.0'; // hi blaine
  407. protected $signature_methods = array();
  408. protected $data_store;
  409. function __construct($data_store) {
  410. $this->data_store = $data_store;
  411. }
  412. public function add_signature_method($signature_method) {
  413. $this->signature_methods[$signature_method->get_name()] =
  414. $signature_method;
  415. }
  416. // high level functions
  417. /**
  418. * process a request_token request
  419. * returns the request token on success
  420. */
  421. public function fetch_request_token(&$request) {
  422. $this->get_version($request);
  423. $consumer = $this->get_consumer($request);
  424. // no token required for the initial token request
  425. $token = NULL;
  426. $this->check_signature($request, $consumer, $token);
  427. // Rev A change
  428. $callback = $request->get_parameter('oauth_callback');
  429. $new_token = $this->data_store->new_request_token($consumer, $callback);
  430. return $new_token;
  431. }
  432. /**
  433. * process an access_token request
  434. * returns the access token on success
  435. */
  436. public function fetch_access_token(&$request) {
  437. $this->get_version($request);
  438. $consumer = $this->get_consumer($request);
  439. // requires authorized request token
  440. $token = $this->get_token($request, $consumer, "request");
  441. $this->check_signature($request, $consumer, $token);
  442. // Rev A change
  443. $verifier = $request->get_parameter('oauth_verifier');
  444. $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
  445. return $new_token;
  446. }
  447. /**
  448. * verify an api call, checks all the parameters
  449. */
  450. public function verify_request(&$request) {
  451. $this->get_version($request);
  452. $consumer = $this->get_consumer($request);
  453. $token = $this->get_token($request, $consumer, "access");
  454. $this->check_signature($request, $consumer, $token);
  455. return array($consumer, $token);
  456. }
  457. // Internals from here
  458. /**
  459. * version 1
  460. */
  461. private function get_version(&$request) {
  462. $version = $request->get_parameter("oauth_version");
  463. if (!$version) {
  464. // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
  465. // Chapter 7.0 ("Accessing Protected Ressources")
  466. $version = '1.0';
  467. }
  468. if ($version !== $this->version) {
  469. throw new OAuthException("OAuth version '$version' not supported");
  470. }
  471. return $version;
  472. }
  473. /**
  474. * figure out the signature with some defaults
  475. */
  476. private function get_signature_method($request) {
  477. $signature_method = $request instanceof OAuthRequest
  478. ? $request->get_parameter("oauth_signature_method")
  479. : NULL;
  480. if (!$signature_method) {
  481. // According to chapter 7 ("Accessing Protected Ressources") the signature-method
  482. // parameter is required, and we can't just fallback to PLAINTEXT
  483. throw new OAuthException('No signature method parameter. This parameter is required');
  484. }
  485. if (!in_array($signature_method,
  486. array_keys($this->signature_methods))) {
  487. throw new OAuthException(
  488. "Signature method '$signature_method' not supported " .
  489. "try one of the following: " .
  490. implode(", ", array_keys($this->signature_methods))
  491. );
  492. }
  493. return $this->signature_methods[$signature_method];
  494. }
  495. /**
  496. * try to find the consumer for the provided request's consumer key
  497. */
  498. private function get_consumer($request) {
  499. $consumer_key = $request instanceof OAuthRequest
  500. ? $request->get_parameter("oauth_consumer_key")
  501. : NULL;
  502. if (!$consumer_key) {
  503. throw new OAuthException("Invalid consumer key");
  504. }
  505. $consumer = $this->data_store->lookup_consumer($consumer_key);
  506. if (!$consumer) {
  507. throw new OAuthException("Invalid consumer");
  508. }
  509. return $consumer;
  510. }
  511. /**
  512. * try to find the token for the provided request's token key
  513. */
  514. private function get_token($request, $consumer, $token_type="access") {
  515. $token_field = $request instanceof OAuthRequest
  516. ? $request->get_parameter('oauth_token')
  517. : NULL;
  518. if (!empty($token_field)) {
  519. $token = $this->data_store->lookup_token(
  520. $consumer, $token_type, $token_field
  521. );
  522. if (!$token) {
  523. throw new OAuthException("Invalid $token_type token: $token_field");
  524. }
  525. }
  526. else {
  527. $token = new OAuthToken('', '');
  528. }
  529. return $token;
  530. }
  531. /**
  532. * all-in-one function to check the signature on a request
  533. * should guess the signature method appropriately
  534. */
  535. private function check_signature($request, $consumer, $token) {
  536. // this should probably be in a different method
  537. $timestamp = $request instanceof OAuthRequest
  538. ? $request->get_parameter('oauth_timestamp')
  539. : NULL;
  540. $nonce = $request instanceof OAuthRequest
  541. ? $request->get_parameter('oauth_nonce')
  542. : NULL;
  543. $this->check_timestamp($timestamp);
  544. $this->check_nonce($consumer, $token, $nonce, $timestamp);
  545. $signature_method = $this->get_signature_method($request);
  546. $signature = $request->get_parameter('oauth_signature');
  547. $valid_sig = $signature_method->check_signature(
  548. $request,
  549. $consumer,
  550. $token,
  551. $signature
  552. );
  553. if (!$valid_sig) {
  554. throw new OAuthException("Invalid signature");
  555. }
  556. }
  557. /**
  558. * check that the timestamp is new enough
  559. */
  560. private function check_timestamp($timestamp) {
  561. if( ! $timestamp )
  562. throw new OAuthException(
  563. 'Missing timestamp parameter. The parameter is required'
  564. );
  565. // verify that timestamp is recentish
  566. $now = time();
  567. if (abs($now - $timestamp) > $this->timestamp_threshold) {
  568. throw new OAuthException(
  569. "Expired timestamp, yours $timestamp, ours $now"
  570. );
  571. }
  572. }
  573. /**
  574. * check that the nonce is not repeated
  575. */
  576. private function check_nonce($consumer, $token, $nonce, $timestamp) {
  577. if( ! $nonce )
  578. throw new OAuthException(
  579. 'Missing nonce parameter. The parameter is required'
  580. );
  581. // verify that the nonce is uniqueish
  582. $found = $this->data_store->lookup_nonce(
  583. $consumer,
  584. $token,
  585. $nonce,
  586. $timestamp
  587. );
  588. if ($found) {
  589. throw new OAuthException("Nonce already used: $nonce");
  590. }
  591. }
  592. }
  593. class OAuthDataStore {
  594. function lookup_consumer($consumer_key) {
  595. // implement me
  596. }
  597. function lookup_token($consumer, $token_type, $token) {
  598. // implement me
  599. }
  600. function lookup_nonce($consumer, $token, $nonce, $timestamp) {
  601. // implement me
  602. }
  603. function new_request_token($consumer, $callback = null) {
  604. // return a new token attached to this consumer
  605. }
  606. function new_access_token($token, $consumer, $verifier = null) {
  607. // return a new access token attached to this consumer
  608. // for the user associated with this token if the request token
  609. // is authorized
  610. // should also invalidate the request token
  611. }
  612. }
  613. class OAuthUtil {
  614. public static function urlencode_rfc3986($input) {
  615. if (is_array($input)) {
  616. return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
  617. } else if (is_scalar($input)) {
  618. return str_replace(
  619. '+',
  620. ' ',
  621. str_replace('%7E', '~', rawurlencode($input))
  622. );
  623. } else {
  624. return '';
  625. }
  626. }
  627. // This decode function isn't taking into consideration the above
  628. // modifications to the encoding process. However, this method doesn't
  629. // seem to be used anywhere so leaving it as is.
  630. public static function urldecode_rfc3986($string) {
  631. return urldecode($string);
  632. }
  633. // Utility function for turning the Authorization: header into
  634. // parameters, has to do some unescaping
  635. // Can filter out any non-oauth parameters if needed (default behaviour)
  636. // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
  637. // see http://code.google.com/p/oauth/issues/detail?id=163
  638. public static function split_header($header, $only_allow_oauth_parameters = true) {
  639. $params = array();
  640. if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
  641. foreach ($matches[1] as $i => $h) {
  642. $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
  643. }
  644. if (isset($params['realm'])) {
  645. unset($params['realm']);
  646. }
  647. }
  648. return $params;
  649. }
  650. // helper to try to sort out headers for people who aren't running apache
  651. public static function get_headers() {
  652. if (function_exists('apache_request_headers')) {
  653. // we need this to get the actual Authorization: header
  654. // because apache tends to tell us it doesn't exist
  655. $headers = apache_request_headers();
  656. // sanitize the output of apache_request_headers because
  657. // we always want the keys to be Cased-Like-This and arh()
  658. // returns the headers in the same case as they are in the
  659. // request
  660. $out = array();
  661. foreach ($headers AS $key => $value) {
  662. $key = str_replace(
  663. " ",
  664. "-",
  665. ucwords(strtolower(str_replace("-", " ", $key)))
  666. );
  667. $out[$key] = $value;
  668. }
  669. } else {
  670. // otherwise we don't have apache and are just going to have to hope
  671. // that $_SERVER actually contains what we need
  672. $out = array();
  673. if( isset($_SERVER['CONTENT_TYPE']) )
  674. $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
  675. if( isset($_ENV['CONTENT_TYPE']) )
  676. $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
  677. foreach ($_SERVER as $key => $value) {
  678. if (substr($key, 0, 5) == "HTTP_") {
  679. // this is chaos, basically it is just there to capitalize the first
  680. // letter of every word that is not an initial HTTP and strip HTTP
  681. // code from przemek
  682. $key = str_replace(
  683. " ",
  684. "-",
  685. ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
  686. );
  687. $out[$key] = $value;
  688. }
  689. }
  690. }
  691. return $out;
  692. }
  693. // This function takes a input like a=b&a=c&d=e and returns the parsed
  694. // parameters like this
  695. // array('a' => array('b','c'), 'd' => 'e')
  696. public static function parse_parameters( $input ) {
  697. if (!isset($input) || !$input) return array();
  698. $pairs = explode('&', $input);
  699. $parsed_parameters = array();
  700. foreach ($pairs as $pair) {
  701. $split = explode('=', $pair, 2);
  702. $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
  703. $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
  704. if (isset($parsed_parameters[$parameter])) {
  705. // We have already recieved parameter(s) with this name, so add to the list
  706. // of parameters with this name
  707. if (is_scalar($parsed_parameters[$parameter])) {
  708. // This is the first duplicate, so transform scalar (string) into an array
  709. // so we can add the duplicates
  710. $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
  711. }
  712. $parsed_parameters[$parameter][] = $value;
  713. } else {
  714. $parsed_parameters[$parameter] = $value;
  715. }
  716. }
  717. return $parsed_parameters;
  718. }
  719. public static function build_http_query($params) {
  720. if (!$params) return '';
  721. // Urlencode both keys and values
  722. $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
  723. $values = OAuthUtil::urlencode_rfc3986(array_values($params));
  724. $params = array_combine($keys, $values);
  725. // Parameters are sorted by name, using lexicographical byte value ordering.
  726. // Ref: Spec: 9.1.1 (1)
  727. uksort($params, 'strcmp');
  728. $pairs = array();
  729. foreach ($params as $parameter => $value) {
  730. if (is_array($value)) {
  731. // If two or more parameters share the same name, they are sorted by their value
  732. // Ref: Spec: 9.1.1 (1)
  733. // June 12th, 2010 - changed to sort because of issue 164 by hidetaka
  734. sort($value, SORT_STRING);
  735. foreach ($value as $duplicate_value) {
  736. $pairs[] = $parameter . '=' . $duplicate_value;
  737. }
  738. } else {
  739. $pairs[] = $parameter . '=' . $value;
  740. }
  741. }
  742. // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
  743. // Each name-value pair is separated by an '&' character (ASCII code 38)
  744. return implode('&', $pairs);
  745. }
  746. }