OAuth.php 27 KB

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