]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/OAuth.php
Provisional OAuth, OpenID token check timing attack patches
[quix0rs-gnu-social.git] / extlib / OAuth.php
1 <?php
2 // vim: foldmethod=marker
3
4 /* Generic exception class
5  */
6 class OAuthException extends Exception {/*{{{*/
7   // pass
8 }/*}}}*/
9
10 class OAuthConsumer {/*{{{*/
11   public $key;
12   public $secret;
13
14   function __construct($key, $secret, $callback_url=NULL) {/*{{{*/
15     $this->key = $key;
16     $this->secret = $secret;
17     $this->callback_url = $callback_url;
18   }/*}}}*/
19
20   function __toString() {/*{{{*/
21     return "OAuthConsumer[key=$this->key,secret=$this->secret]";
22   }/*}}}*/
23 }/*}}}*/
24
25 class OAuthToken {/*{{{*/
26   // access tokens and request tokens
27   public $key;
28   public $secret;
29
30   /**
31    * key = the token
32    * secret = the token secret
33    */
34   function __construct($key, $secret) {/*{{{*/
35     $this->key = $key;
36     $this->secret = $secret;
37   }/*}}}*/
38
39   /**
40    * generates the basic string serialization of a token that a server
41    * would respond to request_token and access_token calls with
42    */
43   function to_string() {/*{{{*/
44     return "oauth_token=" . OAuthUtil::urlencode_rfc3986($this->key) . 
45         "&oauth_token_secret=" . 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     // Check for zero length, although unlikely here
59     if (strlen($built) == 0 || strlen($signature) == 0) {
60       return false;
61     }
62
63     if (strlen($built) != strlen($signature)) {
64       return false;
65     }
66
67     $result = 0;
68
69     // Avoid a timing leak with a (hopefully) time insensitive compare
70     for ($i = 0; $i < strlen($signature); $i++) {
71       $result |= ord($built{$i}) ^ ord($signature{$i});
72     }
73
74     return $result == 0;
75   }
76 }/*}}}*/
77
78 class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {/*{{{*/
79   function get_name() {/*{{{*/
80     return "HMAC-SHA1";
81   }/*}}}*/
82
83   public function build_signature($request, $consumer, $token) {/*{{{*/
84     $base_string = $request->get_signature_base_string();
85     $request->base_string = $base_string;
86
87     $key_parts = array(
88       $consumer->secret,
89       ($token) ? $token->secret : ""
90     );
91
92     $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
93     $key = implode('&', $key_parts);
94
95     return base64_encode( hash_hmac('sha1', $base_string, $key, true));
96   }/*}}}*/
97 }/*}}}*/
98
99 class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {/*{{{*/
100   public function get_name() {/*{{{*/
101     return "PLAINTEXT";
102   }/*}}}*/
103
104   public function build_signature($request, $consumer, $token) {/*{{{*/
105     $sig = array(
106       OAuthUtil::urlencode_rfc3986($consumer->secret)
107     );
108
109     if ($token) {
110       array_push($sig, OAuthUtil::urlencode_rfc3986($token->secret));
111     } else {
112       array_push($sig, '');
113     }
114
115     $raw = implode("&", $sig);
116     // for debug purposes
117     $request->base_string = $raw;
118
119     return OAuthUtil::urlencode_rfc3986($raw);
120   }/*}}}*/
121 }/*}}}*/
122
123 class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {/*{{{*/
124   public function get_name() {/*{{{*/
125     return "RSA-SHA1";
126   }/*}}}*/
127
128   protected function fetch_public_cert(&$request) {/*{{{*/
129     // not implemented yet, ideas are:
130     // (1) do a lookup in a table of trusted certs keyed off of consumer
131     // (2) fetch via http using a url provided by the requester
132     // (3) some sort of specific discovery code based on request
133     //
134     // either way should return a string representation of the certificate
135     throw Exception("fetch_public_cert not implemented");
136   }/*}}}*/
137
138   protected function fetch_private_cert(&$request) {/*{{{*/
139     // not implemented yet, ideas are:
140     // (1) do a lookup in a table of trusted certs keyed off of consumer
141     //
142     // either way should return a string representation of the certificate
143     throw Exception("fetch_private_cert not implemented");
144   }/*}}}*/
145
146   public function build_signature(&$request, $consumer, $token) {/*{{{*/
147     $base_string = $request->get_signature_base_string();
148     $request->base_string = $base_string;
149   
150     // Fetch the private key cert based on the request
151     $cert = $this->fetch_private_cert($request);
152
153     // Pull the private key ID from the certificate
154     $privatekeyid = openssl_get_privatekey($cert);
155
156     // Sign using the key
157     $ok = openssl_sign($base_string, $signature, $privatekeyid);   
158
159     // Release the key resource
160     openssl_free_key($privatekeyid);
161   
162     return base64_encode($signature);
163   } /*}}}*/
164
165   public function check_signature(&$request, $consumer, $token, $signature) {/*{{{*/
166     $decoded_sig = base64_decode($signature);
167
168     $base_string = $request->get_signature_base_string();
169   
170     // Fetch the public key cert based on the request
171     $cert = $this->fetch_public_cert($request);
172
173     // Pull the public key ID from the certificate
174     $publickeyid = openssl_get_publickey($cert);
175
176     // Check the computed signature against the one passed in the query
177     $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);   
178
179     // Release the key resource
180     openssl_free_key($publickeyid);
181   
182     return $ok == 1;
183   } /*}}}*/
184 }/*}}}*/
185
186 class OAuthRequest {/*{{{*/
187   private $parameters;
188   private $http_method;
189   private $http_url;
190   // for debug purposes
191   public $base_string;
192   public static $version = '1.0';
193
194   function __construct($http_method, $http_url, $parameters=NULL) {/*{{{*/
195     @$parameters or $parameters = array();
196     $this->parameters = $parameters;
197     $this->http_method = $http_method;
198     $this->http_url = $http_url;
199   }/*}}}*/
200
201
202   /**
203    * attempt to build up a request from what was passed to the server
204    */
205   public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {/*{{{*/
206     $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https';
207     @$http_url or $http_url = $scheme . '://' . $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI'];
208     @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
209     
210     $request_headers = OAuthRequest::get_headers();
211
212     // let the library user override things however they'd like, if they know
213     // which parameters to use then go for it, for example XMLRPC might want to
214     // do this
215     if ($parameters) {
216       $req = new OAuthRequest($http_method, $http_url, $parameters);
217     } else {
218       // collect request parameters from query string (GET) and post-data (POST) if appropriate (note: POST vars have priority)
219       $req_parameters = $_GET;
220       if ($http_method == "POST" &&
221         ( @strstr($request_headers["Content-Type"], "application/x-www-form-urlencoded") || @strstr($_ENV["CONTENT_TYPE"], "application/x-www-form-urlencoded") )) {
222         $req_parameters = array_merge($req_parameters, $_POST);
223       }
224
225       // next check for the auth header, we need to do some extra stuff
226       // if that is the case, namely suck in the parameters from GET or POST
227       // so that we can include them in the signature
228       if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
229         $header_parameters = OAuthRequest::split_header($request_headers['Authorization']);
230         $parameters = array_merge($req_parameters, $header_parameters);
231         $req = new OAuthRequest($http_method, $http_url, $parameters);
232       } else $req = new OAuthRequest($http_method, $http_url, $req_parameters);
233     }
234
235     return $req;
236   }/*}}}*/
237
238   /**
239    * pretty much a helper function to set up the request
240    */
241   public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {/*{{{*/
242     @$parameters or $parameters = array();
243     $defaults = array("oauth_version" => OAuthRequest::$version,
244                       "oauth_nonce" => OAuthRequest::generate_nonce(),
245                       "oauth_timestamp" => OAuthRequest::generate_timestamp(),
246                       "oauth_consumer_key" => $consumer->key);
247     $parameters = array_merge($defaults, $parameters);
248
249     if ($token) {
250       $parameters['oauth_token'] = $token->key;
251     }
252     return new OAuthRequest($http_method, $http_url, $parameters);
253   }/*}}}*/
254
255   public function set_parameter($name, $value) {/*{{{*/
256     $this->parameters[$name] = $value;
257   }/*}}}*/
258
259   public function get_parameter($name) {/*{{{*/
260     return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
261   }/*}}}*/
262
263   public function get_parameters() {/*{{{*/
264     return $this->parameters;
265   }/*}}}*/
266
267   /**
268    * Returns the normalized parameters of the request
269    * 
270    * This will be all (except oauth_signature) parameters,
271    * sorted first by key, and if duplicate keys, then by
272    * value.
273    *
274    * The returned string will be all the key=value pairs
275    * concated by &.
276    * 
277    * @return string
278    */
279   public function get_signable_parameters() {/*{{{*/
280     // Grab all parameters
281     $params = $this->parameters;
282                 
283     // Remove oauth_signature if present
284     if (isset($params['oauth_signature'])) {
285       unset($params['oauth_signature']);
286     }
287                 
288     // Urlencode both keys and values
289     $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
290     $values = OAuthUtil::urlencode_rfc3986(array_values($params));
291     $params = array_combine($keys, $values);
292
293     // Sort by keys (natsort)
294     uksort($params, 'strcmp');
295
296     // Generate key=value pairs
297     $pairs = array();
298     foreach ($params as $key=>$value ) {
299       if (is_array($value)) {
300         // If the value is an array, it's because there are multiple 
301         // with the same key, sort them, then add all the pairs
302         natsort($value);
303         foreach ($value as $v2) {
304           $pairs[] = $key . '=' . $v2;
305         }
306       } else {
307         $pairs[] = $key . '=' . $value;
308       }
309     }
310                 
311     // Return the pairs, concated with &
312     return implode('&', $pairs);
313   }/*}}}*/
314
315   /**
316    * Returns the base string of this request
317    *
318    * The base string defined as the method, the url
319    * and the parameters (normalized), each urlencoded
320    * and the concated with &.
321    */
322   public function get_signature_base_string() {/*{{{*/
323     $parts = array(
324       $this->get_normalized_http_method(),
325       $this->get_normalized_http_url(),
326       $this->get_signable_parameters()
327     );
328
329     $parts = OAuthUtil::urlencode_rfc3986($parts);
330
331     return implode('&', $parts);
332   }/*}}}*/
333
334   /**
335    * just uppercases the http method
336    */
337   public function get_normalized_http_method() {/*{{{*/
338     return strtoupper($this->http_method);
339   }/*}}}*/
340
341   /**
342    * parses the url and rebuilds it to be
343    * scheme://host/path
344    */
345   public function get_normalized_http_url() {/*{{{*/
346     $parts = parse_url($this->http_url);
347
348     $port = isset($parts['port']) ? $parts['port'] : null;
349     $scheme = $parts['scheme'];
350     $host = $parts['host'];
351     $path = @$parts['path'];
352
353     $port or $port = ($scheme == 'https') ? '443' : '80';
354
355     if (($scheme == 'https' && $port != '443')
356         || ($scheme == 'http' && $port != '80')) {
357       $host = "$host:$port";
358     }
359     return "$scheme://$host$path";
360   }/*}}}*/
361
362   /**
363    * builds a url usable for a GET request
364    */
365   public function to_url() {/*{{{*/
366     $out = $this->get_normalized_http_url() . "?";
367     $out .= $this->to_postdata();
368     return $out;
369   }/*}}}*/
370
371   /**
372    * builds the data one would send in a POST request
373    *
374    * TODO(morten.fangel):
375    * this function might be easily replaced with http_build_query()
376    * and corrections for rfc3986 compatibility.. but not sure
377    */
378   public function to_postdata() {/*{{{*/
379     $total = array();
380     foreach ($this->parameters as $k => $v) {
381       if (is_array($v)) {
382         foreach ($v as $va) {
383           $total[] = OAuthUtil::urlencode_rfc3986($k) . "[]=" . OAuthUtil::urlencode_rfc3986($va);
384         }
385       } else {
386         $total[] = OAuthUtil::urlencode_rfc3986($k) . "=" . OAuthUtil::urlencode_rfc3986($v);
387       }
388     }
389     $out = implode("&", $total);
390     return $out;
391   }/*}}}*/
392
393   /**
394    * builds the Authorization: header
395    */
396   public function to_header() {/*{{{*/
397     $out ='Authorization: OAuth realm=""';
398     $total = array();
399     foreach ($this->parameters as $k => $v) {
400       if (substr($k, 0, 5) != "oauth") continue;
401       if (is_array($v)) throw new OAuthException('Arrays not supported in headers');
402       $out .= ',' . OAuthUtil::urlencode_rfc3986($k) . '="' . OAuthUtil::urlencode_rfc3986($v) . '"';
403     }
404     return $out;
405   }/*}}}*/
406
407   public function __toString() {/*{{{*/
408     return $this->to_url();
409   }/*}}}*/
410
411
412   public function sign_request($signature_method, $consumer, $token) {/*{{{*/
413     $this->set_parameter("oauth_signature_method", $signature_method->get_name());
414     $signature = $this->build_signature($signature_method, $consumer, $token);
415     $this->set_parameter("oauth_signature", $signature);
416   }/*}}}*/
417
418   public function build_signature($signature_method, $consumer, $token) {/*{{{*/
419     $signature = $signature_method->build_signature($this, $consumer, $token);
420     return $signature;
421   }/*}}}*/
422
423   /**
424    * util function: current timestamp
425    */
426   private static function generate_timestamp() {/*{{{*/
427     return time();
428   }/*}}}*/
429
430   /**
431    * util function: current nonce
432    */
433   private static function generate_nonce() {/*{{{*/
434     $mt = microtime();
435     $rand = mt_rand();
436
437     return md5($mt . $rand); // md5s look nicer than numbers
438   }/*}}}*/
439
440   /**
441    * util function for turning the Authorization: header into
442    * parameters, has to do some unescaping
443    */
444   private static function split_header($header) {/*{{{*/
445     $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
446     $offset = 0;
447     $params = array();
448     while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
449       $match = $matches[0];
450       $header_name = $matches[2][0];
451       $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
452       $params[$header_name] = OAuthUtil::urldecode_rfc3986( $header_content );
453       $offset = $match[1] + strlen($match[0]);
454     }
455   
456     if (isset($params['realm'])) {
457        unset($params['realm']);
458     }
459
460     return $params;
461   }/*}}}*/
462
463   /**
464    * helper to try to sort out headers for people who aren't running apache
465    */
466   private static function get_headers() {/*{{{*/
467     if (function_exists('apache_request_headers')) {
468       // we need this to get the actual Authorization: header
469       // because apache tends to tell us it doesn't exist
470       return apache_request_headers();
471     }
472     // otherwise we don't have apache and are just going to have to hope
473     // that $_SERVER actually contains what we need
474     $out = array();
475     foreach ($_SERVER as $key => $value) {
476       if (substr($key, 0, 5) == "HTTP_") {
477         // this is chaos, basically it is just there to capitalize the first
478         // letter of every word that is not an initial HTTP and strip HTTP
479         // code from przemek
480         $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
481         $out[$key] = $value;
482       }
483     }
484     return $out;
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     $new_token = $this->data_store->new_request_token($consumer);
521
522     return $new_token;
523   }/*}}}*/
524
525   /**
526    * process an access_token request
527    * returns the access token on success
528    */
529   public function fetch_access_token(&$request) {/*{{{*/
530     $this->get_version($request);
531
532     $consumer = $this->get_consumer($request);
533
534     // requires authorized request token
535     $token = $this->get_token($request, $consumer, "request");
536
537
538     $this->check_signature($request, $consumer, $token);
539
540     $new_token = $this->data_store->new_access_token($token, $consumer);
541
542     return $new_token;
543   }/*}}}*/
544
545   /**
546    * verify an api call, checks all the parameters
547    */
548   public function verify_request(&$request) {/*{{{*/
549     $this->get_version($request);
550     $consumer = $this->get_consumer($request);
551     $token = $this->get_token($request, $consumer, "access");
552     $this->check_signature($request, $consumer, $token);
553     return array($consumer, $token);
554   }/*}}}*/
555
556   // Internals from here
557   /**
558    * version 1
559    */
560   private function get_version(&$request) {/*{{{*/
561     $version = $request->get_parameter("oauth_version");
562     if (!$version) {
563       $version = 1.0;
564     }
565     if ($version && $version != $this->version) {
566       throw new OAuthException("OAuth version '$version' not supported");
567     }
568     return $version;
569   }/*}}}*/
570
571   /**
572    * figure out the signature with some defaults
573    */
574   private function get_signature_method(&$request) {/*{{{*/
575     $signature_method =  
576         @$request->get_parameter("oauth_signature_method");
577     if (!$signature_method) {
578       $signature_method = "PLAINTEXT";
579     }
580     if (!in_array($signature_method, 
581                   array_keys($this->signature_methods))) {
582       throw new OAuthException(
583         "Signature method '$signature_method' not supported try one of the following: " . implode(", ", array_keys($this->signature_methods))
584       );      
585     }
586     return $this->signature_methods[$signature_method];
587   }/*}}}*/
588
589   /**
590    * try to find the consumer for the provided request's consumer key
591    */
592   private function get_consumer(&$request) {/*{{{*/
593     $consumer_key = @$request->get_parameter("oauth_consumer_key");
594     if (!$consumer_key) {
595       throw new OAuthException("Invalid consumer key");
596     }
597
598     $consumer = $this->data_store->lookup_consumer($consumer_key);
599     if (!$consumer) {
600       throw new OAuthException("Invalid consumer");
601     }
602
603     return $consumer;
604   }/*}}}*/
605
606   /**
607    * try to find the token for the provided request's token key
608    */
609   private function get_token(&$request, $consumer, $token_type="access") {/*{{{*/
610     $token_field = @$request->get_parameter('oauth_token');
611     $token = $this->data_store->lookup_token(
612       $consumer, $token_type, $token_field
613     );
614     if (!$token) {
615       throw new OAuthException("Invalid $token_type token: $token_field");
616     }
617     return $token;
618   }/*}}}*/
619
620   /**
621    * all-in-one function to check the signature on a request
622    * should guess the signature method appropriately
623    */
624   private function check_signature(&$request, $consumer, $token) {/*{{{*/
625     // this should probably be in a different method
626     $timestamp = @$request->get_parameter('oauth_timestamp');
627     $nonce = @$request->get_parameter('oauth_nonce');
628
629     $this->check_timestamp($timestamp);
630     $this->check_nonce($consumer, $token, $nonce, $timestamp);
631
632     $signature_method = $this->get_signature_method($request);
633
634     $signature = $request->get_parameter('oauth_signature');    
635     $valid_sig = $signature_method->check_signature(
636       $request, 
637       $consumer, 
638       $token, 
639       $signature
640     );
641
642     if (!$valid_sig) {
643       throw new OAuthException("Invalid signature");
644     }
645   }/*}}}*/
646
647   /**
648    * check that the timestamp is new enough
649    */
650   private function check_timestamp($timestamp) {/*{{{*/
651     // verify that timestamp is recentish
652     $now = time();
653     if ($now - $timestamp > $this->timestamp_threshold) {
654       throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
655     }
656   }/*}}}*/
657
658   /**
659    * check that the nonce is not repeated
660    */
661   private function check_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
662     // verify that the nonce is uniqueish
663     $found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp);
664     if ($found) {
665       throw new OAuthException("Nonce already used: $nonce");
666     }
667   }/*}}}*/
668
669
670
671 }/*}}}*/
672
673 class OAuthDataStore {/*{{{*/
674   function lookup_consumer($consumer_key) {/*{{{*/
675     // implement me
676   }/*}}}*/
677
678   function lookup_token($consumer, $token_type, $token) {/*{{{*/
679     // implement me
680   }/*}}}*/
681
682   function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
683     // implement me
684   }/*}}}*/
685
686   function new_request_token($consumer) {/*{{{*/
687     // return a new token attached to this consumer
688   }/*}}}*/
689
690   function new_access_token($token, $consumer) {/*{{{*/
691     // return a new access token attached to this consumer
692     // for the user associated with this token if the request token
693     // is authorized
694     // should also invalidate the request token
695   }/*}}}*/
696
697 }/*}}}*/
698
699
700 /*  A very naive dbm-based oauth storage
701  */
702 class SimpleOAuthDataStore extends OAuthDataStore {/*{{{*/
703   private $dbh;
704
705   function __construct($path = "oauth.gdbm") {/*{{{*/
706     $this->dbh = dba_popen($path, 'c', 'gdbm');
707   }/*}}}*/
708
709   function __destruct() {/*{{{*/
710     dba_close($this->dbh);
711   }/*}}}*/
712
713   function lookup_consumer($consumer_key) {/*{{{*/
714     $rv = dba_fetch("consumer_$consumer_key", $this->dbh);
715     if ($rv === FALSE) {
716       return NULL;
717     }
718     $obj = unserialize($rv);
719     if (!($obj instanceof OAuthConsumer)) {
720       return NULL;
721     }
722     return $obj;
723   }/*}}}*/
724
725   function lookup_token($consumer, $token_type, $token) {/*{{{*/
726     $rv = dba_fetch("${token_type}_${token}", $this->dbh);
727     if ($rv === FALSE) {
728       return NULL;
729     }
730     $obj = unserialize($rv);
731     if (!($obj instanceof OAuthToken)) {
732       return NULL;
733     }
734     return $obj;
735   }/*}}}*/
736
737   function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
738     if (dba_exists("nonce_$nonce", $this->dbh)) {
739       return TRUE;
740     } else {
741       dba_insert("nonce_$nonce", "1", $this->dbh);
742       return FALSE;
743     }
744   }/*}}}*/
745
746   function new_token($consumer, $type="request") {/*{{{*/
747     $key = md5(time());
748     $secret = time() + time();
749     $token = new OAuthToken($key, md5(md5($secret)));
750     if (!dba_insert("${type}_$key", serialize($token), $this->dbh)) {
751       throw new OAuthException("doooom!");
752     }
753     return $token;
754   }/*}}}*/
755
756   function new_request_token($consumer) {/*{{{*/
757     return $this->new_token($consumer, "request");
758   }/*}}}*/
759
760   function new_access_token($token, $consumer) {/*{{{*/
761
762     $token = $this->new_token($consumer, 'access');
763     dba_delete("request_" . $token->key, $this->dbh);
764     return $token;
765   }/*}}}*/
766 }/*}}}*/
767
768 class OAuthUtil {/*{{{*/
769   public static function urlencode_rfc3986($input) {/*{{{*/
770         if (is_array($input)) {
771                 return array_map(array('OAuthUtil','urlencode_rfc3986'), $input);
772         } else if (is_scalar($input)) {
773                 return str_replace('+', ' ',
774                                str_replace('%7E', '~', rawurlencode($input)));
775         } else {
776                 return '';
777         }
778   }/*}}}*/
779     
780
781   // This decode function isn't taking into consideration the above 
782   // modifications to the encoding process. However, this method doesn't 
783   // seem to be used anywhere so leaving it as is.
784   public static function urldecode_rfc3986($string) {/*{{{*/
785     return rawurldecode($string);
786   }/*}}}*/
787 }/*}}}*/
788
789 ?>