├── lib ├── config.php ├── familymap.php ├── fireeagle.php └── OAuth.php ├── update.php └── setup.php /lib/config.php: -------------------------------------------------------------------------------- 1 | locate($fm_phone, $fm_password); 8 | if(is_array($coords)) 9 | { 10 | $fe = new FireEagle($fe_key, $fe_secret, $fe_access_token, $fe_access_secret); 11 | $fe->update(array('q' => $coords['lat'] . ', ' . $coords['lng'])); 12 | echo "Yay!"; 13 | } 14 | else 15 | { 16 | echo "Fail."; 17 | } 18 | -------------------------------------------------------------------------------- /lib/familymap.php: -------------------------------------------------------------------------------- 1 | curl(FamilyMap::URL_LOGIN, FamilyMap::URL_LOGIN, "mdn=$phone&password=$password"); 21 | 22 | // Grab locate link 23 | $link = $this->match('/(main\.htm\?ri=[0-9]+)/ms', $html, 1); 24 | 25 | // Load location page 26 | $max_attempts = 10; 27 | do 28 | { 29 | if(isset($img)) sleep(2); 30 | $html = $this->curl(FamilyMap::URL_BASE . $link, FamilyMap::URL_MAIN); 31 | $img = $this->match('/zoomed\.png\?a=(.*?)(\'|")/ms', $html, 1); 32 | } 33 | while($img === false && --$max_attempts > 0); 34 | 35 | if($img === false) 36 | return false; 37 | 38 | // https://familymap.att.com/finder-wap-att/map/zoomed.png?a=-86.7844444,36.1658333,1544,0.0,0.0,0,,-86.7844444,36.1658333,12.352,0,0 39 | // 0 = user lng 40 | // 1 = user lat 41 | // 7 = map lng 42 | // 8 = map lat 43 | $info = explode(',', $img); 44 | 45 | return array('lat' => $info[1], 'lng' => $info[0]); 46 | } 47 | 48 | private function curl($url, $referer = null, $post = null, $return_header = false) 49 | { 50 | static $tmpfile; 51 | 52 | if(!isset($tmpfile) || ($tmpfile == '')) $tmpfile = tempnam('/tmp', 'FOO'); 53 | 54 | $ch = curl_init($url); 55 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 56 | curl_setopt($ch, CURLOPT_COOKIEFILE, $tmpfile); 57 | curl_setopt($ch, CURLOPT_COOKIEJAR, $tmpfile); 58 | // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 59 | curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_2_1 like Mac OS X; en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5H11 Safari/525.20"); 60 | if($referer) curl_setopt($ch, CURLOPT_REFERER, $referer); 61 | 62 | if(!is_null($post)) 63 | { 64 | curl_setopt($ch, CURLOPT_POST, true); 65 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post); 66 | } 67 | 68 | if($return_header) 69 | { 70 | curl_setopt($ch, CURLOPT_HEADER, 1); 71 | $html = curl_exec($ch); 72 | $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); 73 | $this->lastURL = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); 74 | return substr($html, 0, $header_size); 75 | } 76 | else 77 | { 78 | $html = curl_exec($ch); 79 | $this->lastURL = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); 80 | return $html; 81 | } 82 | } 83 | 84 | private function match($regex, $str, $i = 0) 85 | { 86 | return preg_match($regex, $str, $match) == 1 ? $match[$i] : false; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /setup.php: -------------------------------------------------------------------------------- 1 | getRequestToken(); 19 | if (!isset($tok['oauth_token']) 20 | || !is_string($tok['oauth_token']) 21 | || !isset($tok['oauth_token_secret']) 22 | || !is_string($tok['oauth_token_secret'])) { 23 | echo "ERROR! FireEagle::getRequestToken() returned an invalid response. Giving up."; 24 | exit; 25 | } 26 | $_SESSION['auth_state'] = "start"; 27 | $_SESSION['request_token'] = $token = $tok['oauth_token']; 28 | $_SESSION['request_secret'] = $tok['oauth_token_secret']; 29 | header("Location: ".$fe->getAuthorizeURL($token)); 30 | // END step 1 31 | } else if (@$_GET['f'] == 'callback') { 32 | // the user has authorized us at FE, so now we can pick up our access token + secret 33 | // START step 2 34 | if (@$_SESSION['auth_state'] != "start") { 35 | echo "Out of sequence."; 36 | exit; 37 | } 38 | if ($_GET['oauth_token'] != $_SESSION['request_token']) { 39 | echo "Token mismatch."; 40 | exit; 41 | } 42 | 43 | $fe = new FireEagle($fe_key, $fe_secret, $_SESSION['request_token'], $_SESSION['request_secret']); 44 | $tok = $fe->getAccessToken(); 45 | if (!isset($tok['oauth_token']) || !is_string($tok['oauth_token']) 46 | || !isset($tok['oauth_token_secret']) || !is_string($tok['oauth_token_secret'])) { 47 | error_log("Bad token from FireEagle::getAccessToken(): ".var_export($tok, TRUE)); 48 | echo "ERROR! FireEagle::getAccessToken() returned an invalid response. Giving up."; 49 | exit; 50 | } 51 | 52 | $_SESSION['access_token'] = $tok['oauth_token']; 53 | $_SESSION['access_secret'] = $tok['oauth_token_secret']; 54 | $_SESSION['auth_state'] = "done"; 55 | header("Location: ".$_SERVER['SCRIPT_NAME']); 56 | // END step 2 57 | } else if (@$_SESSION['auth_state'] == 'done') { 58 | // we have our access token + secret, so now we can actually *use* the api 59 | // START step 3 60 | $fe = new FireEagle($fe_key, $fe_secret, $_SESSION['access_token'], $_SESSION['access_secret']); 61 | 62 | // handle postback for location update 63 | if ($_SERVER['REQUEST_METHOD'] == "POST") { 64 | // we're updating the user's location. 65 | $where = array(); 66 | foreach (array("lat", "lon", "q", "place_id") as $k) { 67 | if (!empty($_POST[$k])) $where[$k] = $_POST[$k]; 68 | } 69 | switch (@$_POST['submit']) { 70 | case 'Move!': 71 | $r = $fe->update($where); // equivalent to $fe->call("update", $where) 72 | header("Location: ".$_SERVER['SCRIPT_NAME']); 73 | exit; 74 | case 'Lookup': 75 | echo "

