2019-06-20

Flickr:Oauth認証


◆Oauth認証をするphp

http://temog.info/archives/programming/twitter-oauth-php-login.html を参考に以下のコードを作成した。

後述の FlickrOauth.php と Oauth.php を同じディレクトリに保存して実行する。

oauth_token に access token が、 oauth_token_secret に access token secret が代入され、
javascript で使用できる。

  1. <?php
  2.  
  3. session_start();
  4. require_once('FlickrOauth.php');
  5.  
  6. define('CONSUMER_KEY', '*** Key ***');
  7. define('CONSUMER_SECRET', '*** Secret ***');
  8. define('CALLBACK_URL', '**** 自分自身のファイル名 ***.php');
  9.  
  10. if(! isset($_SESSION['access_token'])){
  11.  
  12. if(! isset($_SESSION['request_token'])){
  13.  
  14. // request token取得
  15. $tw = new FlickrOAuth(CONSUMER_KEY, CONSUMER_SECRET);
  16. $token = $tw->getRequestToken(CALLBACK_URL);
  17.  
  18. // SESSIONに登録
  19. $_SESSION['request_token'] = $token['oauth_token'];
  20. $_SESSION['request_token_secret'] = $token['oauth_token_secret'];
  21.  
  22. // 認証用URL取得してredirect
  23. $authURL = $tw->getAuthorizeURL($_SESSION['request_token']);
  24. header("Location: " . $authURL);
  25.  
  26. }else{
  27.  
  28. // access token 取得
  29. $tw = new FlickrOAuth(CONSUMER_KEY, CONSUMER_SECRET,
  30. $_SESSION['request_token'], $_SESSION['request_token_secret']);
  31. $access_token = $tw->getAccessToken($_REQUEST['oauth_verifier']);
  32.  
  33. // SESSIONに登録
  34. $_SESSION['access_token'] = $access_token['oauth_token'];
  35. $_SESSION['access_token_secret'] = $access_token['oauth_token_secret'];
  36.  
  37. }
  38.  
  39. }
  40.  
  41. ?>
  42.  
  43.  
  44. <script type="text/javascript">
  45.  
  46. var oauth_token = "<?php echo $_SESSION['access_token']; ?>";
  47. var oauth_token_secret = "<?php echo $_SESSION['access_token_secret']; ?>";
  48.  
  49. </script>
  50.  
  51.  


◆FlickrOauth.php

https://github.com/abraham/twitteroauth/tree/master/twitteroauth から twitteroauth.php と Oauth.php をダウンロードする。(2018-12-01現在、ダウンロードできない。)

twitteroauth.php をコピーして以下の FlickrOauth.php を作成した。

2019-06-20 以下変更した。
http://www.flickr.com/services/ → https://www.flickr.com/services/
おそらく、Flickrの仕様が変わったため、httpからhttpsにリダイレクトされphpが動作しなくなった。上記の変更で直った。


FlickrOauth.php
  1. <?php
  2.  
  3. /*
  4. * 以下の twitteroauth.php を Flickr 用に修正した。
  5. *
  6. * Abraham Williams (abraham@abrah.am) http://abrah.am
  7. *
  8. * The first PHP Library to support OAuth for Twitter's REST API.
  9. */
  10.  
  11. /* Load OAuth lib. You can find it at http://oauth.net */
  12. require_once('OAuth.php');
  13.  
  14. /**
  15. * OAuth class
  16. */
  17. class FlickrOAuth {
  18.  
  19. /* Contains the last API call. */
  20. public $url;
  21.  
  22. /**
  23. * Set API URLS
  24. */
  25. function accessTokenURL() { return 'https://www.flickr.com/services/oauth/access_token'; }
  26. function authorizeURL() { return 'https://www.flickr.com/services/oauth/authorize'; }
  27. function requestTokenURL() { return 'https://www.flickr.com/services/oauth/request_token'; }
  28.  
  29. /**
  30. * construct FlickrOAuth object
  31. */
  32. function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) {
  33. $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
  34. $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);
  35. if (!empty($oauth_token) && !empty($oauth_token_secret)) {
  36. $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret);
  37. } else {
  38. $this->token = NULL;
  39. }
  40. }
  41.  
  42. /**
  43. * Get a request_token from Flickr
  44. *
  45. * @returns a key/value array containing oauth_token and oauth_token_secret
  46. */
  47. function getRequestToken($oauth_callback = NULL) {
  48. $parameters = array();
  49. if (!empty($oauth_callback)) {
  50. $parameters['oauth_callback'] = $oauth_callback;
  51. }
  52. $request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters);
  53. $token = OAuthUtil::parse_parameters($request);
  54. $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
  55. return $token;
  56. }
  57.  
  58. /**
  59. * Get the authorize URL
  60. *
  61. * @returns a string
  62. *
  63. * perm を記述するように変更した。
  64. */
  65. function getAuthorizeURL($token, $perm = "read") {
  66. if (is_array($token)) {
  67. $token = $token['oauth_token'];
  68. }
  69. return $this->authorizeURL() . "?oauth_token={$token}&perms=" . $perm;
  70. }
  71.  
  72. /**
  73. * Exchange request token and secret for an access token and
  74. * secret, to sign API calls.
  75. */
  76. function getAccessToken($oauth_verifier = FALSE) {
  77. $parameters = array();
  78. if (!empty($oauth_verifier)) {
  79. $parameters['oauth_verifier'] = $oauth_verifier;
  80. }
  81. $request = $this->oAuthRequest($this->accessTokenURL(), 'GET', $parameters);
  82. $token = OAuthUtil::parse_parameters($request);
  83. $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
  84. return $token;
  85. }
  86.  
  87. /**
  88. * Format and sign an OAuth / API request
  89. */
  90. function oAuthRequest($url, $method, $parameters) {
  91. $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters);
  92. $request->sign_request($this->sha1_method, $this->consumer, $this->token);
  93. return $this->http($request->to_url(), 'GET');
  94. }
  95.  
  96. /**
  97. * Make an HTTP request
  98. *
  99. * @return API results
  100. */
  101. function http($url, $method, $postfields = NULL) {
  102. $ci = curl_init();
  103. /* Curl settings */
  104. curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
  105. curl_setopt($ci, CURLOPT_URL, $url);
  106. $response = curl_exec($ci);
  107. $this->url = $url;
  108. curl_close ($ci);
  109. return $response;
  110. }
  111.  
  112. }
  113.  


◆Oauth.php

注)Oauth.phpで
Fatal error: Cannot declare class OAuthException, because the name is already in use
が発生。
OAuthException→OAuth_Exceptionに変更して解決。

Oauth.php

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




以上