OSDN Git Service

embrj
[embrj/master.git] / lib / oauth_lib.php
1 <?php
2
3 /* Generic exception class
4  */
5 class OAuthException extends Exception {
6   // pass
7 }
8
9 class OAuthConsumer {
10   public $key;
11   public $secret;
12
13   function __construct($key, $secret) {
14     $this->key = $key;
15     $this->secret = $secret;
16   }
17
18   function __toString() {
19     return "OAuthConsumer[key=$this->key,secret=$this->secret]";
20   }
21 }
22
23 class OAuthToken {
24   // access tokens and request tokens
25   public $key;
26   public $secret;
27
28   /**
29    * key = the token
30    * secret = the token secret
31    */
32   function __construct($key, $secret) {
33     $this->key = $key;
34     $this->secret = $secret;
35   }
36
37   /**
38    * generates the basic string serialization of a token that a server
39    * would respond to request_token and access_token calls with
40    */
41   function to_string() {
42     return "oauth_token=" .
43            OAuthUtil::urlencode_rfc3986($this->key) .
44            "&oauth_token_secret=" .
45            OAuthUtil::urlencode_rfc3986($this->secret);
46   }
47
48   function __toString() {
49     return $this->to_string();
50   }
51 }
52
53 class OAuthSignatureMethod {
54   public function check_signature(&$request, $consumer, $token, $signature) {
55     $built = $this->build_signature($request, $consumer, $token);
56     return $built == $signature;
57   }
58 }
59
60 class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
61   function get_name() {
62     return "HMAC-SHA1";
63   }
64
65   public function build_signature($request, $consumer, $token) {
66     $base_string = $request->get_signature_base_string();
67     $request->base_string = $base_string;
68
69     $key_parts = array(
70       $consumer->secret,
71       ($token) ? $token->secret : ""
72     );
73
74     $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
75     $key = implode('&', $key_parts);
76
77     return base64_encode(hash_hmac('sha1', $base_string, $key, true));
78   }
79 }
80
81 class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
82   public function get_name() {
83     return "PLAINTEXT";
84   }
85
86   public function build_signature($request, $consumer, $token) {
87     $sig = array(
88       OAuthUtil::urlencode_rfc3986($consumer->secret)
89     );
90
91     if ($token) {
92       array_push($sig, OAuthUtil::urlencode_rfc3986($token->secret));
93     } else {
94       array_push($sig, '');
95     }
96
97     $raw = implode("&", $sig);
98     // for debug purposes
99     $request->base_string = $raw;
100
101     return OAuthUtil::urlencode_rfc3986($raw);
102   }
103 }
104
105 class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
106   public function get_name() {
107     return "RSA-SHA1";
108   }
109
110   protected function fetch_public_cert(&$request) {
111     // not implemented yet, ideas are:
112     // (1) do a lookup in a table of trusted certs keyed off of consumer
113     // (2) fetch via http using a url provided by the requester
114     // (3) some sort of specific discovery code based on request
115     //
116     // either way should return a string representation of the certificate
117     throw Exception("fetch_public_cert not implemented");
118   }
119
120   protected function fetch_private_cert(&$request) {
121     // not implemented yet, ideas are:
122     // (1) do a lookup in a table of trusted certs keyed off of consumer
123     //
124     // either way should return a string representation of the certificate
125     throw Exception("fetch_private_cert not implemented");
126   }
127
128   public function build_signature(&$request, $consumer, $token) {
129     $base_string = $request->get_signature_base_string();
130     $request->base_string = $base_string;
131
132     // Fetch the private key cert based on the request
133     $cert = $this->fetch_private_cert($request);
134
135     // Pull the private key ID from the certificate
136     $privatekeyid = openssl_get_privatekey($cert);
137
138     // Sign using the key
139     $ok = openssl_sign($base_string, $signature, $privatekeyid);
140
141     // Release the key resource
142     openssl_free_key($privatekeyid);
143
144     return base64_encode($signature);
145   }
146
147   public function check_signature(&$request, $consumer, $token, $signature) {
148     $decoded_sig = base64_decode($signature);
149
150     $base_string = $request->get_signature_base_string();
151
152     // Fetch the public key cert based on the request
153     $cert = $this->fetch_public_cert($request);
154
155     // Pull the public key ID from the certificate
156     $publickeyid = openssl_get_publickey($cert);
157
158     // Check the computed signature against the one passed in the query
159     $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
160
161     // Release the key resource
162     openssl_free_key($publickeyid);
163
164     return $ok == 1;
165   }
166 }
167
168 class OAuthRequest {
169   private $parameters;
170   private $http_method;
171   private $http_url;
172   public $http_header;
173   // for debug purposes
174   public $base_string;
175   public static $version = '1.0a';
176   public static $POST_INPUT = 'php://input';
177
178   function __construct($http_method, $http_url, $parameters = array(), $http_header= array()) {
179     //@$parameters or $parameters = array();
180     $this->parameters = $parameters;
181         //@$http_header or $http_header = array();
182         $this->http_header = $http_header;
183     $this->http_method = $http_method;
184     $this->http_url = $http_url;
185   }
186
187
188   /**
189    * attempt to build up a request from what was passed to the server
190    */
191   public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
192     $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
193               ? 'http'
194               : 'https';
195     @$http_url or $http_url = $scheme .
196                               '://' . $_SERVER['HTTP_HOST'] .
197                               ':' .
198                               $_SERVER['SERVER_PORT'] .
199                               $_SERVER['REQUEST_URI'];
200     @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
201
202     // We weren't handed any parameters, so let's find the ones relevant to
203     // this request.
204     // If you run XML-RPC or similar you should use this to provide your own
205     // parsed parameter-list
206     if (!$parameters) {
207       // Find request headers
208       $request_headers = OAuthUtil::get_headers();
209
210       // Parse the query-string to find GET parameters
211       $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
212
213       // It's a POST request of the proper content-type, so parse POST
214       // parameters and add those overriding any duplicates from GET
215       if ($http_method == "POST"
216           && @strstr($request_headers["Content-Type"],
217                      "application/x-www-form-urlencoded")
218           ) {
219         $post_data = OAuthUtil::parse_parameters(
220           file_get_contents(self::$POST_INPUT)
221         );
222         $parameters = array_merge($parameters, $post_data);
223       }
224
225       // We have a Authorization-header with OAuth data. Parse the header
226       // and add those overriding any duplicates from GET or POST
227       if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
228         $header_parameters = OAuthUtil::split_header(
229           $request_headers['Authorization']
230         );
231         $parameters = array_merge($parameters, $header_parameters);
232       }
233
234     }
235
236     return new OAuthRequest($http_method, $http_url, $parameters);
237   }
238
239   /**
240    * pretty much a helper function to set up the request
241    */
242   public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
243     @$parameters or $parameters = array();
244     $defaults = array("oauth_version" => OAuthRequest::$version,
245                       "oauth_nonce" => OAuthRequest::generate_nonce(),
246                       "oauth_timestamp" => OAuthRequest::generate_timestamp(),
247                       "oauth_consumer_key" => $consumer->key);
248     if ($token)
249       $defaults['oauth_token'] = $token->key;
250
251     $parameters = array_merge($defaults, $parameters);
252
253     return new OAuthRequest($http_method, $http_url, $parameters);
254   }
255
256   public function set_parameter($name, $value, $allow_duplicates = true) {
257     if ($allow_duplicates && isset($this->parameters[$name])) {
258       // We have already added parameter(s) with this name, so add to the list
259       if (is_scalar($this->parameters[$name])) {
260         // This is the first duplicate, so transform scalar (string)
261         // into an array so we can add the duplicates
262         $this->parameters[$name] = array($this->parameters[$name]);
263       }
264
265       $this->parameters[$name][] = $value;
266     } else {
267       $this->parameters[$name] = $value;
268     }
269   }
270   
271
272   public function set_http_header(&$multipart = NULL) {
273         if (empty($this->parameters)) {
274                 $this->http_header[] = 'Content-Type:';
275       $this->http_header[] = 'Content-Length:';
276         } else {
277                 if($multipart) $this->http_header[] = $this->to_header(); //add OAuth header if we post multipart
278                 $this->http_header[] = 'Expect: ';
279         }
280   }
281
282   public function get_parameter($name) {
283     return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
284   }
285
286   public function get_parameters() {
287     return $this->parameters;
288   }
289
290   public function unset_parameter($name) {
291     unset($this->parameters[$name]);
292   }
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
302     // Remove oauth_signature if present
303     // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
304     if (isset($params['oauth_signature'])) {
305       unset($params['oauth_signature']);
306     }
307
308     return OAuthUtil::build_http_query($params);
309   }
310
311   /**
312    * Returns the base string of this request
313    *
314    * The base string defined as the method, the url
315    * and the parameters (normalized), each urlencoded
316    * and the concated with &.
317    */
318   public function get_signature_base_string() {
319     $parts = array(
320       $this->get_normalized_http_method(),
321       $this->get_normalized_http_url(),
322       $this->get_signable_parameters()
323     );
324
325     $parts = OAuthUtil::urlencode_rfc3986($parts);
326
327     return implode('&', $parts);
328   }
329
330   /**
331    * just uppercases the http method
332    */
333   public function get_normalized_http_method() {
334     return strtoupper($this->http_method);
335   }
336
337   /**
338    * parses the url and rebuilds it to be
339    * scheme://host/path
340    */
341   public function get_normalized_http_url() {
342     $parts = parse_url($this->http_url);
343
344     $port = @$parts['port'];
345     $scheme = $parts['scheme'];
346     $host = $parts['host'];
347     $path = @$parts['path'];
348
349     $port or $port = ($scheme == 'https') ? '443' : '80';
350
351     if (($scheme == 'https' && $port != '443')
352         || ($scheme == 'http' && $port != '80')) {
353       $host = "$host:$port";
354     }
355     return "$scheme://$host$path";
356   }
357
358   /**
359    * builds a url usable for a GET request
360    */
361   public function to_url() {
362     $post_data = $this->to_postdata();
363     $out = $this->get_normalized_http_url();
364     if ($post_data) {
365       $out .= '?'.$post_data;
366     }
367     return $out;
368   }
369
370   /**
371    * builds the data one would send in a POST request
372    */
373   public function to_postdata() {
374     return OAuthUtil::build_http_query($this->parameters);
375   }
376
377   /**
378    * builds the Authorization: header
379    */
380   public function to_header($realm=NULL) {
381     $out = 'Authorization: OAuth ';
382         if ($realm) $out .= 'realm="'.$realm.'",';
383     $total = array();
384     foreach ($this->parameters as $k => $v) {
385       if (substr($k, 0, 5) != "oauth") continue;
386       if (is_array($v)) {
387         throw new OAuthException('Arrays not supported in headers');
388       }
389       $out .= OAuthUtil::urlencode_rfc3986($k) .
390               '="' .
391               OAuthUtil::urlencode_rfc3986($v) .
392               '",';
393     }
394     return substr($out,0,-1);
395   }
396
397   public function __toString() {
398     return $this->to_url();
399   }
400
401
402   public function sign_request($signature_method, $consumer, $token) {
403     $this->set_parameter(
404       "oauth_signature_method",
405       $signature_method->get_name(),
406       false
407     );
408     $signature = $this->build_signature($signature_method, $consumer, $token);
409     $this->set_parameter("oauth_signature", $signature, false);
410   }
411
412   public function build_signature($signature_method, $consumer, $token) {
413     $signature = $signature_method->build_signature($this, $consumer, $token);
414     return $signature;
415   }
416
417   /**
418    * util function: current timestamp
419    */
420   private static function generate_timestamp() {
421     return $_SERVER['REQUEST_TIME'];
422   }
423
424   /**
425    * util function: current nonce
426    */
427   private static function generate_nonce() {
428     $mt = microtime();
429     $rand = mt_rand();
430
431     return md5($mt . $rand); // md5s look nicer than numbers
432   }
433 }
434
435 class OAuthUtil {
436   public static function urlencode_rfc3986($input) {
437   if (is_array($input)) {
438     return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
439   } else if (is_scalar($input)) {
440     return str_replace(
441       '+',
442       ' ',
443       str_replace('%7E', '~', rawurlencode($input))
444     );
445   } else {
446     return '';
447   }
448 }
449
450
451   // This decode function isn't taking into consideration the above
452   // modifications to the encoding process. However, this method doesn't
453   // seem to be used anywhere so leaving it as is.
454   public static function urldecode_rfc3986($string) {
455     return urldecode($string);
456   }
457
458   // Utility function for turning the Authorization: header into
459   // parameters, has to do some unescaping
460   // Can filter out any non-oauth parameters if needed (default behaviour)
461   public static function split_header($header, $only_allow_oauth_parameters = true) {
462     $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
463     $offset = 0;
464     $params = array();
465     while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
466       $match = $matches[0];
467       $header_name = $matches[2][0];
468       $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
469       if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) {
470         $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content);
471       }
472       $offset = $match[1] + strlen($match[0]);
473     }
474
475     if (isset($params['realm'])) {
476       unset($params['realm']);
477     }
478
479     return $params;
480   }
481
482   // helper to try to sort out headers for people who aren't running apache
483   public static function get_headers() {
484     if (function_exists('apache_request_headers')) {
485       // we need this to get the actual Authorization: header
486       // because apache tends to tell us it doesn't exist
487       return apache_request_headers();
488     }
489     // otherwise we don't have apache and are just going to have to hope
490     // that $_SERVER actually contains what we need
491     $out = array();
492     foreach ($_SERVER as $key => $value) {
493       if (substr($key, 0, 5) == "HTTP_") {
494         // this is chaos, basically it is just there to capitalize the first
495         // letter of every word that is not an initial HTTP and strip HTTP
496         // code from przemek
497         $key = str_replace(
498           " ",
499           "-",
500           ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
501         );
502         $out[$key] = $value;
503       }
504     }
505     return $out;
506   }
507
508   // This function takes a input like a=b&a=c&d=e and returns the parsed
509   // parameters like this
510   // array('a' => array('b','c'), 'd' => 'e')
511   public static function parse_parameters( $input ) {
512     if (!isset($input) || !$input) return array();
513
514     $pairs = explode('&', $input);
515
516     $parsed_parameters = array();
517     foreach ($pairs as $pair) {
518       $split = explode('=', $pair, 2);
519       $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
520       $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
521
522       if (isset($parsed_parameters[$parameter])) {
523         // We have already recieved parameter(s) with this name, so add to the list
524         // of parameters with this name
525
526         if (is_scalar($parsed_parameters[$parameter])) {
527           // This is the first duplicate, so transform scalar (string) into an array
528           // so we can add the duplicates
529           $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
530         }
531
532         $parsed_parameters[$parameter][] = $value;
533       } else {
534         $parsed_parameters[$parameter] = $value;
535       }
536     }
537     return $parsed_parameters;
538   }
539
540   public static function build_http_query($params,$multipart=NULL) {
541     if (!$params) return '';
542
543     // Urlencode both keys and values
544     $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
545     $values = OAuthUtil::urlencode_rfc3986(array_values($params));
546     $params = array_combine($keys, $values);
547
548     // Parameters are sorted by name, using lexicographical byte value ordering.
549     // Ref: Spec: 9.1.1 (1)
550     uksort($params, 'strcmp');
551
552     $pairs = array();
553     foreach ($params as $parameter => $value) {
554       if (is_array($value)) {
555         // If two or more parameters share the same name, they are sorted by their value
556         // Ref: Spec: 9.1.1 (1)
557         natsort($value);
558         foreach ($value as $duplicate_value) {
559           $pairs[] = $parameter . '=' . $duplicate_value;
560         }
561       } else {
562         $pairs[] = $parameter . '=' . $value;
563       }
564     }
565     // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
566     // Each name-value pair is separated by an '&' character (ASCII code 38)
567     return implode('&', $pairs);
568   }
569 }
570
571 ?>