Lookup results:

".nl2br(htmlspecialchars(var_export($fe->lookup($where), TRUE)))."
"; 76 | break; 77 | } 78 | } 79 | 80 | ?>

You are authenticated with Fire Eagle! (Change settings.)

Here are the settings you need to now paste into config.php

access token:
access secret:

To setup, go to the Fire Eagle developer's area and create a new "Auth for web-base services" application. Use this URL as the callback URL. Once your application has been created, paste the application keys into the config.php file.

Once that is completed, click here to authenticate with Fire Eagle!

-------------------------------------------------------------------------------- /lib/fireeagle.php: -------------------------------------------------------------------------------- 1 | $blocksize) 42 | $key = pack('H*', $hashfunc($key)); 43 | $key = str_pad($key,$blocksize,chr(0x00)); 44 | $ipad = str_repeat(chr(0x36),$blocksize); 45 | $opad = str_repeat(chr(0x5c),$blocksize); 46 | $hmac = pack( 47 | 'H*',$hashfunc( 48 | ($key^$opad).pack( 49 | 'H*',$hashfunc( 50 | ($key^$ipad).$data 51 | ) 52 | ) 53 | ) 54 | ); 55 | return $hmac; 56 | } 57 | } 58 | 59 | // Various things that can go wrong 60 | class FireEagleException extends Exception { 61 | const TOKEN_REQUIRED = 1; // call missing an oauth request/access token 62 | const LOCATION_REQUIRED = 2; // call to update() without a location 63 | const REMOTE_ERROR = 3; // FE sent an error 64 | const REQUEST_FAILED = 4; // empty or malformed response from FE 65 | const CONNECT_FAILED = 5; // totally failed to make an HTTP request 66 | const INTERNAL_ERROR = 6; // totally failed to make an HTTP request 67 | const CONFIG_READ_ERROR = 7; // can't find or parse fireeaglerc 68 | 69 | const REMOTE_SUCCESS = 0; // Request succeeded. 70 | const REMOTE_UPDATE_PROHIBITED = 1; // Update not permitted for that user. 71 | const REMOTE_UPDATE_ONLY = 2; // Update successful, but read access prohibited. 72 | const REMOTE_QUERY_PROHIBITED = 3; // Query not permitted for that user. 73 | const REMOTE_SUSPENDED = 4; // User account is suspended. 74 | const REMOTE_PLACE_NOT_FOUND = 6; // Place can't be identified. 75 | const REMOTE_USER_NOT_FOUND = 7; // Authentication token can't be matched to a user. 76 | const REMOTE_INVALID_QUERY = 8; // Invalid location query. 77 | const REMOTE_IS_FROB = 10; // Token provided is a request token, not an auth token. 78 | const REMOTE_NOT_VALIDATED = 11; // Request token has not been validated. 79 | const REMOTE_REQUEST_TOKEN_REQUIRED = 12; // Token provided must be an access token. 80 | const REMOTE_EXPIRED = 13; // Token has expired. 81 | const REMOTE_GENERAL_TOKEN_REQUIRED = 14; // Token provided must be an general purpose token. 82 | const REMOTE_UNKNOWN_CONSUMER = 15; // Unknown consumer key. 83 | const REMOTE_UNKNOWN_TOKEN = 16; // Token not found. 84 | const REMOTE_BAD_IP_ADDRESS = 17; // Request made from non-blessed ip address. 85 | const REMOTE_OAUTH_CONSUMER_KEY_REQUIRED = 20; // oauth_consumer_key parameter required. 86 | const REMOTE_OAUTH_TOKEN_REQUIRED = 21; // oauth_token parameter required. 87 | const REMOTE_BAD_SIGNATURE_METHOD = 22; // Unsupported signature method. 88 | const REMOTE_INVALID_SIGNATURE = 23; // Invalid OAuth signature. 89 | const REMOTE_REPEATED_NONCE = 24; // Provided nonce has been seen before. 90 | const REMOTE_YAHOOAPIS_REQUIRED = 30; // All api methods should use fireeagle.yahooapis.com. 91 | const REMOTE_SSL_REQUIRED = 31; // SSL / https is required. 92 | const REMOTE_RATE_LIMITING = 32; // Rate limit/IP Block due to excessive requests. 93 | const REMOTE_INTERNAL_ERROR = 50; // Internal error occurred; try again later. 94 | 95 | public $response; // for REMOTE_ERROR codes, this is the response from FireEagle (useful: $response->code and $response->message) 96 | 97 | function __construct($msg, $code, $response=null) { 98 | parent::__construct($msg, $code); 99 | $this->response = $response; 100 | } 101 | } 102 | 103 | /** 104 | * FireEagle API access helper class. 105 | */ 106 | class FireEagle { 107 | 108 | public static $FE_ROOT = "http://fireeagle.yahoo.net"; 109 | public static $FE_API_ROOT = "https://fireeagle.yahooapis.com"; 110 | 111 | public static $FE_DEBUG = false; // set to true to print out debugging info 112 | public static $FE_DUMP_REQUESTS = false; // set to a pathname to dump out http requests to a log 113 | 114 | // OAuth URLs 115 | function requestTokenURL() { return self::$FE_API_ROOT.'/oauth/request_token'; } 116 | function authorizeURL() { return self::$FE_ROOT.'/oauth/authorize'; } 117 | function accessTokenURL() { return self::$FE_API_ROOT.'/oauth/access_token'; } 118 | // API URLs 119 | function methodURL($method) { return self::$FE_API_ROOT.'/api/0.1/'.$method.'.json'; } 120 | 121 | function __construct($consumerKey, 122 | $consumerSecret, 123 | $oAuthToken = null, 124 | $oAuthTokenSecret = null) { 125 | $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); 126 | $this->consumer = new OAuthConsumer($consumerKey, $consumerSecret, NULL); 127 | if (!empty($oAuthToken) && !empty($oAuthTokenSecret)) { 128 | $this->token = new OAuthConsumer($oAuthToken, $oAuthTokenSecret); 129 | } else { 130 | $this->token = NULL; 131 | } 132 | } 133 | 134 | // read consumer key and secret, and optionally fireeagle auth and api urls, from a .fireeaglerc file 135 | public static function from_fireeaglerc($fn, $token=null, $secret=null) { 136 | $text = @file_get_contents($fn); 137 | if ($text === false) throw new FireEagleException("Could not read $fn", FireEagleException::CONFIG_READ_ERROR); 138 | $info = array(); 139 | foreach (preg_split("/\n/", $text) as $line) { 140 | $line = trim(preg_replace("/#.*/", "", $line)); 141 | if (empty($line)) continue; 142 | if (!preg_match("/^([^\s=]+)\s*\=\s*(.*)$/", $line, $m)) throw new FireEagleException("Failed to parse line '$line' in $fn", FireEagleException::CONFIG_READ_ERROR); 143 | list(, $k, $v) = $m; 144 | $info[$k] = $v; 145 | } 146 | 147 | if (empty($info['consumer_key'])) throw new FireEagleException("Missing consumer_key in $fn", FireEagleException::CONFIG_READ_ERROR); 148 | if (empty($info['consumer_secret'])) throw new FireEagleException("Missing consumer_secret in $fn", FireEagleException::CONFIG_READ_ERROR); 149 | 150 | if (isset($info['api_server'])) self::$FE_API_ROOT = self::build_server_url($info, 'api'); 151 | if (isset($info['auth_server'])) self::$FE_ROOT = self::build_server_url($info, 'auth'); 152 | 153 | return new FireEagle($info['consumer_key'], $info['consumer_secret'], $token, $secret); 154 | } 155 | 156 | private static function build_server_url($info, $role) { 157 | $proto = isset($info["${role}_protocol"]) ? $info["${role}_protocol"] : 'https'; 158 | $default_port = ($proto == 'https' ? 443 : 80); 159 | $port = isset($info["${role}_port"]) ? $info["${role}_port"] : $default_port; 160 | $url = $proto . "://" . $info["${role}_server"]; 161 | if ($port != $default_port) $url .= ":" . $port; 162 | return $url; 163 | } 164 | 165 | /** 166 | * Get a request token for authenticating your application with FE. 167 | * 168 | * @returns a key/value pair array containing: oauth_token and 169 | * oauth_token_secret. 170 | */ 171 | public function getRequestToken() { 172 | $r = $this->oAuthRequest($this->requestTokenURL()); 173 | $token = $this->oAuthParseResponse($r); 174 | $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); // use this token from now on 175 | if (self::$FE_DUMP_REQUESTS) self::dump("Now the user is redirected to ".$this->getAuthorizeURL($token['oauth_token'])."\nOnce the user returns, via the callback URL for web authentication or manually for desktop authentication, we can get their access token and secret by calling /oauth/access_token.\n\n"); 176 | return $token; 177 | } 178 | public function request_token() { return $this->getRequestToken(); } 179 | 180 | /** 181 | * Get the URL to redirect to to authorize the user and validate a 182 | * request token. 183 | * 184 | * @returns a string containing the URL to redirect to. 185 | */ 186 | public function getAuthorizeURL($token) { 187 | // $token can be a string, or an array in the format returned by getRequestToken(). 188 | if (is_array($token)) $token = $token['oauth_token']; 189 | return $this->authorizeURL() . '?oauth_token=' . $token; 190 | } 191 | public function authorize($token) { return $this->getAuthorizeURL($token); } 192 | 193 | /** 194 | * Exchange the request token and secret for an access token and 195 | * secret, to sign API calls. 196 | * 197 | * 198 | * @returns array("oauth_token" => the access token, 199 | * "oauth_token_secret" => the access secret) 200 | */ 201 | public function getAccessToken($token=NULL) { 202 | $this->requireToken(); 203 | $r = $this->oAuthRequest($this->accessTokenURL()); 204 | $token = $this->oAuthParseResponse($r); 205 | $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); // use this token from now on 206 | return $token; 207 | } 208 | public function access_token() { return $this->getAccessToken(); } 209 | 210 | /** 211 | * Generic method call function. You can use this to get the raw 212 | * output from an API method, or to call future API methods. 213 | * 214 | * e.g. 215 | * Get a user's location: $fe->call("user") 216 | * or $fe->user() 217 | * Set a user's location: $fe->call("update", array("q" => "new york, new york")) 218 | * or $fe->update(array("q" => "new york, new york")) 219 | */ 220 | 221 | public function call($method, $params=array(), $request_method=NULL) { 222 | $this->requireToken(); 223 | $r = $this->oAuthRequest($this->methodURL($method), $params, $request_method); 224 | return $this->parseJSON($r); 225 | } 226 | 227 | // --- Wrappers for individual methods --- 228 | 229 | /** 230 | * Wrapper for 'user' API method, which fetches the current location 231 | * for a user. 232 | */ 233 | public function user() { 234 | $r = $this->call("user"); 235 | // add latitudes and longitudes, and extract best guess 236 | if (isset($r->user->location_hierarchy)) { 237 | $r->user->best_guess = NULL; 238 | foreach ($r->user->location_hierarchy as &$loc) { 239 | $c = $loc->geometry->coordinates; 240 | switch ($loc->geometry->type) { 241 | case 'Box': // DEPRECATED 242 | $loc->bbox = $c; 243 | $loc->longitude = ($c[0][0] + $c[1][0]) / 2; 244 | $loc->latitude = ($c[0][1] + $c[1][1]) / 2; 245 | $loc->geotype = 'box'; 246 | break; 247 | case 'Polygon': 248 | $loc->bbox = $bbox = $loc->geometry->bbox; 249 | $loc->longitude = ($bbox[0][0] + $bbox[1][0]) / 2; 250 | $loc->latitude = ($bbox[0][1] + $bbox[1][1]) / 2; 251 | $loc->geotype = 'box'; 252 | break; 253 | case 'Point': 254 | list($loc->longitude, $loc->latitude) = $c; 255 | $loc->geotype = 'point'; 256 | break; 257 | } 258 | if ($loc->best_guess) $r->user->best_guess = $loc; // add shortcut to get 'best guess' loc 259 | unset($loc); 260 | } 261 | } 262 | 263 | return $r; 264 | } 265 | 266 | /** 267 | * Wrapper for 'update' API method, to set a user's location. 268 | */ 269 | public function update($args=array()) { 270 | if (empty($args)) throw new FireEagleException("FireEagle::update() needs a location", FireEagleException::LOCATION_REQUIRED); 271 | return $this->call("update", $args); 272 | } 273 | 274 | /** 275 | * Wrapper for 'lookup' API method, to run a location query without 276 | * setting the user's location (so an application can show a list of 277 | * possibilities that match a user-supplied query -- not to be used 278 | * as a generic geocoder). 279 | */ 280 | public function lookup($args=array()) { 281 | if (!is_array($args)) throw new FireEagleException("\$args parameter to FireEagle::lookup() should be an array", FireEagleException::LOCATION_REQUIRED); 282 | if (empty($args)) throw new FireEagleException("FireEagle::lookup() needs a location", FireEagleException::LOCATION_REQUIRED); 283 | return $this->call("lookup", $args, "GET"); 284 | } 285 | 286 | /** 287 | * Wrapper for 'recent' API method 288 | */ 289 | public function recent($since=NULL, $per_page=NULL, $page=NULL) { 290 | $params = array( 291 | "per_page" => ($per_page === NULL) ? 10 : $per_page, 292 | "page" => ($page === NULL) ? 1 : $page, 293 | ); 294 | if (!empty($since)) $params['time'] = $since; 295 | 296 | return $this->call("recent", $params, "GET"); 297 | } 298 | 299 | /** 300 | * Wrapper for 'within' API method 301 | */ 302 | public function within($params=array()) { 303 | return $this->call("within", $params, "GET"); 304 | } 305 | 306 | // --- Internal bits and pieces --- 307 | 308 | protected function parseJSON($json) { 309 | $r = json_decode($json); 310 | if (empty($r)) throw new FireEagleException("Empty JSON response", FireEagleException::REQUEST_FAILED); 311 | if (isset($r->rsp) && $r->rsp->stat != 'ok') { 312 | throw new FireEagleException($r->rsp->code.": ".$r->rsp->message, FireEagleException::REMOTE_ERROR, $r->rsp); 313 | } 314 | return $r; 315 | } 316 | 317 | protected function requireToken() { 318 | if (!isset($this->token)) { 319 | throw new FireEagleException("This function requires an OAuth token", FireEagleException::TOKEN_REQUIRED); 320 | } 321 | } 322 | 323 | // Parse a URL-encoded OAuth response 324 | protected function oAuthParseResponse($responseString) { 325 | $r = array(); 326 | foreach (explode('&', $responseString) as $param) { 327 | $pair = explode('=', $param, 2); 328 | if (count($pair) != 2) continue; 329 | $r[urldecode($pair[0])] = urldecode($pair[1]); 330 | } 331 | return $r; 332 | } 333 | 334 | // Format and sign an OAuth / API request 335 | function oAuthRequest($url, $args=array(), $method=NULL) { 336 | if (empty($method)) $method = empty($args) ? "GET" : "POST"; 337 | $req = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $args); 338 | $req->sign_request($this->sha1_method, $this->consumer, $this->token); 339 | if (self::$FE_DEBUG) { 340 | echo "
[OAuth request:
".nl2br(htmlspecialchars(var_export($req, TRUE)))."
base string: ".htmlspecialchars($req->base_string)."
]
"; 341 | } 342 | if (self::$FE_DUMP_REQUESTS) { 343 | $k = $this->consumer->secret . "&"; 344 | if ($this->token) $k .= $this->token->secret; 345 | self::dump("---\n\nOAUTH REQUEST TO $url"); 346 | if (!empty($args)) self::dump(" WITH PARAMS ".json_encode($args)); 347 | self::dump("\n\nBase string: ".$req->base_string."\nSignature string: $k\n"); 348 | } 349 | switch ($method) { 350 | case 'GET': return $this->http($req->to_url()); 351 | case 'POST': return $this->http($req->get_normalized_http_url(), $req->to_postdata()); 352 | } 353 | } 354 | 355 | // Make an HTTP request, throwing an exception if we get anything other than a 200 response 356 | public function http($url, $postData=null) { 357 | if (self::$FE_DEBUG) { 358 | echo "[FE HTTP request: url: ".htmlspecialchars($url).", post data: ".htmlspecialchars(var_export($postData, TRUE))."]"; 359 | } 360 | if (self::$FE_DUMP_REQUESTS) { 361 | self::dump("Final URL: $url\n\n"); 362 | $url_bits = parse_url($url); 363 | if (isset($postData)) { 364 | self::dump("POST ".$url_bits['path']." HTTP/1.0\nHost: ".$url_bits['host']."\nContent-Type: application/x-www-urlencoded\nContent-Length: ".strlen($postData)."\n\n$postData\n"); 365 | } else { 366 | $get_url = $url_bits['path']; 367 | if ($url_bits['query']) $get_url .= '?' . $url_bits['query']; 368 | self::dump("GET $get_url HTTP/1.0\nHost: ".$url_bits['host']."\n\n"); 369 | } 370 | } 371 | $ch = curl_init(); 372 | if (defined("CURL_CA_BUNDLE_PATH")) curl_setopt($ch, CURLOPT_CAINFO, CURL_CA_BUNDLE_PATH); 373 | curl_setopt($ch, CURLOPT_URL, $url); 374 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); 375 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); 376 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 377 | if (isset($postData)) { 378 | curl_setopt($ch, CURLOPT_POST, 1); 379 | curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); 380 | } 381 | $response = curl_exec($ch); 382 | $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); 383 | $ct = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); 384 | if ($ct) $ct = preg_replace("/;.*/", "", $ct); // strip off charset 385 | if (!$status) throw new FireEagleException("Connection to $url failed", FireEagleException::CONNECT_FAILED); 386 | if ($status != 200) { 387 | if ($ct == "application/json") { 388 | $r = json_decode($response); 389 | if ($r && isset($r->rsp) && $r->rsp->stat != 'ok') { 390 | throw new FireEagleException($r->rsp->code.": ".$r->rsp->message, FireEagleException::REMOTE_ERROR, $r->rsp); 391 | } 392 | } 393 | throw new FireEagleException("Request to $url failed: HTTP error $status ($response)", FireEagleException::REQUEST_FAILED); 394 | } 395 | if (self::$FE_DUMP_REQUESTS) { 396 | self::dump("HTTP/1.0 $status OK\n"); 397 | if ($ct) self::dump("Content-Type: $ct\n"); 398 | $cl = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD); 399 | if ($cl) self::dump("Content-Length: $cl\n"); 400 | self::dump("\n$response\n\n"); 401 | } 402 | curl_close ($ch); 403 | 404 | if (self::$FE_DEBUG) { 405 | echo "[HTTP response: ".nl2br(htmlspecialchars($response))."]"; 406 | } 407 | 408 | return $response; 409 | } 410 | 411 | private function dump($text) { 412 | if (!self::$FE_DUMP_REQUESTS) throw new Exception('FireEagle::$FE_DUMP_REQUESTS must be set to enable request trace dumping'); 413 | file_put_contents(self::$FE_DUMP_REQUESTS, $text, FILE_APPEND); 414 | } 415 | 416 | } 417 | 418 | -------------------------------------------------------------------------------- /lib/OAuth.php: -------------------------------------------------------------------------------- 1 | key = $key; 16 | $this->secret = $secret; 17 | $this->callback_url = $callback_url; 18 | }/*}}}*/ 19 | }/*}}}*/ 20 | 21 | class OAuthToken {/*{{{*/ 22 | // access tokens and request tokens 23 | public $key; 24 | public $secret; 25 | 26 | /** 27 | * key = the token 28 | * secret = the token secret 29 | */ 30 | function __construct($key, $secret) {/*{{{*/ 31 | $this->key = $key; 32 | $this->secret = $secret; 33 | }/*}}}*/ 34 | 35 | /** 36 | * generates the basic string serialization of a token that a server 37 | * would respond to request_token and access_token calls with 38 | */ 39 | function to_string() {/*{{{*/ 40 | return "oauth_token=" . OAuthUtil::urlencodeRFC3986($this->key) . 41 | "&oauth_token_secret=" . OAuthUtil::urlencodeRFC3986($this->secret); 42 | }/*}}}*/ 43 | 44 | function __toString() {/*{{{*/ 45 | return $this->to_string(); 46 | }/*}}}*/ 47 | }/*}}}*/ 48 | 49 | class OAuthSignatureMethod {/*{{{*/ 50 | 51 | }/*}}}*/ 52 | 53 | class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {/*{{{*/ 54 | function get_name() {/*{{{*/ 55 | return "HMAC-SHA1"; 56 | }/*}}}*/ 57 | 58 | public function build_signature($request, $consumer, $token) {/*{{{*/ 59 | $sig = array( 60 | OAuthUtil::urlencodeRFC3986($request->get_normalized_http_method()), 61 | OAuthUtil::urlencodeRFC3986($request->get_normalized_http_url()), 62 | OAuthUtil::urlencodeRFC3986($request->get_signable_parameters()), 63 | ); 64 | 65 | $key = OAuthUtil::urlencodeRFC3986($consumer->secret) . "&"; 66 | 67 | if ($token) { 68 | $key .= OAuthUtil::urlencodeRFC3986($token->secret); 69 | } 70 | 71 | $raw = implode("&", $sig); 72 | // for debug purposes 73 | $request->base_string = $raw; 74 | 75 | // this is silly. 76 | $hashed = base64_encode(hash_hmac("sha1", $raw, $key, TRUE)); 77 | return $hashed; 78 | }/*}}}*/ 79 | }/*}}}*/ 80 | 81 | class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {/*{{{*/ 82 | public function get_name() {/*{{{*/ 83 | return "PLAINTEXT"; 84 | }/*}}}*/ 85 | public function build_signature($request, $consumer, $token) {/*{{{*/ 86 | $sig = array( 87 | OAuthUtil::urlencodeRFC3986($consumer->secret) 88 | ); 89 | 90 | if ($token) { 91 | array_push($sig, OAuthUtil::urlencodeRFC3986($token->secret)); 92 | } else { 93 | array_push($sig, ''); 94 | } 95 | 96 | $raw = implode("&", $sig); 97 | // for debug purposes 98 | $request->base_string = $raw; 99 | 100 | return OAuthUtil::urlencodeRFC3986($raw); 101 | }/*}}}*/ 102 | }/*}}}*/ 103 | 104 | class OAuthRequest {/*{{{*/ 105 | private $parameters; 106 | private $http_method; 107 | private $http_url; 108 | // for debug purposes 109 | public $base_string; 110 | public static $version = '1.0'; 111 | 112 | function __construct($http_method, $http_url, $parameters=NULL) {/*{{{*/ 113 | @$parameters or $parameters = array(); 114 | $this->parameters = $parameters; 115 | $this->http_method = $http_method; 116 | $this->http_url = $http_url; 117 | }/*}}}*/ 118 | 119 | 120 | /** 121 | * attempt to build up a request from what was passed to the server 122 | */ 123 | public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {/*{{{*/ 124 | @$http_url or $http_url = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; 125 | @$http_method or $http_method = $_SERVER['REQUEST_METHOD']; 126 | 127 | $request_headers = OAuthRequest::get_headers(); 128 | 129 | // let the library user override things however they'd like, if they know 130 | // which parameters to use then go for it, for example XMLRPC might want to 131 | // do this 132 | if ($parameters) { 133 | $req = new OAuthRequest($http_method, $http_url, $parameters); 134 | } 135 | // next check for the auth header, we need to do some extra stuff 136 | // if that is the case, namely suck in the parameters from GET or POST 137 | // so that we can include them in the signature 138 | else if (@substr($request_headers['Authorization'], 0, 5) == "OAuth") { 139 | $header_parameters = OAuthRequest::split_header($request_headers['Authorization']); 140 | if ($http_method == "GET") { 141 | $req_parameters = $_GET; 142 | } 143 | else if ($http_method = "POST") { 144 | $req_parameters = $_POST; 145 | } 146 | $parameters = array_merge($header_parameters, $req_parameters); 147 | $req = new OAuthRequest($http_method, $http_url, $parameters); 148 | } 149 | else if ($http_method == "GET") { 150 | $req = new OAuthRequest($http_method, $http_url, $_GET); 151 | } 152 | else if ($http_method == "POST") { 153 | $req = new OAuthRequest($http_method, $http_url, $_POST); 154 | } 155 | return $req; 156 | }/*}}}*/ 157 | 158 | /** 159 | * pretty much a helper function to set up the request 160 | */ 161 | public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {/*{{{*/ 162 | // @$parameters or $parameters = array(); 163 | if(!is_array($parameters)) $parameters = array(); 164 | $defaults = array("oauth_version" => OAuthRequest::$version, 165 | "oauth_nonce" => OAuthRequest::generate_nonce(), 166 | "oauth_timestamp" => OAuthRequest::generate_timestamp(), 167 | "oauth_consumer_key" => $consumer->key); 168 | $parameters = array_merge($defaults, $parameters); 169 | 170 | if ($token) { 171 | $parameters['oauth_token'] = $token->key; 172 | } 173 | return new OAuthRequest($http_method, $http_url, $parameters); 174 | }/*}}}*/ 175 | 176 | public function set_parameter($name, $value) { 177 | $this->parameters[$name] = $value; 178 | } 179 | 180 | public function get_parameter($name) { 181 | return $this->parameters[$name]; 182 | } 183 | 184 | public function get_parameters() { 185 | return $this->parameters; 186 | } 187 | 188 | /** 189 | * return a string that consists of all the parameters that need to be signed 190 | */ 191 | public function get_signable_parameters() {/*{{{*/ 192 | $sorted = $this->parameters; 193 | ksort($sorted); 194 | 195 | $total = array(); 196 | foreach ($sorted as $k => $v) { 197 | if ($k == "oauth_signature") continue; 198 | //$total[] = $k . "=" . $v; 199 | // andy, apparently we need to double encode or something yuck 200 | $total[] = OAuthUtil::urlencodeRFC3986($k) . "=" . OAuthUtil::urlencodeRFC3986($v); 201 | } 202 | return implode("&", $total); 203 | }/*}}}*/ 204 | 205 | /** 206 | * just uppercases the http method 207 | */ 208 | public function get_normalized_http_method() {/*{{{*/ 209 | return strtoupper($this->http_method); 210 | }/*}}}*/ 211 | 212 | /** 213 | * parses the url and rebuilds it to be 214 | * scheme://host/path 215 | */ 216 | public function get_normalized_http_url() {/*{{{*/ 217 | $parts = parse_url($this->http_url); 218 | $port = ""; 219 | if( array_key_exists('port', $parts) && $parts['port'] != '80' ){ 220 | $port = ':' . $parts['port']; 221 | } 222 | ## aroth, updated to include port 223 | $url_string = 224 | "{$parts['scheme']}://{$parts['host']}{$port}{$parts['path']}"; 225 | return $url_string; 226 | }/*}}}*/ 227 | 228 | /** 229 | * builds a url usable for a GET request 230 | */ 231 | public function to_url() {/*{{{*/ 232 | $out = $this->get_normalized_http_url() . "?"; 233 | $out .= $this->to_postdata(); 234 | return $out; 235 | }/*}}}*/ 236 | 237 | /** 238 | * builds the data one would send in a POST request 239 | */ 240 | public function to_postdata() {/*{{{*/ 241 | $total = array(); 242 | foreach ($this->parameters as $k => $v) { 243 | $total[] = OAuthUtil::urlencodeRFC3986($k) . "=" . OAuthUtil::urlencodeRFC3986($v); 244 | } 245 | $out = implode("&", $total); 246 | return $out; 247 | }/*}}}*/ 248 | 249 | /** 250 | * builds the Authorization: header 251 | */ 252 | public function to_header() {/*{{{*/ 253 | $out ='"Authorization: OAuth realm="",'; 254 | $total = array(); 255 | foreach ($this->parameters as $k => $v) { 256 | if (substr($k, 0, 5) != "oauth") continue; 257 | $total[] = OAuthUtil::urlencodeRFC3986($k) . '="' . OAuthUtil::urlencodeRFC3986($v) . '"'; 258 | } 259 | $out = implode(",", $total); 260 | return $out; 261 | }/*}}}*/ 262 | 263 | public function __toString() {/*{{{*/ 264 | return $this->to_url(); 265 | }/*}}}*/ 266 | 267 | 268 | public function sign_request($signature_method, $consumer, $token) {/*{{{*/ 269 | $this->set_parameter("oauth_signature_method", $signature_method->get_name()); 270 | $signature = $this->build_signature($signature_method, $consumer, $token); 271 | $this->set_parameter("oauth_signature", $signature); 272 | }/*}}}*/ 273 | 274 | public function build_signature($signature_method, $consumer, $token) {/*{{{*/ 275 | $signature = $signature_method->build_signature($this, $consumer, $token); 276 | return $signature; 277 | }/*}}}*/ 278 | 279 | /** 280 | * util function: current timestamp 281 | */ 282 | private static function generate_timestamp() {/*{{{*/ 283 | return time(); 284 | }/*}}}*/ 285 | 286 | /** 287 | * util function: current nonce 288 | */ 289 | private static function generate_nonce() {/*{{{*/ 290 | $mt = microtime(); 291 | $rand = mt_rand(); 292 | 293 | return md5($mt . $rand); // md5s look nicer than numbers 294 | }/*}}}*/ 295 | 296 | /** 297 | * util function for turning the Authorization: header into 298 | * parameters, has to do some unescaping 299 | */ 300 | private static function split_header($header) {/*{{{*/ 301 | // this should be a regex 302 | // error cases: commas in parameter values 303 | $parts = explode(",", $header); 304 | $out = array(); 305 | foreach ($parts as $param) { 306 | $param = ltrim($param); 307 | // skip the "realm" param, nobody ever uses it anyway 308 | if (substr($param, 0, 5) != "oauth") continue; 309 | 310 | $param_parts = explode("=", $param); 311 | 312 | // rawurldecode() used because urldecode() will turn a "+" in the 313 | // value into a space 314 | $out[$param_parts[0]] = rawurldecode(substr($param_parts[1], 1, -1)); 315 | } 316 | return $out; 317 | }/*}}}*/ 318 | 319 | /** 320 | * helper to try to sort out headers for people who aren't running apache 321 | */ 322 | private static function get_headers() { 323 | if (function_exists('apache_request_headers')) { 324 | // we need this to get the actual Authorization: header 325 | // because apache tends to tell us it doesn't exist 326 | return apache_request_headers(); 327 | } 328 | // otherwise we don't have apache and are just going to have to hope 329 | // that $_SERVER actually contains what we need 330 | $out = array(); 331 | foreach ($_SERVER as $key => $value) { 332 | if (substr($key, 0, 5) == "HTTP_") { 333 | // this is chaos, basically it is just there to capitalize the first 334 | // letter of every word that is not an initial HTTP and strip HTTP 335 | // code from przemek 336 | $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5))))); 337 | $out[$key] = $value; 338 | } 339 | } 340 | return $out; 341 | } 342 | }/*}}}*/ 343 | 344 | class OAuthServer {/*{{{*/ 345 | protected $timestamp_threshold = 300; // in seconds, five minutes 346 | protected $version = 1.0; // hi blaine 347 | protected $signature_methods = array(); 348 | 349 | protected $data_store; 350 | 351 | function __construct($data_store) {/*{{{*/ 352 | $this->data_store = $data_store; 353 | }/*}}}*/ 354 | 355 | public function add_signature_method($signature_method) {/*{{{*/ 356 | $this->signature_methods[$signature_method->get_name()] = 357 | $signature_method; 358 | }/*}}}*/ 359 | 360 | // high level functions 361 | 362 | /** 363 | * process a request_token request 364 | * returns the request token on success 365 | */ 366 | public function fetch_request_token(&$request) {/*{{{*/ 367 | $this->get_version($request); 368 | 369 | $consumer = $this->get_consumer($request); 370 | 371 | // no token required for the initial token request 372 | $token = NULL; 373 | 374 | $this->check_signature($request, $consumer, $token); 375 | 376 | $new_token = $this->data_store->new_request_token($consumer); 377 | 378 | return $new_token; 379 | }/*}}}*/ 380 | 381 | /** 382 | * process an access_token request 383 | * returns the access token on success 384 | */ 385 | public function fetch_access_token(&$request) {/*{{{*/ 386 | $this->get_version($request); 387 | 388 | $consumer = $this->get_consumer($request); 389 | 390 | // requires authorized request token 391 | $token = $this->get_token($request, $consumer, "request"); 392 | 393 | $this->check_signature($request, $consumer, $token); 394 | 395 | $new_token = $this->data_store->new_access_token($token, $consumer); 396 | 397 | return $new_token; 398 | }/*}}}*/ 399 | 400 | /** 401 | * verify an api call, checks all the parameters 402 | */ 403 | public function verify_request(&$request) {/*{{{*/ 404 | $this->get_version($request); 405 | $consumer = $this->get_consumer($request); 406 | $token = $this->get_token($request, $consumer, "access"); 407 | $this->check_signature($request, $consumer, $token); 408 | return array($consumer, $token); 409 | }/*}}}*/ 410 | 411 | // Internals from here 412 | /** 413 | * version 1 414 | */ 415 | private function get_version(&$request) {/*{{{*/ 416 | $version = $request->get_parameter("oauth_version"); 417 | if (!$version) { 418 | $version = 1.0; 419 | } 420 | if ($version && $version != $this->version) { 421 | throw new OAuthException("OAuth version '$version' not supported"); 422 | } 423 | return $version; 424 | }/*}}}*/ 425 | 426 | /** 427 | * figure out the signature with some defaults 428 | */ 429 | private function get_signature_method(&$request) {/*{{{*/ 430 | $signature_method = 431 | @$request->get_parameter("oauth_signature_method"); 432 | if (!$signature_method) { 433 | $signature_method = "PLAINTEXT"; 434 | } 435 | if (!in_array($signature_method, 436 | array_keys($this->signature_methods))) { 437 | throw new OAuthException( 438 | "Signature method '$signature_method' not supported try one of the following: " . implode(", ", array_keys($this->signature_methods)) 439 | ); 440 | } 441 | return $this->signature_methods[$signature_method]; 442 | }/*}}}*/ 443 | 444 | /** 445 | * try to find the consumer for the provided request's consumer key 446 | */ 447 | private function get_consumer(&$request) {/*{{{*/ 448 | $consumer_key = @$request->get_parameter("oauth_consumer_key"); 449 | if (!$consumer_key) { 450 | throw new OAuthException("Invalid consumer key"); 451 | } 452 | 453 | $consumer = $this->data_store->lookup_consumer($consumer_key); 454 | if (!$consumer) { 455 | throw new OAuthException("Invalid consumer"); 456 | } 457 | 458 | return $consumer; 459 | }/*}}}*/ 460 | 461 | /** 462 | * try to find the token for the provided request's token key 463 | */ 464 | private function get_token(&$request, $consumer, $token_type="access") {/*{{{*/ 465 | $token_field = @$request->get_parameter('oauth_token'); 466 | $token = $this->data_store->lookup_token( 467 | $consumer, $token_type, $token_field 468 | ); 469 | if (!$token) { 470 | throw new OAuthException("Invalid $token_type token: $token_field"); 471 | } 472 | return $token; 473 | }/*}}}*/ 474 | 475 | /** 476 | * all-in-one function to check the signature on a request 477 | * should guess the signature method appropriately 478 | */ 479 | private function check_signature(&$request, $consumer, $token) {/*{{{*/ 480 | // this should probably be in a different method 481 | $timestamp = @$request->get_parameter('oauth_timestamp'); 482 | $nonce = @$request->get_parameter('oauth_nonce'); 483 | 484 | $this->check_timestamp($timestamp); 485 | $this->check_nonce($consumer, $token, $nonce, $timestamp); 486 | 487 | $signature_method = $this->get_signature_method($request); 488 | 489 | $signature = $request->get_parameter('oauth_signature'); 490 | $built = $signature_method->build_signature( 491 | $request, $consumer, $token 492 | ); 493 | 494 | if ($signature != $built) { 495 | throw new OAuthException("Invalid signature"); 496 | } 497 | }/*}}}*/ 498 | 499 | /** 500 | * check that the timestamp is new enough 501 | */ 502 | private function check_timestamp($timestamp) {/*{{{*/ 503 | // verify that timestamp is recentish 504 | $now = time(); 505 | if ($now - $timestamp > $this->timestamp_threshold) { 506 | throw new OAuthException("Expired timestamp, yours $timestamp, ours $now"); 507 | } 508 | }/*}}}*/ 509 | 510 | /** 511 | * check that the nonce is not repeated 512 | */ 513 | private function check_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/ 514 | // verify that the nonce is uniqueish 515 | $found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp); 516 | if ($found) { 517 | throw new OAuthException("Nonce already used: $nonce"); 518 | } 519 | }/*}}}*/ 520 | 521 | 522 | 523 | }/*}}}*/ 524 | 525 | class OAuthDataStore {/*{{{*/ 526 | function lookup_consumer($consumer_key) {/*{{{*/ 527 | // implement me 528 | }/*}}}*/ 529 | 530 | function lookup_token($consumer, $token_type, $token) {/*{{{*/ 531 | // implement me 532 | }/*}}}*/ 533 | 534 | function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/ 535 | // implement me 536 | }/*}}}*/ 537 | 538 | function fetch_request_token($consumer) {/*{{{*/ 539 | // return a new token attached to this consumer 540 | }/*}}}*/ 541 | 542 | function fetch_access_token($token, $consumer) {/*{{{*/ 543 | // return a new access token attached to this consumer 544 | // for the user associated with this token if the request token 545 | // is authorized 546 | // should also invalidate the request token 547 | }/*}}}*/ 548 | 549 | }/*}}}*/ 550 | 551 | 552 | /* A very naive dbm-based oauth storage 553 | */ 554 | class SimpleOAuthDataStore extends OAuthDataStore {/*{{{*/ 555 | private $dbh; 556 | 557 | function __construct($path = "oauth.gdbm") {/*{{{*/ 558 | $this->dbh = dba_popen($path, 'c', 'gdbm'); 559 | }/*}}}*/ 560 | 561 | function __destruct() {/*{{{*/ 562 | dba_close($this->dbh); 563 | }/*}}}*/ 564 | 565 | function lookup_consumer($consumer_key) {/*{{{*/ 566 | $rv = dba_fetch("consumer_$consumer_key", $this->dbh); 567 | if ($rv === FALSE) { 568 | return NULL; 569 | } 570 | $obj = unserialize($rv); 571 | if (!($obj instanceof OAuthConsumer)) { 572 | return NULL; 573 | } 574 | return $obj; 575 | }/*}}}*/ 576 | 577 | function lookup_token($consumer, $token_type, $token) {/*{{{*/ 578 | $rv = dba_fetch("${token_type}_${token}", $this->dbh); 579 | if ($rv === FALSE) { 580 | return NULL; 581 | } 582 | $obj = unserialize($rv); 583 | if (!($obj instanceof OAuthToken)) { 584 | return NULL; 585 | } 586 | return $obj; 587 | }/*}}}*/ 588 | 589 | function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/ 590 | return dba_exists("nonce_$nonce", $this->dbh); 591 | }/*}}}*/ 592 | 593 | function new_token($consumer, $type="request") {/*{{{*/ 594 | $key = md5(time()); 595 | $secret = time() + time(); 596 | $token = new OAuthToken($key, md5(md5($secret))); 597 | if (!dba_insert("${type}_$key", serialize($token), $this->dbh)) { 598 | throw new OAuthException("doooom!"); 599 | } 600 | return $token; 601 | }/*}}}*/ 602 | 603 | function new_request_token($consumer) {/*{{{*/ 604 | return $this->new_token($consumer, "request"); 605 | }/*}}}*/ 606 | 607 | function new_access_token($token, $consumer) {/*{{{*/ 608 | 609 | $token = $this->new_token($consumer, 'access'); 610 | dba_delete("request_" . $token->key, $this->dbh); 611 | return $token; 612 | }/*}}}*/ 613 | }/*}}}*/ 614 | 615 | class OAuthUtil {/*{{{*/ 616 | public static function urlencodeRFC3986($string) {/*{{{*/ 617 | return str_replace('%7E', '~', rawurlencode($string)); 618 | }/*}}}*/ 619 | 620 | public static function urldecodeRFC3986($string) {/*{{{*/ 621 | return rawurldecode($string); 622 | }/*}}}*/ 623 | }/*}}}*/ 624 | 625 | ?> 626 | --------------------------------------------------------------------------------