├── README ├── example └── index.php ├── twitterOAuth.php └── OAuth.php /README: -------------------------------------------------------------------------------- 1 | Abraham Williams | abraham@abrah.am | http://abrah.am | @abraham 2 | 3 | Basic example of working with Twitter's OAuth beta. 4 | 5 | Docs: https://docs.google.com/View?docID=dcf2dzzs_2339fzbfsf4 6 | -------------------------------------------------------------------------------- /example/index.php: -------------------------------------------------------------------------------- 1 | getRequestToken(); 45 | 46 | /* Save tokens for later */ 47 | $_SESSION['oauth_request_token'] = $token = $tok['oauth_token']; 48 | $_SESSION['oauth_request_token_secret'] = $tok['oauth_token_secret']; 49 | $_SESSION['oauth_state'] = "start"; 50 | 51 | /* Build the authorization URL */ 52 | $request_link = $to->getAuthorizeURL($token); 53 | 54 | /* Build link that gets user to twitter to authorize the app */ 55 | $content = 'Click on the link to go to twitter to authorize your account.'; 56 | $content .= '
'.$request_link.''; 57 | break; 58 | case 'returned': 59 | /* If the access tokens are already set skip to the API call */ 60 | if ($_SESSION['oauth_access_token'] === NULL && $_SESSION['oauth_access_token_secret'] === NULL) { 61 | /* Create TwitterOAuth object with app key/secret and token key/secret from default phase */ 62 | $to = new TwitterOAuth($consumer_key, $consumer_secret, $_SESSION['oauth_request_token'], $_SESSION['oauth_request_token_secret']); 63 | /* Request access tokens from twitter */ 64 | $tok = $to->getAccessToken(); 65 | 66 | /* Save the access tokens. Normally these would be saved in a database for future use. */ 67 | $_SESSION['oauth_access_token'] = $tok['oauth_token']; 68 | $_SESSION['oauth_access_token_secret'] = $tok['oauth_token_secret']; 69 | } 70 | /* Random copy */ 71 | $content = 'your account should now be registered with twitter. Check here:
'; 72 | $content .= 'https://twitter.com/account/connections'; 73 | 74 | /* Create TwitterOAuth with app key/secret and user access key/secret */ 75 | $to = new TwitterOAuth($consumer_key, $consumer_secret, $_SESSION['oauth_access_token'], $_SESSION['oauth_access_token_secret']); 76 | /* Run request on twitter API as user. */ 77 | $content = $to->OAuthRequest('https://twitter.com/account/verify_credentials.xml', array(), 'GET'); 78 | //$content = $to->OAuthRequest('https://twitter.com/statuses/update.xml', array('status' => 'Test OAuth update. #testoauth'), 'POST'); 79 | //$content = $to->OAuthRequest('https://twitter.com/statuses/replies.xml', array(), 'POST'); 80 | break; 81 | }/*}}}*/ 82 | ?> 83 | 84 | 85 | 86 | Twitter OAuth in PHP 87 | 88 | 89 |

Welcome to a Twitter OAuth PHP example.

90 |

This site is a basic showcase of Twitters new OAuth authentication method. Everything is saved in sessions. If you want to start over ?test=clear'>clear sessions.

91 | 92 |

93 | Get the code powering this at http://github.com/abraham/twitteroauth 94 |
95 | Read the documentation at https://docs.google.com/View?docID=dcf2dzzs_2339fzbfsf4 96 |

97 | 98 |

