├── 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)))."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 "]".nl2br(htmlspecialchars(var_export($req, TRUE)))."
base string: ".htmlspecialchars($req->base_string)."
".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 |
--------------------------------------------------------------------------------