Source for file OAuth.php

Documentation is available at OAuth.php

  1. <?php
  2.  
  3. /**
  4.  *
  5.  * @package OAuth
  6.  * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  7.  *
  8.  *  The MIT License
  9.  *
  10.  *  Copyright (c) 2007 Andy Smith
  11.  *
  12.  *  Permission is hereby granted, free of charge, to any person obtaining a copy
  13.  *  of this software and associated documentation files (the "Software"), to deal
  14.  *  in the Software without restriction, including without limitation the rights
  15.  *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  16.  *  copies of the Software, and to permit persons to whom the Software is
  17.  *  furnished to do so, subject to the following conditions:
  18.  *
  19.  *  The above copyright notice and this permission notice shall be included in
  20.  *  all copies or substantial portions of the Software.
  21.  *
  22.  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  23.  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  24.  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  25.  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  26.  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  27.  *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  28.  *  THE SOFTWARE.
  29.  */
  30.  
  31. class OAuthConsumer {
  32.   var $key;
  33.   var $secret;
  34.  
  35.   function OAuthConsumer($key$secret$callback_url=NULL{
  36.     $this->key = $key;
  37.     $this->secret = $secret;
  38.     $this->callback_url $callback_url;
  39.   }
  40.  
  41.   function __toString({
  42.     return "OAuthConsumer[key=$this->key,secret=$this->secret]";
  43.   }
  44. }
  45.  
  46. class OAuthToken {
  47.   // access tokens and request tokens
  48.   var $key;
  49.   var $secret;
  50.  
  51.   /**
  52.    * key = the token
  53.    * secret = the token secret
  54.    */
  55.   function OAuthToken($key$secret{
  56.     $this->key = $key;
  57.     $this->secret = $secret;
  58.   }
  59.  
  60.   /**
  61.    * generates the basic string serialization of a token that a server
  62.    * would respond to request_token and access_token calls with
  63.    */
  64.   function to_string({
  65.     return "oauth_token=" OAuthUtil::urlencode_rfc3986($this->key.
  66.         "&oauth_token_secret=" OAuthUtil::urlencode_rfc3986($this->secret);
  67.   }
  68.  
  69.   function __toString({
  70.     return $this->to_string();
  71.   }
  72. }
  73.  
  74.   function check_signature(&$request$consumer$token$signature{
  75.     $built $this->build_signature($request$consumer$token);
  76.     return $built == $signature;
  77.   }
  78. }
  79.  
  80.   function get_name({
  81.     return "HMAC-SHA1";
  82.   }
  83.  
  84.   function build_signature($request$consumer$token{
  85.     $base_string $request->get_signature_base_string();
  86.     $request->base_string $base_string;
  87.  
  88.     $key_parts array(
  89.       $consumer->secret,
  90.       ($token$token->secret ""
  91.     );
  92.  
  93.     $key_parts OAuthUtil::urlencode_rfc3986($key_parts);
  94.     $key implode('&'$key_parts);
  95.  
  96.     return base64_encodehash_hmac('sha1'$base_string$keytrue));
  97.   }
  98. }
  99.  
  100.   function get_name({
  101.     return "PLAINTEXT";
  102.   }
  103.  
  104.   function build_signature($request$consumer$token{
  105.     $sig array(
  106.       OAuthUtil::urlencode_rfc3986($consumer->secret)
  107.     );
  108.  
  109.     if ($token{
  110.       array_push($sigOAuthUtil::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.   function get_name({
  124.     return "RSA-SHA1";
  125.   }
  126.  
  127.   function fetch_public_cert(&$request{
  128.     // not implemented yet, ideas are:
  129.     // (1) do a lookup in a table of trusted certs keyed off of consumer
  130.     // (2) fetch via http using a url provided by the requester
  131.     // (3) some sort of specific discovery code based on request
  132.     //
  133.     // either way should return a string representation of the certificate
  134.     trigger_error("fetch_public_cert not implemented"E_USER_WARNING);
  135.     return NULL;
  136.   }
  137.  
  138.   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.     trigger_error("fetch_private_cert not implemented"E_USER_WARNING);
  144.     return NULL;
  145.   }
  146.  
  147.   function build_signature(&$request$consumer$token{
  148.     $base_string $request->get_signature_base_string();
  149.     $request->base_string $base_string;
  150.  
  151.     // Fetch the private key cert based on the request
  152.     $cert $this->fetch_private_cert($request);
  153.  
  154.     // Pull the private key ID from the certificate
  155.     $privatekeyid openssl_get_privatekey($cert);
  156.  
  157.     // Sign using the key
  158.     $ok openssl_sign($base_string$signature$privatekeyid);
  159.  
  160.     // Release the key resource
  161.     openssl_free_key($privatekeyid);
  162.  
  163.     return base64_encode($signature);
  164.   }
  165.  
  166.   function check_signature(&$request$consumer$token$signature{
  167.     $decoded_sig base64_decode($signature);
  168.  
  169.     $base_string $request->get_signature_base_string();
  170.  
  171.     // Fetch the public key cert based on the request
  172.     $cert $this->fetch_public_cert($request);
  173.  
  174.     // Pull the public key ID from the certificate
  175.     $publickeyid openssl_get_publickey($cert);
  176.  
  177.     // Check the computed signature against the one passed in the query
  178.     $ok openssl_verify($base_string$decoded_sig$publickeyid);
  179.  
  180.     // Release the key resource
  181.     openssl_free_key($publickeyid);
  182.  
  183.     return $ok == 1;
  184.   }
  185. }
  186.  
  187. class OAuthRequest {
  188.   var $parameters;
  189.   var $http_method;
  190.   var $http_url;
  191.   // for debug purposes
  192.   var $base_string;
  193.   var $version = '1.0';
  194.  
  195.   function OAuthRequest($http_method$http_url$parameters=NULL{
  196.     @$parameters or $parameters array();
  197.     $this->parameters = $parameters;
  198.     $this->http_method = $http_method;
  199.     $this->http_url = $http_url;
  200.   }
  201.  
  202.   function unset_parameter($name{
  203.     unset($this->parameters[$name]);
  204.   }
  205.  
  206.  
  207.   /**
  208.    * attempt to build up a request from what was passed to the server
  209.    */
  210.   static function from_request($http_method=NULL$http_url=NULL$parameters=NULL{
  211.     $scheme (!isset($_SERVER['HTTPS']|| $_SERVER['HTTPS'!= "on"'http' 'https';
  212.     @$http_url or $http_url $scheme '://' $_SERVER['HTTP_HOST'':' $_SERVER['SERVER_PORT'$_SERVER['REQUEST_URI'];
  213.     @$http_method or $http_method $_SERVER['REQUEST_METHOD'];
  214.  
  215.     $request_headers OAuthRequest::get_headers();
  216.  
  217.     // let the library user override things however they'd like, if they know
  218.     // which parameters to use then go for it, for example XMLRPC might want to
  219.     // do this
  220.     if ($parameters{
  221.       $req new OAuthRequest($http_method$http_url$parameters);
  222.     else {
  223.       // collect request parameters from query string (GET) and post-data (POST) if appropriate (note: POST vars have priority)
  224.       // NOTE: $_GET and $_POST will strip duplicate query parameters
  225.       $req_parameters $_GET;
  226.       if ($http_method == "POST" && @strstr($request_headers["Content-Type"]"application/x-www-form-urlencoded") ) {
  227.         $req_parameters array_merge($req_parameters$_POST);
  228.       }
  229.  
  230.       // next check for the auth header, we need to do some extra stuff
  231.       // if that is the case, namely suck in the parameters from GET or POST
  232.       // so that we can include them in the signature
  233.       if (@substr($request_headers['Authorization']06== "OAuth "{
  234.         $header_parameters OAuthRequest::split_header($request_headers['Authorization']);
  235.         $parameters array_merge($req_parameters$header_parameters);
  236.         $req new OAuthRequest($http_method$http_url$parameters);
  237.       else $req new OAuthRequest($http_method$http_url$req_parameters);
  238.     }
  239.  
  240.     return $req;
  241.   }
  242.  
  243.   /**
  244.    * pretty much a helper function to set up the request
  245.    */
  246.   static function from_consumer_and_token($consumer$token$http_method$http_url$parameters=NULL{
  247.     @$parameters or $parameters array();
  248.     $defaults array("oauth_version" => '1.0',
  249.                       "oauth_nonce" => OAuthRequest::generate_nonce(),
  250.                       "oauth_timestamp" => OAuthRequest::generate_timestamp(),
  251.                       "oauth_consumer_key" => $consumer->key);
  252.     $parameters array_merge($defaults$parameters);
  253.  
  254.     if ($token{
  255.       $parameters['oauth_token'$token->key;
  256.     }
  257.     return new OAuthRequest($http_method$http_url$parameters);
  258.   }
  259.  
  260.   function set_parameter($name$value{
  261.     $this->parameters[$name$value;
  262.   }
  263.  
  264.   function get_parameter($name{
  265.     return isset($this->parameters[$name]$this->parameters[$nameNULL;
  266.   }
  267.  
  268.   function get_parameters({
  269.     return $this->parameters;
  270.   }
  271.  
  272.   /**
  273.    * Returns the normalized parameters of the request
  274.    *
  275.    * This will be all (except oauth_signature) parameters,
  276.    * sorted first by key, and if duplicate keys, then by
  277.    * value.
  278.    *
  279.    * The returned string will be all the key=value pairs
  280.    * concated by &.
  281.    *
  282.    * @return string 
  283.    */
  284.   function get_signable_parameters({
  285.     // Include query parameters
  286.     $query_str parse_url($this->http_urlPHP_URL_QUERY);
  287.     if($query_str{
  288.       $parsed_query OAuthUtil::oauth_parse_string($query_str);
  289.       parse_str($query_str$php_parsed_query);
  290.  
  291.       if(OAuthUtil::oauth_http_build_query($parsed_query!= OAuthUtil::oauth_http_build_query($php_parsed_query)) {
  292.         $parsed_query $php_parsed_query;
  293.       }
  294.  
  295.       foreach($parsed_query as $key => $value{
  296.         $this->set_parameter($key$value);
  297.       }
  298.     }
  299.     // Grab all parameters
  300.     $params $this->parameters;
  301.  
  302.     // Remove oauth_signature if present
  303.     if (isset($params['oauth_signature'])) {
  304.       unset($params['oauth_signature']);
  305.     }
  306.  
  307.     return OAuthUtil::oauth_http_build_query($params);
  308.   }
  309.  
  310.   /**
  311.    * Returns the base string of this request
  312.    *
  313.    * The base string defined as the method, the url
  314.    * and the parameters (normalized), each urlencoded
  315.    * and the concated with &.
  316.    */
  317.   function get_signature_base_string({
  318.     $parts array(
  319.       $this->get_normalized_http_method(),
  320.       $this->get_normalized_http_url(),
  321.       $this->get_signable_parameters()
  322.     );
  323.  
  324.     $parts OAuthUtil::urlencode_rfc3986($parts);
  325.  
  326.     return implode('&'$parts);
  327.   }
  328.  
  329.   /**
  330.    * just uppercases the http method
  331.    */
  332.   function get_normalized_http_method({
  333.     return strtoupper($this->http_method);
  334.   }
  335.  
  336.   /**
  337.    * parses the url and rebuilds it to be
  338.    * scheme://host/path
  339.    */
  340.   function get_normalized_http_url({
  341.     $parts parse_url($this->http_url);
  342.  
  343.     $port @$parts['port'];
  344.     $scheme $parts['scheme'];
  345.     $host $parts['host'];
  346.     $path @$parts['path'];
  347.  
  348.     $port or $port ($scheme == 'https''443' '80';
  349.  
  350.     if (($scheme == 'https' && $port != '443')
  351.         || ($scheme == 'http' && $port != '80')) {
  352.       $host "$host:$port";
  353.     }
  354.     return "$scheme://$host$path";
  355.   }
  356.  
  357.   /**
  358.    * builds a url usable for a GET request
  359.    */
  360.   function to_url({
  361.     $out $this->get_normalized_http_url("?";
  362.     $out .= $this->to_postdata();
  363.     return $out;
  364.   }
  365.  
  366.   /**
  367.    * builds the data one would send in a POST request
  368.    */
  369.   function to_postdata({
  370.     return OAuthUtil::oauth_http_build_query($this->parameters);
  371.   }
  372.  
  373.   /**
  374.    * builds the Authorization: header
  375.    */
  376.   function to_header({
  377.     $out ='Authorization: OAuth realm="yahooapis.com"';
  378.     $total array();
  379.     foreach ($this->parameters as $k => $v{
  380.       if (substr($k05!= "oauth")
  381.       {
  382.         continue;
  383.       }
  384.       if (is_array($v))
  385.       {
  386.          trigger_error('Arrays not supported in headers'E_USER_WARNING);
  387.          return NULL;
  388.       }
  389.       $out .= ',' OAuthUtil::urlencode_rfc3986($k'="' OAuthUtil::urlencode_rfc3986($v'"';
  390.     }
  391.  
  392.     return $out;
  393.   }
  394.  
  395.   function __toString({
  396.     return $this->to_url();
  397.   }
  398.  
  399.  
  400.   function sign_request($signature_method$consumer$token{
  401.     $this->set_parameter("oauth_signature_method"$signature_method->get_name());
  402.     $signature $this->build_signature($signature_method$consumer$token);
  403.     $this->set_parameter("oauth_signature"$signature);
  404.   }
  405.  
  406.   function build_signature($signature_method$consumer$token{
  407.     $signature $signature_method->build_signature($this$consumer$token);
  408.     return $signature;
  409.   }
  410.  
  411.   /**
  412.    * util function: current timestamp
  413.    */
  414.   static function generate_timestamp({
  415.     return time();
  416.   }
  417.  
  418.   /**
  419.    * util function: current nonce
  420.    */
  421.   static function generate_nonce({
  422.     $mt microtime();
  423.     $rand mt_rand();
  424.  
  425.     return md5($mt $rand)// md5s look nicer than numbers
  426.   }
  427.  
  428.   /**
  429.    * util function for turning the Authorization: header into
  430.    * parameters, has to do some unescaping
  431.    */
  432.   static function split_header($header{
  433.     $pattern '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
  434.     $offset 0;
  435.     $params array();
  436.     while (preg_match($pattern$header$matchesPREG_OFFSET_CAPTURE$offset0{
  437.       $match $matches[0];
  438.       $header_name $matches[2][0];
  439.       $header_content (isset($matches[5])) $matches[5][0$matches[4][0];
  440.       $params[$header_nameOAuthUtil::urldecode_rfc3986$header_content );
  441.       $offset $match[1strlen($match[0]);
  442.     }
  443.  
  444.     if (isset($params['realm'])) {
  445.        unset($params['realm']);
  446.     }
  447.  
  448.     return $params;
  449.   }
  450.  
  451.   /**
  452.    * helper to try to sort out headers for people who aren't running apache
  453.    */
  454.   static function get_headers({
  455.     if (function_exists('apache_request_headers')) {
  456.       // we need this to get the actual Authorization: header
  457.       // because apache tends to tell us it doesn't exist
  458.       return apache_request_headers();
  459.     }
  460.     // otherwise we don't have apache and are just going to have to hope
  461.     // that $_SERVER actually contains what we need
  462.     $out array();
  463.     foreach ($_SERVER as $key => $value{
  464.       if (substr($key05== "HTTP_"{
  465.         // this is chaos, basically it is just there to capitalize the first
  466.         // letter of every word that is not an initial HTTP and strip HTTP
  467.         // code from przemek
  468.         $key str_replace(" ""-"ucwords(strtolower(str_replace("_"" "substr($key5)))));
  469.         $out[$key$value;
  470.       }
  471.     }
  472.  
  473.     if(!isset($out['Content-Type'])) {
  474.       $out['Content-Type'@$_SERVER['CONTENT_TYPE'];
  475.     }
  476.  
  477.     return $out;
  478.   }
  479. }
  480.  
  481. class OAuthServer {
  482.   var $timestamp_threshold = 300// in seconds, five minutes
  483.   var $version = '1.0';             // hi blaine
  484.   var $signature_methods = array();
  485.  
  486.   var $data_store;
  487.  
  488.   function OAuthServer($data_store{
  489.     $this->data_store = $data_store;
  490.   }
  491.  
  492.   function add_signature_method($signature_method{
  493.     $this->signature_methods[$signature_method->get_name()=
  494.         $signature_method;
  495.   }
  496.  
  497.   // high level functions
  498.  
  499.   /**
  500.    * process a request_token request
  501.    * returns the request token on success
  502.    */
  503.   function fetch_request_token(&$request{
  504.     $this->get_version($request);
  505.  
  506.     $consumer $this->get_consumer($request);
  507.  
  508.     // no token required for the initial token request
  509.     $token NULL;
  510.  
  511.     $this->check_signature($request$consumer$token);
  512.  
  513.     $new_token $this->data_store->new_request_token($consumer);
  514.  
  515.     return $new_token;
  516.   }
  517.  
  518.   /**
  519.    * process an access_token request
  520.    * returns the access token on success
  521.    */
  522.   function fetch_access_token(&$request{
  523.     $this->get_version($request);
  524.  
  525.     $consumer $this->get_consumer($request);
  526.  
  527.     // requires authorized request token
  528.     $token $this->get_token($request$consumer"request");
  529.  
  530.  
  531.     $this->check_signature($request$consumer$token);
  532.  
  533.     $new_token $this->data_store->new_access_token($token$consumer);
  534.  
  535.     return $new_token;
  536.   }
  537.  
  538.   /**
  539.    * verify an api call, checks all the parameters
  540.    */
  541.   function verify_request(&$request{
  542.     $this->get_version($request);
  543.     $consumer $this->get_consumer($request);
  544.     $token $this->get_token($request$consumer"access");
  545.     $this->check_signature($request$consumer$token);
  546.     return array($consumer$token);
  547.   }
  548.  
  549.   // Internals from here
  550.   /**
  551.    * version 1
  552.    */
  553.   function get_version(&$request{
  554.     $version $request->get_parameter("oauth_version");
  555.     if (!$version{
  556.       $version '1.0';
  557.     }
  558.     if ($version && $version != $this->version{
  559.       trigger_error("OAuth version '$version' not supported"E_USER_WARNING);
  560.       return '1.0';
  561.     }
  562.     return $version;
  563.   }
  564.  
  565.   /**
  566.    * figure out the signature with some defaults
  567.    */
  568.   function get_signature_method(&$request{
  569.     $signature_method =
  570.         @$request->get_parameter("oauth_signature_method");
  571.     if (!$signature_method{
  572.       $signature_method "PLAINTEXT";
  573.     }
  574.     if (!in_array($signature_method,
  575.                   array_keys($this->signature_methods))) {
  576.       trigger_error(
  577.         "Signature method '$signature_method' not supported try one of the following: implode(", "array_keys($this->signature_methods))E_USER_WARNING
  578.       )return NULL;
  579.     }
  580.     return $this->signature_methods[$signature_method];
  581.   }
  582.  
  583.   /**
  584.    * try to find the consumer for the provided request's consumer key
  585.    */
  586.   function get_consumer(&$request{
  587.     $consumer_key @$request->get_parameter("oauth_consumer_key");
  588.     if (!$consumer_key{
  589.       trigger_error("Invalid consumer key"E_USER_WARNING);
  590.       return NULL;
  591.     }
  592.  
  593.     $consumer $this->data_store->lookup_consumer($consumer_key);
  594.     if (!$consumer{
  595.       trigger_error("Invalid consumer"E_USER_WARNING);
  596.       return NULL;
  597.     }
  598.  
  599.     return $consumer;
  600.   }
  601.  
  602.   /**
  603.    * try to find the token for the provided request's token key
  604.    */
  605.   function get_token(&$request$consumer$token_type="access"{
  606.     $token_field @$request->get_parameter('oauth_token');
  607.     $token $this->data_store->lookup_token(
  608.       $consumer$token_type$token_field
  609.     );
  610.     if (!$token{
  611.       trigger_error("Invalid $token_type token: $token_field"E_USER_WARNING);
  612.       return NULL;
  613.     }
  614.     return $token;
  615.   }
  616.  
  617.   /**
  618.    * all-in-one function to check the signature on a request
  619.    * should guess the signature method appropriately
  620.    */
  621.   function check_signature(&$request$consumer$token{
  622.     // this should probably be in a different method
  623.     $timestamp @$request->get_parameter('oauth_timestamp');
  624.     $nonce @$request->get_parameter('oauth_nonce');
  625.  
  626.     $this->check_timestamp($timestamp);
  627.     $this->check_nonce($consumer$token$nonce$timestamp);
  628.  
  629.     $signature_method $this->get_signature_method($request);
  630.  
  631.     $signature $request->get_parameter('oauth_signature');
  632.     $valid_sig $signature_method->check_signature(
  633.       $request,
  634.       $consumer,
  635.       $token,
  636.       $signature
  637.     );
  638.  
  639.     if (!$valid_sig{
  640.       trigger_error("Invalid signature"E_USER_WARNING);
  641.       return NULL;
  642.     }
  643.   }
  644.  
  645.   /**
  646.    * check that the timestamp is new enough
  647.    */
  648.   function check_timestamp($timestamp{
  649.     // verify that timestamp is recentish
  650.     $now time();
  651.     if ($now $timestamp $this->timestamp_threshold{
  652.       trigger_error("Expired timestamp, yours $timestamp, ours $now"E_USER_WARNING);
  653.       return NULL;
  654.     }
  655.   }
  656.  
  657.   /**
  658.    * check that the nonce is not repeated
  659.    */
  660.   function check_nonce($consumer$token$nonce$timestamp{
  661.     // verify that the nonce is uniqueish
  662.     $found $this->data_store->lookup_nonce($consumer$token$nonce$timestamp);
  663.     if ($found{
  664.       trigger_error("Nonce already used: $nonce"E_USER_WARNING);
  665.       return NULL;
  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.   var $dbh;
  703.  
  704.   function SimpleOAuthDataStore($path "oauth.gdbm"{
  705.     $this->dbh = dba_popen($path'c''gdbm');
  706.   }
  707.  
  708.   function __destruct({
  709.     dba_close($this->dbh);
  710.   }
  711.  
  712.   function lookup_consumer($consumer_key{
  713.     $rv dba_fetch("consumer_$consumer_key"$this->dbh);
  714.     if ($rv === FALSE{
  715.       return NULL;
  716.     }
  717.     $obj unserialize($rv);
  718.     if (!($obj instanceof OAuthConsumer)) {
  719.       return NULL;
  720.     }
  721.     return $obj;
  722.   }
  723.  
  724.   function lookup_token($consumer$token_type$token{
  725.     $rv dba_fetch("${token_type}_${token}"$this->dbh);
  726.     if ($rv === FALSE{
  727.       return NULL;
  728.     }
  729.     $obj unserialize($rv);
  730.     if (!($obj instanceof OAuthToken)) {
  731.       return NULL;
  732.     }
  733.     return $obj;
  734.   }
  735.  
  736.   function lookup_nonce($consumer$token$nonce$timestamp{
  737.     if (dba_exists("nonce_$nonce"$this->dbh)) {
  738.       return TRUE;
  739.     else {
  740.       dba_insert("nonce_$nonce""1"$this->dbh);
  741.       return FALSE;
  742.     }
  743.   }
  744.  
  745.   function new_token($consumer$type="request"{
  746.     $key md5(time());
  747.     $secret mt_rand();
  748.     $token new OAuthToken($keymd5($secret));
  749.     if (!dba_insert("${type}_$key"serialize($token)$this->dbh)) {
  750.       trigger_error("doooom!"E_USER_WARNING);
  751.       return NULL;
  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.  
  770.   // This function takes a input like a=b&a=c&d=e and returns the parsed
  771.   // parameters like this
  772.   // array('a' => array('b','c'), 'd' => 'e')
  773.   static function parse_parameters$input {
  774.     if (!isset($input|| !$inputreturn array();
  775.  
  776.     $pairs explode('&'$input);
  777.  
  778.     $parsed_parameters array();
  779.     foreach ($pairs as $pair{
  780.       $split explode('='$pair2);
  781.       $parameter OAuthUtil::urldecode_rfc3986($split[0]);
  782.       $value = isset($split[1]OAuthUtil::urldecode_rfc3986($split[1]'';
  783.  
  784.       if (isset($parsed_parameters[$parameter])) {
  785.         // We have already recieved parameter(s) with this name, so add to the list
  786.         // of parameters with this name
  787.  
  788.         if (is_scalar($parsed_parameters[$parameter])) {
  789.           // This is the first duplicate, so transform scalar (string) into an array
  790.           // so we can add the duplicates
  791.           $parsed_parameters[$parameterarray($parsed_parameters[$parameter]);
  792.         }
  793.  
  794.         $parsed_parameters[$parameter][$value;
  795.       else {
  796.         $parsed_parameters[$parameter$value;
  797.       }
  798.     }
  799.     return $parsed_parameters;
  800.   }
  801.  
  802.   // Utility function for turning the Authorization: header into
  803.   // parameters, has to do some unescaping
  804.   // Can filter out any non-oauth parameters if needed (default behaviour)
  805.   static function split_header($header$only_allow_oauth_parameters true{
  806.     $pattern '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
  807.     $offset 0;
  808.     $params array();
  809.     while (preg_match($pattern$header$matchesPREG_OFFSET_CAPTURE$offset0{
  810.       $match $matches[0];
  811.       $header_name $matches[2][0];
  812.       $header_content (isset($matches[5])) $matches[5][0$matches[4][0];
  813.       if (preg_match('/^oauth_/'$header_name|| !$only_allow_oauth_parameters{
  814.         $params[$header_nameOAuthUtil::urldecode_rfc3986($header_content);
  815.       }
  816.       $offset $match[1strlen($match[0]);
  817.     }
  818.  
  819.     if (isset($params['realm'])) {
  820.        unset($params['realm']);
  821.     }
  822.  
  823.     return $params;
  824.   }
  825.  
  826.   // helper to try to sort out headers for people who aren't running apache
  827.   static function get_headers({
  828.     if (function_exists('apache_request_headers')) {
  829.       // we need this to get the actual Authorization: header
  830.       // because apache tends to tell us it doesn't exist
  831.       return apache_request_headers();
  832.     }
  833.     // otherwise we don't have apache and are just going to have to hope
  834.     // that $_SERVER actually contains what we need
  835.     $out array();
  836.     foreach ($_SERVER as $key => $value{
  837.       if (substr($key05== "HTTP_"{
  838.         // this is chaos, basically it is just there to capitalize the first
  839.         // letter of every word that is not an initial HTTP and strip HTTP
  840.         // code from przemek
  841.         $key str_replace(
  842.           " ",
  843.           "-",
  844.           ucwords(strtolower(str_replace("_"" "substr($key5))))
  845.         );
  846.         $out[$key$value;
  847.       }
  848.     }
  849.     return $out;
  850.   }
  851.  
  852.  
  853.   static function build_http_query($params{
  854.     if (!$paramsreturn '';
  855.  
  856.     // Urlencode both keys and values
  857.     $keys OAuthUtil::urlencode_rfc3986(array_keys($params));
  858.     $values OAuthUtil::urlencode_rfc3986(array_values($params));
  859.     $params array_combine($keys$values);
  860.  
  861.     // Parameters are sorted by name, using lexicographical byte value ordering.
  862.     // Ref: Spec: 9.1.1 (1)
  863.     uksort($params'strcmp');
  864.  
  865.     $pairs array();
  866.     foreach ($params as $parameter => $value{
  867.       if (is_array($value)) {
  868.         // If two or more parameters share the same name, they are sorted by their value
  869.         // Ref: Spec: 9.1.1 (1)
  870.         natsort($value);
  871.         foreach ($value as $duplicate_value{
  872.           $pairs[$parameter '=' $duplicate_value;
  873.         }
  874.       else {
  875.         $pairs[$parameter '=' $value;
  876.       }
  877.     }
  878.     // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
  879.     // Each name-value pair is separated by an '&' character (ASCII code 38)
  880.     return implode('&'$pairs);
  881.   }
  882.  
  883.   static function oauth_parse_string($query_string{
  884.     if(!isset($query_string)) {
  885.       return array();
  886.     }
  887.     $pairs explode('&'$query_string);
  888.     $query_arr array();
  889.     foreach($pairs as $pair{
  890.       list($k$vexplode('='$pair2);
  891.  
  892.       // Handle duplicate query params
  893.       if(isset($query_arr[$k])) {
  894.  
  895.         // Transform scalar to array
  896.         if(is_scalar($query_arr[$k])) {
  897.           $query_arr[$karray($query_arr[$k]);
  898.         }
  899.         $query_arr[$k][$v;
  900.       else {
  901.         $query_arr[$k$v;
  902.       }
  903.     }
  904.     return $query_arr;
  905.   }
  906.  
  907.   static function oauth_http_build_query($params{
  908.  
  909.     $out false;
  910.     if(!empty($params))
  911.     {
  912.       // Urlencode both keys and values
  913.       $keys OAuthUtil::urlencode_rfc3986(array_keys($params));
  914.       $values OAuthUtil::urlencode_rfc3986(array_values($params));
  915.       $params array_combine($keys$values);
  916.  
  917.       // Sort by keys (natsort)
  918.       uksort($params'strcmp');
  919.  
  920.       $pairs array();
  921.       foreach ($params as $k => $v{
  922.         if (is_array($v)) {
  923.           natsort($v);
  924.           foreach ($v as $duplicate_value{
  925.             $pairs[$k '=' $duplicate_value;
  926.           }
  927.         else {
  928.           $pairs[$k '=' $v;
  929.         }
  930.       }
  931.       $out implode('&'$pairs);
  932.     }
  933.     return $out;
  934.   }
  935.  
  936.   static function urlencode_rfc3986($input{
  937.     if (is_array($input)) {
  938.       return array_map(array('OAuthUtil','urlencode_rfc3986')$input);
  939.     else if (is_scalar($input)) {
  940.       return str_replace('+'' ',
  941.                            str_replace('%7E''~'rawurlencode($input)));
  942.     else {
  943.       return '';
  944.     }
  945.   }
  946.  
  947.   // This decode function isn't taking into consideration the above
  948.   // modifications to the encoding process. However, this method doesn't
  949.   // seem to be used anywhere so leaving it as is.
  950.   static function urldecode_rfc3986($string{
  951.     return urldecode($string);
  952.   }
  953.  
  954.   static function urlencodeRFC3986($input{
  955.     return OAuthUtil::urlencode_rfc3986($input);
  956.   }
  957.  
  958.   static function urldecodeRFC3986($input{
  959.     return OAuthUtil::urldecode_rfcC3986($input);
  960.   }
  961. }
  962.  
  963.  
  964. /**
  965.  * Crib'd native implementation of hash_hmac() for SHA1 from the
  966.  * Fire Eagle PHP code:
  967.  *
  968.  * http://fireeagle.yahoo.net/developer/code/php
  969.  */
  970. if (!function_exists("hash_hmac")) {
  971.     // Earlier versions of PHP5 are missing hash_hmac().  Here's a
  972.     // pure-PHP version in case you're using one of them.
  973.     function hash_hmac($algo$data$key{
  974.         // Thanks, Kellan: http://laughingmeme.org/code/hmacsha1.php.txt
  975.         if ($algo != 'sha1'{
  976.             trigger_error("Internal hash_hmac() can only do sha1, sorry"E_USER_WARNING);
  977.             return NULL;
  978.         }
  979.  
  980.         $blocksize 64;
  981.         $hashfunc 'sha1';
  982.         if (strlen($key)>$blocksize)
  983.             $key pack('H*'$hashfunc($key));
  984.         $key str_pad($key,$blocksize,chr(0x00));
  985.         $ipad str_repeat(chr(0x36),$blocksize);
  986.         $opad str_repeat(chr(0x5c),$blocksize);
  987.         $hmac pack(
  988.                 'H*',$hashfunc(
  989.                     ($key^$opad).pack(
  990.                                       'H*',$hashfunc(
  991.                                           ($key^$ipad).$data
  992.                                           )
  993.                                      )
  994.                     )
  995.                 );
  996.         return $hmac;
  997.     }
  998. }

Documentation generated on Thu, 22 Oct 2009 12:54:45 -0700 by phpDocumentor 1.4.3