99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /twitterOAuth.php: -------------------------------------------------------------------------------- 1 | http_status; } 40 | function lastAPICall() { return $this->last_api_call; } 41 | 42 | /** 43 | * construct TwitterOAuth object 44 | */ 45 | function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) {/*{{{*/ 46 | $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); 47 | $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); 48 | if (!empty($oauth_token) && !empty($oauth_token_secret)) { 49 | $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret); 50 | } else { 51 | $this->token = NULL; 52 | } 53 | }/*}}}*/ 54 | 55 | 56 | /** 57 | * Get a request_token from Twitter 58 | * 59 | * @returns a key/value array containing oauth_token and oauth_token_secret 60 | */ 61 | function getRequestToken() {/*{{{*/ 62 | $r = $this->oAuthRequest($this->requestTokenURL()); 63 | $token = $this->oAuthParseResponse($r); 64 | $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); 65 | return $token; 66 | }/*}}}*/ 67 | 68 | /** 69 | * Parse a URL-encoded OAuth response 70 | * 71 | * @return a key/value array 72 | */ 73 | function oAuthParseResponse($responseString) { 74 | $r = array(); 75 | foreach (explode('&', $responseString) as $param) { 76 | $pair = explode('=', $param, 2); 77 | if (count($pair) != 2) continue; 78 | $r[urldecode($pair[0])] = urldecode($pair[1]); 79 | } 80 | return $r; 81 | } 82 | 83 | /** 84 | * Get the authorize URL 85 | * 86 | * @returns a string 87 | */ 88 | function getAuthorizeURL($token) {/*{{{*/ 89 | if (is_array($token)) $token = $token['oauth_token']; 90 | return $this->authorizeURL() . '?oauth_token=' . $token; 91 | }/*}}}*/ 92 | 93 | /** 94 | * Exchange the request token and secret for an access token and 95 | * secret, to sign API calls. 96 | * 97 | * @returns array("oauth_token" => the access token, 98 | * "oauth_token_secret" => the access secret) 99 | */ 100 | function getAccessToken($token = NULL) {/*{{{*/ 101 | $r = $this->oAuthRequest($this->accessTokenURL()); 102 | $token = $this->oAuthParseResponse($r); 103 | $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); 104 | return $token; 105 | }/*}}}*/ 106 | 107 | /** 108 | * Format and sign an OAuth / API request 109 | */ 110 | function oAuthRequest($url, $args = array(), $method = NULL) {/*{{{*/ 111 | if (empty($method)) $method = empty($args) ? "GET" : "POST"; 112 | $req = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $args); 113 | $req->sign_request($this->sha1_method, $this->consumer, $this->token); 114 | switch ($method) { 115 | case 'GET': return $this->http($req->to_url()); 116 | case 'POST': return $this->http($req->get_normalized_http_url(), $req->to_postdata()); 117 | } 118 | }/*}}}*/ 119 | 120 | /** 121 | * Make an HTTP request 122 | * 123 | * @return API results 124 | */ 125 | function http($url, $post_data = null) {/*{{{*/ 126 | $ch = curl_init(); 127 | if (defined("CURL_CA_BUNDLE_PATH")) curl_setopt($ch, CURLOPT_CAINFO, CURL_CA_BUNDLE_PATH); 128 | curl_setopt($ch, CURLOPT_URL, $url); 129 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); 130 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); 131 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 132 | ////////////////////////////////////////////////// 133 | ///// Set to 1 to verify Twitter's SSL Cert ////// 134 | ////////////////////////////////////////////////// 135 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 136 | if (isset($post_data)) { 137 | curl_setopt($ch, CURLOPT_POST, 1); 138 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); 139 | } 140 | $response = curl_exec($ch); 141 | $this->http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); 142 | $this->last_api_call = $url; 143 | curl_close ($ch); 144 | return $response; 145 | }/*}}}*/ 146 | }/*}}}*/ -------------------------------------------------------------------------------- /OAuth.php: -------------------------------------------------------------------------------- 1 | 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 | }/*}}}*/ 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 | // for debug purposes 173 | public $base_string; 174 | public static $version = '1.0'; 175 | 176 | function __construct($http_method, $http_url, $parameters=NULL) {/*{{{*/ 177 | @$parameters or $parameters = array(); 178 | $this->parameters = $parameters; 179 | $this->http_method = $http_method; 180 | $this->http_url = $http_url; 181 | }/*}}}*/ 182 | 183 | 184 | /** 185 | * attempt to build up a request from what was passed to the server 186 | */ 187 | public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {/*{{{*/ 188 | $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https'; 189 | @$http_url or $http_url = $scheme . '://' . $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI']; 190 | @$http_method or $http_method = $_SERVER['REQUEST_METHOD']; 191 | 192 | $request_headers = OAuthRequest::get_headers(); 193 | 194 | // let the library user override things however they'd like, if they know 195 | // which parameters to use then go for it, for example XMLRPC might want to 196 | // do this 197 | if ($parameters) { 198 | $req = new OAuthRequest($http_method, $http_url, $parameters); 199 | } else { 200 | // collect request parameters from query string (GET) and post-data (POST) if appropriate (note: POST vars have priority) 201 | $req_parameters = $_GET; 202 | if ($http_method == "POST" && @strstr($request_headers["Content-Type"], "application/x-www-form-urlencoded") ) { 203 | $req_parameters = array_merge($req_parameters, $_POST); 204 | } 205 | 206 | // next check for the auth header, we need to do some extra stuff 207 | // if that is the case, namely suck in the parameters from GET or POST 208 | // so that we can include them in the signature 209 | if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") { 210 | $header_parameters = OAuthRequest::split_header($request_headers['Authorization']); 211 | $parameters = array_merge($req_parameters, $header_parameters); 212 | $req = new OAuthRequest($http_method, $http_url, $parameters); 213 | } else $req = new OAuthRequest($http_method, $http_url, $req_parameters); 214 | } 215 | 216 | return $req; 217 | }/*}}}*/ 218 | 219 | /** 220 | * pretty much a helper function to set up the request 221 | */ 222 | public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {/*{{{*/ 223 | @$parameters or $parameters = array(); 224 | $defaults = array("oauth_version" => OAuthRequest::$version, 225 | "oauth_nonce" => OAuthRequest::generate_nonce(), 226 | "oauth_timestamp" => OAuthRequest::generate_timestamp(), 227 | "oauth_consumer_key" => $consumer->key); 228 | $parameters = array_merge($defaults, $parameters); 229 | 230 | if ($token) { 231 | $parameters['oauth_token'] = $token->key; 232 | } 233 | return new OAuthRequest($http_method, $http_url, $parameters); 234 | }/*}}}*/ 235 | 236 | public function set_parameter($name, $value) {/*{{{*/ 237 | $this->parameters[$name] = $value; 238 | }/*}}}*/ 239 | 240 | public function get_parameter($name) {/*{{{*/ 241 | return isset($this->parameters[$name]) ? $this->parameters[$name] : null; 242 | }/*}}}*/ 243 | 244 | public function get_parameters() {/*{{{*/ 245 | return $this->parameters; 246 | }/*}}}*/ 247 | 248 | /** 249 | * Returns the normalized parameters of the request 250 | * 251 | * This will be all (except oauth_signature) parameters, 252 | * sorted first by key, and if duplicate keys, then by 253 | * value. 254 | * 255 | * The returned string will be all the key=value pairs 256 | * concated by &. 257 | * 258 | * @return string 259 | */ 260 | public function get_signable_parameters() {/*{{{*/ 261 | // Grab all parameters 262 | $params = $this->parameters; 263 | 264 | // Remove oauth_signature if present 265 | if (isset($params['oauth_signature'])) { 266 | unset($params['oauth_signature']); 267 | } 268 | 269 | // Urlencode both keys and values 270 | $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); 271 | $values = OAuthUtil::urlencode_rfc3986(array_values($params)); 272 | $params = array_combine($keys, $values); 273 | 274 | // Sort by keys (natsort) 275 | uksort($params, 'strcmp'); 276 | 277 | // Generate key=value pairs 278 | $pairs = array(); 279 | foreach ($params as $key=>$value ) { 280 | if (is_array($value)) { 281 | // If the value is an array, it's because there are multiple 282 | // with the same key, sort them, then add all the pairs 283 | natsort($value); 284 | foreach ($value as $v2) { 285 | $pairs[] = $key . '=' . $v2; 286 | } 287 | } else { 288 | $pairs[] = $key . '=' . $value; 289 | } 290 | } 291 | 292 | // Return the pairs, concated with & 293 | return implode('&', $pairs); 294 | }/*}}}*/ 295 | 296 | /** 297 | * Returns the base string of this request 298 | * 299 | * The base string defined as the method, the url 300 | * and the parameters (normalized), each urlencoded 301 | * and the concated with &. 302 | */ 303 | public function get_signature_base_string() {/*{{{*/ 304 | $parts = array( 305 | $this->get_normalized_http_method(), 306 | $this->get_normalized_http_url(), 307 | $this->get_signable_parameters() 308 | ); 309 | 310 | $parts = OAuthUtil::urlencode_rfc3986($parts); 311 | 312 | return implode('&', $parts); 313 | }/*}}}*/ 314 | 315 | /** 316 | * just uppercases the http method 317 | */ 318 | public function get_normalized_http_method() {/*{{{*/ 319 | return strtoupper($this->http_method); 320 | }/*}}}*/ 321 | 322 | /** 323 | * parses the url and rebuilds it to be 324 | * scheme://host/path 325 | */ 326 | public function get_normalized_http_url() {/*{{{*/ 327 | $parts = parse_url($this->http_url); 328 | 329 | $port = @$parts['port']; 330 | $scheme = $parts['scheme']; 331 | $host = $parts['host']; 332 | $path = @$parts['path']; 333 | 334 | $port or $port = ($scheme == 'https') ? '443' : '80'; 335 | 336 | if (($scheme == 'https' && $port != '443') 337 | || ($scheme == 'http' && $port != '80')) { 338 | $host = "$host:$port"; 339 | } 340 | return "$scheme://$host$path"; 341 | }/*}}}*/ 342 | 343 | /** 344 | * builds a url usable for a GET request 345 | */ 346 | public function to_url() {/*{{{*/ 347 | $out = $this->get_normalized_http_url() . "?"; 348 | $out .= $this->to_postdata(); 349 | return $out; 350 | }/*}}}*/ 351 | 352 | /** 353 | * builds the data one would send in a POST request 354 | * 355 | * TODO(morten.fangel): 356 | * this function might be easily replaced with http_build_query() 357 | * and corrections for rfc3986 compatibility.. but not sure 358 | */ 359 | public function to_postdata() {/*{{{*/ 360 | $total = array(); 361 | foreach ($this->parameters as $k => $v) { 362 | if (is_array($v)) { 363 | foreach ($v as $va) { 364 | $total[] = OAuthUtil::urlencode_rfc3986($k) . "[]=" . OAuthUtil::urlencode_rfc3986($va); 365 | } 366 | } else { 367 | $total[] = OAuthUtil::urlencode_rfc3986($k) . "=" . OAuthUtil::urlencode_rfc3986($v); 368 | } 369 | } 370 | $out = implode("&", $total); 371 | return $out; 372 | }/*}}}*/ 373 | 374 | /** 375 | * builds the Authorization: header 376 | */ 377 | public function to_header() {/*{{{*/ 378 | $out ='Authorization: OAuth realm=""'; 379 | $total = array(); 380 | foreach ($this->parameters as $k => $v) { 381 | if (substr($k, 0, 5) != "oauth") continue; 382 | if (is_array($v)) throw new OAuthException('Arrays not supported in headers'); 383 | $out .= ',' . OAuthUtil::urlencode_rfc3986($k) . '="' . OAuthUtil::urlencode_rfc3986($v) . '"'; 384 | } 385 | return $out; 386 | }/*}}}*/ 387 | 388 | public function __toString() {/*{{{*/ 389 | return $this->to_url(); 390 | }/*}}}*/ 391 | 392 | 393 | public function sign_request($signature_method, $consumer, $token) {/*{{{*/ 394 | $this->set_parameter("oauth_signature_method", $signature_method->get_name()); 395 | $signature = $this->build_signature($signature_method, $consumer, $token); 396 | $this->set_parameter("oauth_signature", $signature); 397 | }/*}}}*/ 398 | 399 | public function build_signature($signature_method, $consumer, $token) {/*{{{*/ 400 | $signature = $signature_method->build_signature($this, $consumer, $token); 401 | return $signature; 402 | }/*}}}*/ 403 | 404 | /** 405 | * util function: current timestamp 406 | */ 407 | private static function generate_timestamp() {/*{{{*/ 408 | return time(); 409 | }/*}}}*/ 410 | 411 | /** 412 | * util function: current nonce 413 | */ 414 | private static function generate_nonce() {/*{{{*/ 415 | $mt = microtime(); 416 | $rand = mt_rand(); 417 | 418 | return md5($mt . $rand); // md5s look nicer than numbers 419 | }/*}}}*/ 420 | 421 | /** 422 | * util function for turning the Authorization: header into 423 | * parameters, has to do some unescaping 424 | */ 425 | private static function split_header($header) {/*{{{*/ 426 | $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/'; 427 | $offset = 0; 428 | $params = array(); 429 | while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) { 430 | $match = $matches[0]; 431 | $header_name = $matches[2][0]; 432 | $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0]; 433 | $params[$header_name] = OAuthUtil::urldecode_rfc3986( $header_content ); 434 | $offset = $match[1] + strlen($match[0]); 435 | } 436 | 437 | if (isset($params['realm'])) { 438 | unset($params['realm']); 439 | } 440 | 441 | return $params; 442 | }/*}}}*/ 443 | 444 | /** 445 | * helper to try to sort out headers for people who aren't running apache 446 | */ 447 | private static function get_headers() {/*{{{*/ 448 | if (function_exists('apache_request_headers')) { 449 | // we need this to get the actual Authorization: header 450 | // because apache tends to tell us it doesn't exist 451 | return apache_request_headers(); 452 | } 453 | // otherwise we don't have apache and are just going to have to hope 454 | // that $_SERVER actually contains what we need 455 | $out = array(); 456 | foreach ($_SERVER as $key => $value) { 457 | if (substr($key, 0, 5) == "HTTP_") { 458 | // this is chaos, basically it is just there to capitalize the first 459 | // letter of every word that is not an initial HTTP and strip HTTP 460 | // code from przemek 461 | $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5))))); 462 | $out[$key] = $value; 463 | } 464 | } 465 | return $out; 466 | }/*}}}*/ 467 | }/*}}}*/ 468 | 469 | class OAuthServer {/*{{{*/ 470 | protected $timestamp_threshold = 300; // in seconds, five minutes 471 | protected $version = 1.0; // hi blaine 472 | protected $signature_methods = array(); 473 | 474 | protected $data_store; 475 | 476 | function __construct($data_store) {/*{{{*/ 477 | $this->data_store = $data_store; 478 | }/*}}}*/ 479 | 480 | public function add_signature_method($signature_method) {/*{{{*/ 481 | $this->signature_methods[$signature_method->get_name()] = 482 | $signature_method; 483 | }/*}}}*/ 484 | 485 | // high level functions 486 | 487 | /** 488 | * process a request_token request 489 | * returns the request token on success 490 | */ 491 | public function fetch_request_token(&$request) {/*{{{*/ 492 | $this->get_version($request); 493 | 494 | $consumer = $this->get_consumer($request); 495 | 496 | // no token required for the initial token request 497 | $token = NULL; 498 | 499 | $this->check_signature($request, $consumer, $token); 500 | 501 | $new_token = $this->data_store->new_request_token($consumer); 502 | 503 | return $new_token; 504 | }/*}}}*/ 505 | 506 | /** 507 | * process an access_token request 508 | * returns the access token on success 509 | */ 510 | public function fetch_access_token(&$request) {/*{{{*/ 511 | $this->get_version($request); 512 | 513 | $consumer = $this->get_consumer($request); 514 | 515 | // requires authorized request token 516 | $token = $this->get_token($request, $consumer, "request"); 517 | 518 | 519 | $this->check_signature($request, $consumer, $token); 520 | 521 | $new_token = $this->data_store->new_access_token($token, $consumer); 522 | 523 | return $new_token; 524 | }/*}}}*/ 525 | 526 | /** 527 | * verify an api call, checks all the parameters 528 | */ 529 | public function verify_request(&$request) {/*{{{*/ 530 | $this->get_version($request); 531 | $consumer = $this->get_consumer($request); 532 | $token = $this->get_token($request, $consumer, "access"); 533 | $this->check_signature($request, $consumer, $token); 534 | return array($consumer, $token); 535 | }/*}}}*/ 536 | 537 | // Internals from here 538 | /** 539 | * version 1 540 | */ 541 | private function get_version(&$request) {/*{{{*/ 542 | $version = $request->get_parameter("oauth_version"); 543 | if (!$version) { 544 | $version = 1.0; 545 | } 546 | if ($version && $version != $this->version) { 547 | throw new OAuthException("OAuth version '$version' not supported"); 548 | } 549 | return $version; 550 | }/*}}}*/ 551 | 552 | /** 553 | * figure out the signature with some defaults 554 | */ 555 | private function get_signature_method(&$request) {/*{{{*/ 556 | $signature_method = 557 | @$request->get_parameter("oauth_signature_method"); 558 | if (!$signature_method) { 559 | $signature_method = "PLAINTEXT"; 560 | } 561 | if (!in_array($signature_method, 562 | array_keys($this->signature_methods))) { 563 | throw new OAuthException( 564 | "Signature method '$signature_method' not supported try one of the following: " . implode(", ", array_keys($this->signature_methods)) 565 | ); 566 | } 567 | return $this->signature_methods[$signature_method]; 568 | }/*}}}*/ 569 | 570 | /** 571 | * try to find the consumer for the provided request's consumer key 572 | */ 573 | private function get_consumer(&$request) {/*{{{*/ 574 | $consumer_key = @$request->get_parameter("oauth_consumer_key"); 575 | if (!$consumer_key) { 576 | throw new OAuthException("Invalid consumer key"); 577 | } 578 | 579 | $consumer = $this->data_store->lookup_consumer($consumer_key); 580 | if (!$consumer) { 581 | throw new OAuthException("Invalid consumer"); 582 | } 583 | 584 | return $consumer; 585 | }/*}}}*/ 586 | 587 | /** 588 | * try to find the token for the provided request's token key 589 | */ 590 | private function get_token(&$request, $consumer, $token_type="access") {/*{{{*/ 591 | $token_field = @$request->get_parameter('oauth_token'); 592 | $token = $this->data_store->lookup_token( 593 | $consumer, $token_type, $token_field 594 | ); 595 | if (!$token) { 596 | throw new OAuthException("Invalid $token_type token: $token_field"); 597 | } 598 | return $token; 599 | }/*}}}*/ 600 | 601 | /** 602 | * all-in-one function to check the signature on a request 603 | * should guess the signature method appropriately 604 | */ 605 | private function check_signature(&$request, $consumer, $token) {/*{{{*/ 606 | // this should probably be in a different method 607 | $timestamp = @$request->get_parameter('oauth_timestamp'); 608 | $nonce = @$request->get_parameter('oauth_nonce'); 609 | 610 | $this->check_timestamp($timestamp); 611 | $this->check_nonce($consumer, $token, $nonce, $timestamp); 612 | 613 | $signature_method = $this->get_signature_method($request); 614 | 615 | $signature = $request->get_parameter('oauth_signature'); 616 | $valid_sig = $signature_method->check_signature( 617 | $request, 618 | $consumer, 619 | $token, 620 | $signature 621 | ); 622 | 623 | if (!$valid_sig) { 624 | throw new OAuthException("Invalid signature"); 625 | } 626 | }/*}}}*/ 627 | 628 | /** 629 | * check that the timestamp is new enough 630 | */ 631 | private function check_timestamp($timestamp) {/*{{{*/ 632 | // verify that timestamp is recentish 633 | $now = time(); 634 | if ($now - $timestamp > $this->timestamp_threshold) { 635 | throw new OAuthException("Expired timestamp, yours $timestamp, ours $now"); 636 | } 637 | }/*}}}*/ 638 | 639 | /** 640 | * check that the nonce is not repeated 641 | */ 642 | private function check_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/ 643 | // verify that the nonce is uniqueish 644 | $found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp); 645 | if ($found) { 646 | throw new OAuthException("Nonce already used: $nonce"); 647 | } 648 | }/*}}}*/ 649 | 650 | 651 | 652 | }/*}}}*/ 653 | 654 | class OAuthDataStore {/*{{{*/ 655 | function lookup_consumer($consumer_key) {/*{{{*/ 656 | // implement me 657 | }/*}}}*/ 658 | 659 | function lookup_token($consumer, $token_type, $token) {/*{{{*/ 660 | // implement me 661 | }/*}}}*/ 662 | 663 | function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/ 664 | // implement me 665 | }/*}}}*/ 666 | 667 | function new_request_token($consumer) {/*{{{*/ 668 | // return a new token attached to this consumer 669 | }/*}}}*/ 670 | 671 | function new_access_token($token, $consumer) {/*{{{*/ 672 | // return a new access token attached to this consumer 673 | // for the user associated with this token if the request token 674 | // is authorized 675 | // should also invalidate the request token 676 | }/*}}}*/ 677 | 678 | }/*}}}*/ 679 | 680 | 681 | /* A very naive dbm-based oauth storage 682 | */ 683 | class SimpleOAuthDataStore extends OAuthDataStore {/*{{{*/ 684 | private $dbh; 685 | 686 | function __construct($path = "oauth.gdbm") {/*{{{*/ 687 | $this->dbh = dba_popen($path, 'c', 'gdbm'); 688 | }/*}}}*/ 689 | 690 | function __destruct() {/*{{{*/ 691 | dba_close($this->dbh); 692 | }/*}}}*/ 693 | 694 | function lookup_consumer($consumer_key) {/*{{{*/ 695 | $rv = dba_fetch("consumer_$consumer_key", $this->dbh); 696 | if ($rv === FALSE) { 697 | return NULL; 698 | } 699 | $obj = unserialize($rv); 700 | if (!($obj instanceof OAuthConsumer)) { 701 | return NULL; 702 | } 703 | return $obj; 704 | }/*}}}*/ 705 | 706 | function lookup_token($consumer, $token_type, $token) {/*{{{*/ 707 | $rv = dba_fetch("${token_type}_${token}", $this->dbh); 708 | if ($rv === FALSE) { 709 | return NULL; 710 | } 711 | $obj = unserialize($rv); 712 | if (!($obj instanceof OAuthToken)) { 713 | return NULL; 714 | } 715 | return $obj; 716 | }/*}}}*/ 717 | 718 | function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/ 719 | if (dba_exists("nonce_$nonce", $this->dbh)) { 720 | return TRUE; 721 | } else { 722 | dba_insert("nonce_$nonce", "1", $this->dbh); 723 | return FALSE; 724 | } 725 | }/*}}}*/ 726 | 727 | function new_token($consumer, $type="request") {/*{{{*/ 728 | $key = md5(time()); 729 | $secret = time() + time(); 730 | $token = new OAuthToken($key, md5(md5($secret))); 731 | if (!dba_insert("${type}_$key", serialize($token), $this->dbh)) { 732 | throw new OAuthException("doooom!"); 733 | } 734 | return $token; 735 | }/*}}}*/ 736 | 737 | function new_request_token($consumer) {/*{{{*/ 738 | return $this->new_token($consumer, "request"); 739 | }/*}}}*/ 740 | 741 | function new_access_token($token, $consumer) {/*{{{*/ 742 | 743 | $token = $this->new_token($consumer, 'access'); 744 | dba_delete("request_" . $token->key, $this->dbh); 745 | return $token; 746 | }/*}}}*/ 747 | }/*}}}*/ 748 | 749 | class OAuthUtil {/*{{{*/ 750 | public static function urlencode_rfc3986($input) {/*{{{*/ 751 | if (is_array($input)) { 752 | return array_map(array('OAuthUtil','urlencode_rfc3986'), $input); 753 | } else if (is_scalar($input)) { 754 | return str_replace('+', ' ', 755 | str_replace('%7E', '~', rawurlencode($input))); 756 | } else { 757 | return ''; 758 | } 759 | }/*}}}*/ 760 | 761 | 762 | // This decode function isn't taking into consideration the above 763 | // modifications to the encoding process. However, this method doesn't 764 | // seem to be used anywhere so leaving it as is. 765 | public static function urldecode_rfc3986($string) {/*{{{*/ 766 | return rawurldecode($string); 767 | }/*}}}*/ 768 | }/*}}}*/ --------------------------------------------------------------------------------