From fe9fc152861a0131582c4aa512870d2d01bccb57 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 4 Aug 2009 02:21:18 +0000 Subject: Make TwitterStatusFetcher daemon work with OAuth --- scripts/twitterstatusfetcher.php | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) (limited to 'scripts') diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php index e1745cfc0..d9f035fa6 100755 --- a/scripts/twitterstatusfetcher.php +++ b/scripts/twitterstatusfetcher.php @@ -191,7 +191,7 @@ class TwitterStatusFetcher extends Daemon { $flink = new Foreign_link(); - $flink->service = 1; // Twitter + $flink->service = TWITTER_SERVICE; $flink->orderBy('last_noticesync'); @@ -241,35 +241,33 @@ class TwitterStatusFetcher extends Daemon function getTimeline($flink) { - if (empty($flink)) { + if (empty($flink)) { common_log(LOG_WARNING, "Can't retrieve Foreign_link for foreign ID $fid"); return; } - $fuser = $flink->getForeignUser(); - - if (empty($fuser)) { - common_log(LOG_WARNING, "Unmatched user for ID " . - $flink->user_id); - return; - } - if (defined('SCRIPT_DEBUG')) { common_debug('Trying to get timeline for Twitter user ' . - "$fuser->nickname ($flink->foreign_id)."); + $flink->foreign_id); } // XXX: Biggest remaining issue - How do we know at which status // to start importing? How many statuses? Right now I'm going // with the default last 20. - $url = 'http://twitter.com/statuses/friends_timeline.json'; + $client = new TwitterOAuthClient($flink->token, $flink->credentials); - $timeline_json = get_twitter_data($url, $fuser->nickname, - $flink->credentials); + $timeline = null; - $timeline = json_decode($timeline_json); + try { + $timeline = $client->statuses_friends_timeline(); + } catch (OAuthClientCurlException $e) { + common_log(LOG_WARNING, + 'OAuth client unable to get friends timeline for user ' . + $flink->user_id . ' - code: ' . + $e->getCode() . 'msg: ' . $e->getMessage()); + } if (empty($timeline)) { common_log(LOG_WARNING, "Empty timeline."); @@ -303,7 +301,7 @@ class TwitterStatusFetcher extends Daemon $id = $this->ensureProfile($status->user); $profile = Profile::staticGet($id); - if (!$profile) { + if (empty($profile)) { common_log(LOG_ERR, 'Problem saving notice. No associated Profile.'); return null; @@ -318,7 +316,7 @@ class TwitterStatusFetcher extends Daemon // check to see if we've already imported the status - if (!$notice) { + if (empty($notice)) { $notice = new Notice(); -- cgit v1.2.3-54-g00ecf From 5576917662b47ef27bf7ba3e551411c3d3b5e26c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 4 Aug 2009 17:20:28 +0000 Subject: Use empty() for checking whether DB_DataObject returned something --- scripts/twitterstatusfetcher.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'scripts') diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php index d9f035fa6..dd139417c 100755 --- a/scripts/twitterstatusfetcher.php +++ b/scripts/twitterstatusfetcher.php @@ -356,7 +356,7 @@ class TwitterStatusFetcher extends Daemon $profileurl = 'http://twitter.com/' . $user->screen_name; $profile = Profile::staticGet('profileurl', $profileurl); - if ($profile) { + if (!empty($profile) { if (defined('SCRIPT_DEBUG')) { common_debug("Profile for $profile->nickname found."); } @@ -394,7 +394,7 @@ class TwitterStatusFetcher extends Daemon // check for remote profile $remote_pro = Remote_profile::staticGet('uri', $profileurl); - if (!$remote_pro) { + if (empty($remote_pro)) { $remote_pro = new Remote_profile(); -- cgit v1.2.3-54-g00ecf From cd71f9cc51f77b6b8e3b11c34cb7523f36339cea Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 5 Aug 2009 00:16:12 +0000 Subject: Better (hopefully) database connection management for child processes --- scripts/twitterstatusfetcher.php | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) (limited to 'scripts') diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php index dd139417c..67f52a3cc 100755 --- a/scripts/twitterstatusfetcher.php +++ b/scripts/twitterstatusfetcher.php @@ -99,13 +99,6 @@ class TwitterStatusFetcher extends Daemon foreach ($flinks as $f) { - // We have to disconnect from the DB before forking so - // each sub-process will open its own connection and - // avoid stomping on the others - - $conn = &$f->getDatabaseConnection(); - $conn->disconnect(); - $pid = pcntl_fork(); if ($pid == -1) { @@ -125,7 +118,24 @@ class TwitterStatusFetcher extends Daemon } else { // Child + + // Each child ps needs its own DB connection + + // Note: DataObject::getDatabaseConnection() creates + // a new connection if there isn't one already + + global $_DB_DATAOBJECT; + $conn = &$f->getDatabaseConnection(); + $this->getTimeline($f); + + $conn->disconnect(); + + // XXX: Couldn't find a less brutal way to blow + // away a cached connection + + unset($_DB_DATAOBJECT['CONNECTIONS']); + exit(); } @@ -189,7 +199,10 @@ class TwitterStatusFetcher extends Daemon function refreshFlinks() { + global $_DB_DATAOBJECT; + $flink = new Foreign_link(); + $conn = &$flink->getDatabaseConnection(); $flink->service = TWITTER_SERVICE; @@ -215,6 +228,9 @@ class TwitterStatusFetcher extends Daemon $flink->free(); unset($flink); + $conn->disconnect(); + unset($_DB_DATAOBJECT['CONNECTIONS']); + return $flinks; } @@ -299,6 +315,7 @@ class TwitterStatusFetcher extends Daemon function saveStatus($status, $flink) { $id = $this->ensureProfile($status->user); + $profile = Profile::staticGet($id); if (empty($profile)) { @@ -356,7 +373,7 @@ class TwitterStatusFetcher extends Daemon $profileurl = 'http://twitter.com/' . $user->screen_name; $profile = Profile::staticGet('profileurl', $profileurl); - if (!empty($profile) { + if (!empty($profile)) { if (defined('SCRIPT_DEBUG')) { common_debug("Profile for $profile->nickname found."); } -- cgit v1.2.3-54-g00ecf From 421e33f145de0476088f0b802f0b0a9303372b8a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 6 Aug 2009 07:03:05 +0000 Subject: - Rewrote SyncTwitterFriends as a daemon - Made it use OAuth - Code clean up --- lib/twitter.php | 325 ++++++++--------------------------------- lib/twitteroauthclient.php | 40 +++++ scripts/synctwitterfriends.php | 261 ++++++++++++++++++++++++++------- 3 files changed, 306 insertions(+), 320 deletions(-) (limited to 'scripts') diff --git a/lib/twitter.php b/lib/twitter.php index 2369ac267..345516997 100644 --- a/lib/twitter.php +++ b/lib/twitter.php @@ -17,83 +17,20 @@ * along with this program. If not, see . */ -if (!defined('LACONICA')) { exit(1); } - -define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1 - -function get_twitter_data($uri, $screen_name, $password) -{ - - $options = array( - CURLOPT_USERPWD => sprintf("%s:%s", $screen_name, $password), - CURLOPT_RETURNTRANSFER => true, - CURLOPT_FAILONERROR => true, - CURLOPT_HEADER => false, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_USERAGENT => "Laconica", - CURLOPT_CONNECTTIMEOUT => 120, - CURLOPT_TIMEOUT => 120, - # Twitter is strict about accepting invalid "Expect" headers - CURLOPT_HTTPHEADER => array('Expect:') - ); - - $ch = curl_init($uri); - curl_setopt_array($ch, $options); - $data = curl_exec($ch); - $errmsg = curl_error($ch); - - if ($errmsg) { - common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $screen_name.", - __FILE__); - - if (defined('SCRIPT_DEBUG')) { - print "cURL error: $errmsg - trying to load: $uri with user $screen_name.\n"; - } - } - - curl_close($ch); - - return $data; +if (!defined('LACONICA')) { + exit(1); } -function twitter_json_data($uri, $screen_name, $password) -{ - $json_data = get_twitter_data($uri, $screen_name, $password); - - if (!$json_data) { - return false; - } - - $data = json_decode($json_data); - - if (!$data) { - return false; - } - - return $data; -} - -function twitter_user_info($screen_name, $password) -{ - $uri = "http://twitter.com/users/show/$screen_name.json"; - return twitter_json_data($uri, $screen_name, $password); -} - -function twitter_friends_ids($screen_name, $password) -{ - $uri = "http://twitter.com/friends/ids/$screen_name.json"; - return twitter_json_data($uri, $screen_name, $password); -} +define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1 function update_twitter_user($twitter_id, $screen_name) { $uri = 'http://twitter.com/' . $screen_name; - $fuser = new Foreign_user(); $fuser->query('BEGIN'); - // Dropping down to SQL because regular db_object udpate stuff doesn't seem + // Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem // to work so good with tables that have multiple column primary keys // Any time we update the uri for a forein user we have to make sure there @@ -102,35 +39,14 @@ function update_twitter_user($twitter_id, $screen_name) $qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = '; $qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE; - $result = $fuser->query($qry); - - if ($result) { - common_debug("Removed uri ($uri) from another foreign_user who was squatting on it."); - if (defined('SCRIPT_DEBUG')) { - print("Removed uri ($uri) from another Twitter user who was squatting on it.\n"); - } - } + $fuser->query($qry); // Update the user + $qry = 'UPDATE foreign_user SET nickname = '; $qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' '; $qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE; - $result = $fuser->query($qry); - - if (!$result) { - common_log(LOG_WARNING, - "Couldn't update foreign_user data for Twitter user: $screen_name"); - common_log_db_error($fuser, 'UPDATE', __FILE__); - if (defined('SCRIPT_DEBUG')) { - print "UPDATE failed: for Twitter user: $twitter_id - $screen_name. - "; - print common_log_objstring($fuser) . "\n"; - $error = &PEAR::getStaticProperty('DB_DataObject','lastError'); - print "DB_DataObject Error: " . $error->getMessage() . "\n"; - } - return false; - } - $fuser->query('COMMIT'); $fuser->free(); @@ -147,23 +63,22 @@ function add_twitter_user($twitter_id, $screen_name) // Clear out any bad old foreign_users with the new user's legit URL // This can happen when users move around or fakester accounts get // repoed, and things like that. + $luser = new Foreign_user(); $luser->uri = $new_uri; $luser->service = TWITTER_SERVICE; $result = $luser->delete(); - if ($result) { + if (empty($result)) { common_log(LOG_WARNING, "Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri"); - if (defined('SCRIPT_DEBUG')) { - print "Removed invalid Twitter user squatting on uri: $new_uri\n"; - } } $luser->free(); unset($luser); // Otherwise, create a new Twitter user + $fuser = new Foreign_user(); $fuser->nickname = $screen_name; @@ -173,21 +88,12 @@ function add_twitter_user($twitter_id, $screen_name) $fuser->created = common_sql_now(); $result = $fuser->insert(); - if (!$result) { + if (empty($result)) { common_log(LOG_WARNING, "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name."); common_log_db_error($fuser, 'INSERT', __FILE__); - if (defined('SCRIPT_DEBUG')) { - print "INSERT failed: could not add new Twitter user: $twitter_id - $screen_name. - "; - print common_log_objstring($fuser) . "\n"; - $error = &PEAR::getStaticProperty('DB_DataObject','lastError'); - print "DB_DataObject Error: " . $error->getMessage() . "\n"; - } } else { common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id)."); - if (defined('SCRIPT_DEBUG')) { - print "Added new Twitter user: $screen_name ($twitter_id).\n"; - } } return $result; @@ -199,23 +105,20 @@ function save_twitter_user($twitter_id, $screen_name) // Check to see whether the Twitter user is already in the system, // and update its screen name and uri if so. + $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE); - if ($fuser) { + if (!empty($fuser)) { $result = true; // Only update if Twitter screen name has changed + if ($fuser->nickname != $screen_name) { $result = update_twitter_user($twitter_id, $screen_name); common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' . "$fuser->id to $screen_name, was $fuser->nickname"); - - if (defined('SCRIPT_DEBUG')) { - print 'Updated nickname (and URI) for Twitter user ' . - "$fuser->id to $screen_name, was $fuser->nickname\n"; - } } return $result; @@ -230,119 +133,6 @@ function save_twitter_user($twitter_id, $screen_name) return true; } -function retreive_twitter_friends($twitter_id, $screen_name, $password) -{ - $friends = array(); - - $uri = "http://twitter.com/statuses/friends/$twitter_id.json?page="; - $friends_ids = twitter_friends_ids($screen_name, $password); - - if (!$friends_ids) { - return $friends; - } - - if (defined('SCRIPT_DEBUG')) { - print "Twitter 'social graph' ids method says $screen_name has " . - count($friends_ids) . " friends.\n"; - } - - // Calculate how many pages to get... - $pages = ceil(count($friends_ids) / 100); - - if ($pages == 0) { - common_log(LOG_WARNING, - "Twitter bridge - $screen_name seems to have no friends."); - if (defined('SCRIPT_DEBUG')) { - print "$screen_name seems to have no friends.\n"; - } - } - - for ($i = 1; $i <= $pages; $i++) { - - $data = get_twitter_data($uri . $i, $screen_name, $password); - - if (!$data) { - common_log(LOG_WARNING, - "Twitter bridge - Couldn't retrieve page $i of $screen_name's friends."); - if (defined('SCRIPT_DEBUG')) { - print "Couldn't retrieve page $i of $screen_name's friends.\n"; - } - continue; - } - - $more_friends = json_decode($data); - - if (!$more_friends) { - - common_log(LOG_WARNING, - "Twitter bridge - No data for page $i of $screen_name's friends."); - if (defined('SCRIPT_DEBUG')) { - print "No data for page $i of $screen_name's friends.\n"; - } - continue; - } - - $friends = array_merge($friends, $more_friends); - } - - return $friends; -} - -function save_twitter_friends($user, $twitter_id, $screen_name, $password) -{ - - $friends = retreive_twitter_friends($twitter_id, $screen_name, $password); - - if (empty($friends)) { - common_debug("Twitter bridge - Couldn't get friends data from Twitter for $screen_name."); - if (defined('SCRIPT_DEBUG')) { - print "Couldn't get friends data from Twitter for $screen_name.\n"; - } - return false; - } - - foreach ($friends as $friend) { - - $friend_name = $friend->screen_name; - $friend_id = (int) $friend->id; - - // Update or create the Foreign_user record - if (!save_twitter_user($friend_id, $friend_name)) { - common_log(LOG_WARNING, - "Twitter bridge - couldn't save $screen_name's friend, $friend_name."); - if (defined('SCRIPT_DEBUG')) { - print "Couldn't save $screen_name's friend, $friend_name.\n"; - } - continue; - } - - // Check to see if there's a related local user - $flink = Foreign_link::getByForeignID($friend_id, 1); - - if ($flink) { - - // Get associated user and subscribe her - $friend_user = User::staticGet('id', $flink->user_id); - if (!empty($friend_user)) { - $result = subs_subscribe_to($user, $friend_user); - - if ($result === true) { - common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname."); - if (defined('SCRIPT_DEBUG')) { - print("Subscribed $friend_user->nickname to $user->nickname.\n"); - } - } else { - if (defined('SCRIPT_DEBUG')) { - print "$result ($friend_user->nickname to $user->nickname)\n"; - } - } - } - } - } - - return true; -} - function is_twitter_bound($notice, $flink) { // Check to see if notice should go to Twitter @@ -351,7 +141,7 @@ function is_twitter_bound($notice, $flink) { // If it's not a Twitter-style reply, or if the user WANTS to send replies. if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) || ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) { - return true; + return true; } } @@ -361,7 +151,7 @@ function is_twitter_bound($notice, $flink) { function broadcast_twitter($notice) { $flink = Foreign_link::getByUserID($notice->profile_id, - TWITTER_SERVICE); + TWITTER_SERVICE); if (is_twitter_bound($notice, $flink)) { @@ -378,54 +168,53 @@ function broadcast_twitter($notice) $status = $client->statuses_update($statustxt); } catch (OAuthClientCurlException $e) { - if ($e->getMessage() == 'The requested URL returned error: 401') { + if ($e->getMessage() == 'The requested URL returned error: 401') { - $errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' . - 'Twitter OAuth access token.', - $user->nickname, $user->id); - common_log(LOG_WARNING, $errmsg); + $errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' . + 'Twitter OAuth access token.', + $user->nickname, $user->id); + common_log(LOG_WARNING, $errmsg); - // Bad auth token! We need to delete the foreign_link - // to Twitter and inform the user. + // Bad auth token! We need to delete the foreign_link + // to Twitter and inform the user. - remove_twitter_link($flink); - return true; + remove_twitter_link($flink); + return true; - } else { + } else { - // Some other error happened, so we should probably - // try to send again later. + // Some other error happened, so we should probably + // try to send again later. - $errmsg = sprintf('cURL error trying to send notice to Twitter ' . - 'for user %1$s (user id: %2$s) - ' . - 'code: %3$s message: $4$s.', - $user->nickname, $user->id, - $e->getCode(), $e->getMessage()); - common_log(LOG_WARNING, $errmsg); + $errmsg = sprintf('cURL error trying to send notice to Twitter ' . + 'for user %1$s (user id: %2$s) - ' . + 'code: %3$s message: $4$s.', + $user->nickname, $user->id, + $e->getCode(), $e->getMessage()); + common_log(LOG_WARNING, $errmsg); - return false; + return false; + } } - } - - if (empty($status)) { - // This could represent a failure posting, - // or the Twitter API might just be behaving flakey. + if (empty($status)) { - $errmsg = sprint('No data returned by Twitter API when ' . - 'trying to send update for %1$s (user id %2$s).', - $user->nickname, $user->id); - common_log(LOG_WARNING, $errmsg); + // This could represent a failure posting, + // or the Twitter API might just be behaving flakey. - return false; - } + $errmsg = sprint('No data returned by Twitter API when ' . + 'trying to send update for %1$s (user id %2$s).', + $user->nickname, $user->id); + common_log(LOG_WARNING, $errmsg); - // Notice crossed the great divide + return false; + } - $msg = sprintf('Twitter bridge posted notice %s to Twitter.', - $notice->id); - common_log(LOG_INFO, $msg); + // Notice crossed the great divide + $msg = sprintf('Twitter bridge posted notice %s to Twitter.', + $notice->id); + common_log(LOG_INFO, $msg); } return true; @@ -442,7 +231,7 @@ function remove_twitter_link($flink) if (empty($result)) { common_log(LOG_ERR, 'Could not remove Twitter bridge ' . - "Foreign_link for $user->nickname (user id: $user->id)!"); + "Foreign_link for $user->nickname (user id: $user->id)!"); common_log_db_error($flink, 'DELETE', __FILE__); } @@ -450,17 +239,17 @@ function remove_twitter_link($flink) if (isset($user->email)) { - $result = mail_twitter_bridge_removed($user); + $result = mail_twitter_bridge_removed($user); - if (!$result) { + if (!$result) { - $msg = 'Unable to send email to notify ' . - "$user->nickname (user id: $user->id) " . - 'that their Twitter bridge link was ' . - 'removed!'; + $msg = 'Unable to send email to notify ' . + "$user->nickname (user id: $user->id) " . + 'that their Twitter bridge link was ' . + 'removed!'; - common_log(LOG_WARNING, $msg); - } + common_log(LOG_WARNING, $msg); + } } } diff --git a/lib/twitteroauthclient.php b/lib/twitteroauthclient.php index c5f114fb0..2636a3833 100644 --- a/lib/twitteroauthclient.php +++ b/lib/twitteroauthclient.php @@ -58,4 +58,44 @@ class TwitterOAuthClient extends OAuthClient return $statuses; } + function statuses_friends($id = null, $user_id = null, $screen_name = null, + $page = null) + { + $url = "https://twitter.com/statuses/friends.json"; + + $params = array('id' => $id, + 'user_id' => $user_id, + 'screen_name' => $screen_name, + 'page' => $page); + $qry = http_build_query($params); + + if (!empty($qry)) { + $url .= "?$qry"; + } + + $response = $this->oAuthGet($url); + $ids = json_decode($response); + return $ids; + } + + function friends_ids($id = null, $user_id = null, $screen_name = null, + $page = null) + { + $url = "https://twitter.com/friends/ids.json"; + + $params = array('id' => $id, + 'user_id' => $user_id, + 'screen_name' => $screen_name, + 'page' => $page); + $qry = http_build_query($params); + + if (!empty($qry)) { + $url .= "?$qry"; + } + + $response = $this->oAuthGet($url); + $ids = json_decode($response); + return $ids; + } + } diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php index fe53ff44d..1bd75bac1 100755 --- a/scripts/synctwitterfriends.php +++ b/scripts/synctwitterfriends.php @@ -20,85 +20,242 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); -// Uncomment this to get useful console output +$shortoptions = 'di::'; +$longoptions = array('id::', 'debug'); + +$helptext = << + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ $helptext = <<service = 1; // Twitter -$flink->orderBy('last_friendsync'); -$flink->limit(25); // sync this many users during this run -$cnt = $flink->find(); + /** + * Name of this daemon + * + * @return string Name of the daemon. + */ -print "Updating Twitter friends subscriptions for $cnt users.\n"; + function name() + { + return ('synctwitterfriendsdaemon.' . $this->_id); + } -while ($flink->fetch()) { + function getObjects() + { + $flinks = array(); + $flink = new Foreign_link(); - if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) { + $conn = &$flink->getDatabaseConnection(); - $user = User::staticGet($flink->user_id); + $flink->service = TWITTER_SERVICE; + $flink->orderBy('last_friendsync'); + $flink->limit(25); // sync this many users during this run + $flink->find(); - if (empty($user)) { - common_log(LOG_WARNING, "Unmatched user for ID " . $flink->user_id); - print "Unmatched user for ID $flink->user_id\n"; - continue; + while ($flink->fetch()) { + if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) { + $flinks[] = clone($flink); + } } - print "Updating Twitter friends for $user->nickname (Laconica ID: $user->id)... "; + $conn->disconnect(); - $fuser = $flink->getForeignUser(); + global $_DB_DATAOBJECT; + unset($_DB_DATAOBJECT['CONNECTIONS']); - if (empty($fuser)) { - common_log(LOG_WARNING, "Unmatched user for ID " . $flink->user_id); - print "Unmatched user for ID $flink->user_id\n"; - continue; - } + return $flinks; + } + + function childTask($flink) { - save_twitter_friends($user, $fuser->id, $fuser->nickname, $flink->credentials); + // Each child ps needs its own DB connection + + // Note: DataObject::getDatabaseConnection() creates + // a new connection if there isn't one already + + $conn = &$flink->getDatabaseConnection(); + + $this->subscribeTwitterFriends($flink); $flink->last_friendsync = common_sql_now(); $flink->update(); - if (defined('SCRIPT_DEBUG')) { - print "\nDONE\n"; - } else { - print "DONE\n"; + $conn->disconnect(); + + // XXX: Couldn't find a less brutal way to blow + // away a cached connection + + global $_DB_DATAOBJECT; + unset($_DB_DATAOBJECT['CONNECTIONS']); + } + + function fetchTwitterFriends($flink) + { + $friends = array(); + + $client = new TwitterOAuthClient($flink->token, $flink->credentials); + + try { + $friends_ids = $client->friends_ids(); + } catch (OAuthCurlException $e) { + common_log(LOG_WARNING, $this->name() . + ' - cURL error getting friend ids ' . + $e->getCode() . ' - ' . $e->getMessage()); + return $friends; + } + + if (empty($friends_ids)) { + common_debug($this->name() . + " - Twitter user $flink->foreign_id " . + 'doesn\'t have any friends!'); + return $friends; + } + + common_debug($this->name() . ' - Twitter\'s API says Twitter user id ' . + "$flink->foreign_id has " . + count($friends_ids) . ' friends.'); + + // Calculate how many pages to get... + $pages = ceil(count($friends_ids) / 100); + + if ($pages == 0) { + common_debug($this->name() . " - $user seems to have no friends."); } + + for ($i = 1; $i <= $pages; $i++) { + + try { + $more_friends = $client->statuses_friends(null, null, null, $i); + } catch (OAuthCurlException $e) { + common_log(LOG_WARNING, $this->name() . + ' - cURL error getting Twitter statuses/friends ' . + "page $i - " . $e->getCode() . ' - ' . + $e->getMessage()); + } + + if (empty($more_friends)) { + common_log(LOG_WARNING, $this->name() . + " - Couldn't retrieve page $i " . + "of Twitter user $flink->foreign_id friends."); + continue; + } else { + $friends = array_merge($friends, $more_friends); + } + } + + return $friends; } -} -function lockFilename() -{ - $piddir = common_config('daemon', 'piddir'); - if (!$piddir) { - $piddir = '/var/run'; + function subscribeTwitterFriends($flink) + { + $friends = $this->fetchTwitterFriends($flink); + + if (empty($friends)) { + common_debug($this->name() . + ' - Couldn\'t get friends from Twitter for ' . + "Twitter user $flink->foreign_id."); + return false; + } + + $user = $flink->getUser(); + + foreach ($friends as $friend) { + + $friend_name = $friend->screen_name; + $friend_id = (int) $friend->id; + + // Update or create the Foreign_user record for each + // Twitter friend + + if (!save_twitter_user($friend_id, $friend_name)) { + common_log(LOG_WARNING, $this-name() . + " - Couldn't save $screen_name's friend, $friend_name."); + continue; + } + + // Check to see if there's a related local user + + $friend_flink = Foreign_link::getByForeignID($friend_id, + TWITTER_SERVICE); + + if (!empty($friend_flink)) { + + // Get associated user and subscribe her + + $friend_user = User::staticGet('id', $friend_flink->user_id); + + if (!empty($friend_user)) { + $result = subs_subscribe_to($user, $friend_user); + + if ($result === true) { + common_log(LOG_INFO, + $this->name() . ' - Subscribed ' . + "$friend_user->nickname to $user->nickname."); + } else { + common_debug($this->name() . + ' - Tried subscribing ' . + "$friend_user->nickname to $user->nickname - " . + $result); + } + } + } + } + + return true; } - return $piddir . '/synctwitterfriends.lock'; } -// Cleanup -fclose($lockfile); -unlink($lockfilename); +declare(ticks = 1); + +$id = null; +$debug = null; + +if (have_option('i')) { + $id = get_option_value('i'); +} else if (have_option('--id')) { + $id = get_option_value('--id'); +} else if (count($args) > 0) { + $id = $args[0]; +} else { + $id = null; +} + +if (have_option('d') || have_option('debug')) { + $debug = true; +} + +$syncer = new SyncTwitterFriendsDaemon($id, 60, 2, $debug); +$syncer->runOnce(); -exit(0); -- cgit v1.2.3-54-g00ecf From 93f585446ebaa4a9347b6e6f77f58ab023b5b51c Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Fri, 7 Aug 2009 01:18:17 +0800 Subject: Added configuration options to enable/disable SMS and Twitter integration. This disables the IM, SMS and Twitter settings pages and queue handlers depending on the config options. --- README | 16 ++++++++++++++++ actions/imsettings.php | 6 ++++++ actions/smssettings.php | 6 ++++++ actions/twittersettings.php | 6 ++++++ config.php.sample | 6 ++++++ lib/action.php | 17 +++++++++++------ lib/common.php | 4 ++++ lib/connectsettingsaction.php | 30 ++++++++++++++++-------------- scripts/getvaliddaemons.php | 8 ++++++-- 9 files changed, 77 insertions(+), 22 deletions(-) (limited to 'scripts') diff --git a/README b/README index ef5a13934..e4cae0e49 100644 --- a/README +++ b/README @@ -1228,6 +1228,22 @@ enabled: Set to true to enable. Default false. server: a string with the hostname of the sphinx server. port: an integer with the port number of the sphinx server. +sms +--- + +For SMS integration. + +enabled: Whether to enable SMS integration. Defaults to true. Queues + should also be enabled. + +twitter +------- + +For Twitter integration + +enabled: Whether to enable Twitter integration. Defaults to true. + Queues should also be enabled. + integration ----------- diff --git a/actions/imsettings.php b/actions/imsettings.php index e0f5ede3a..70a6f37d4 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -84,6 +84,12 @@ class ImsettingsAction extends ConnectSettingsAction function showContent() { + if (!common_config('xmpp', 'enabled')) { + $this->element('div', array('class' => 'error'), + _('IM is not available.')); + return; + } + $user = common_current_user(); $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_im', diff --git a/actions/smssettings.php b/actions/smssettings.php index 922bab9a4..33b54abf6 100644 --- a/actions/smssettings.php +++ b/actions/smssettings.php @@ -80,6 +80,12 @@ class SmssettingsAction extends ConnectSettingsAction function showContent() { + if (!common_config('sms', 'enabled')) { + $this->element('div', array('class' => 'error'), + _('SMS is not available.')); + return; + } + $user = common_current_user(); $this->elementStart('form', array('method' => 'post', diff --git a/actions/twittersettings.php b/actions/twittersettings.php index 2b742788e..3343dba95 100644 --- a/actions/twittersettings.php +++ b/actions/twittersettings.php @@ -85,6 +85,12 @@ class TwittersettingsAction extends ConnectSettingsAction function showContent() { + if (!common_config('twitter', 'enabled')) { + $this->element('div', array('class' => 'error'), + _('Twitter is not available.')); + return; + } + $user = common_current_user(); $profile = $user->getProfile(); diff --git a/config.php.sample b/config.php.sample index c27645ff8..91be39f48 100644 --- a/config.php.sample +++ b/config.php.sample @@ -164,6 +164,12 @@ $config['sphinx']['port'] = 3312; // $config['memcached']['server'] = 'localhost'; // $config['memcached']['port'] = 11211; +// Disable SMS +// $config['sms']['enabled'] = false; + +// Disable Twitter integration +// $config['twitter']['enabled'] = false; + // Twitter integration source attribute. Note: default is Laconica // $config['integration']['source'] = 'Laconica'; diff --git a/lib/action.php b/lib/action.php index 1c6170693..326edf3a0 100644 --- a/lib/action.php +++ b/lib/action.php @@ -402,6 +402,14 @@ class Action extends HTMLOutputter // lawsuit function showPrimaryNav() { $user = common_current_user(); + $connect = ''; + if (common_config('xmpp', 'enabled')) { + $connect = 'imsettings'; + } else if (common_config('sms', 'enabled')) { + $connect = 'smssettings'; + } else if (common_config('twitter', 'enabled')) { + $connect = 'twittersettings'; + } $this->elementStart('dl', array('id' => 'site_nav_global_primary')); $this->element('dt', null, _('Primary site navigation')); @@ -413,12 +421,9 @@ class Action extends HTMLOutputter // lawsuit _('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); $this->menuItem(common_local_url('profilesettings'), _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account'); - if (common_config('xmpp', 'enabled')) { - $this->menuItem(common_local_url('imsettings'), - _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect'); - } else { - $this->menuItem(common_local_url('smssettings'), - _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect'); + if ($connect) { + $this->menuItem(common_local_url($connect), + _('Connect'), _('Connect to services'), false, 'nav_connect'); } if (common_config('invite', 'enabled')) { $this->menuItem(common_local_url('invite'), diff --git a/lib/common.php b/lib/common.php index b3d301862..12a7ac449 100644 --- a/lib/common.php +++ b/lib/common.php @@ -183,6 +183,10 @@ $config = array('piddir' => '/var/run', 'user' => false, 'group' => false), + 'sms' => + array('enabled' => false), + 'twitter' => + array('enabled' => false), 'twitterbridge' => array('enabled' => false), 'integration' => diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php index 30629680e..02c468a35 100644 --- a/lib/connectsettingsaction.php +++ b/lib/connectsettingsaction.php @@ -99,25 +99,27 @@ class ConnectSettingsNav extends Widget function show() { # action => array('prompt', 'title') - $menu = - array('imsettings' => - array(_('IM'), - _('Updates by instant messenger (IM)')), - 'smssettings' => - array(_('SMS'), - _('Updates by SMS')), - 'twittersettings' => - array(_('Twitter'), - _('Twitter integration options'))); + $menu = array(); + if (common_config('xmpp', 'enabled')) { + $menu['imsettings'] = + array(_('IM'), + _('Updates by instant messenger (IM)')); + } + if (common_config('sms', 'enabled')) { + $menu['smssettings'] = + array(_('SMS'), + _('Updates by SMS')); + } + if (common_config('twitter', 'enabled')) { + $menu['twittersettings'] = + array(_('Twitter'), + _('Twitter integration options')); + } $action_name = $this->action->trimmed('action'); $this->action->elementStart('ul', array('class' => 'nav')); foreach ($menu as $menuaction => $menudesc) { - if ($menuaction == 'imsettings' && - !common_config('xmpp', 'enabled')) { - continue; - } $this->action->menuItem(common_local_url($menuaction), $menudesc[0], $menudesc[1], diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index 1e4546dff..8b127bd20 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -43,7 +43,11 @@ if(common_config('twitterbridge','enabled')) { echo "twitterstatusfetcher.php "; } echo "ombqueuehandler.php "; -echo "twitterqueuehandler.php "; +if (common_config('twitter', 'enabled')) { + echo "twitterqueuehandler.php "; +} echo "facebookqueuehandler.php "; echo "pingqueuehandler.php "; -echo "smsqueuehandler.php "; +if (common_config('sms', 'enabled')) { + echo "smsqueuehandler.php "; +} -- cgit v1.2.3-54-g00ecf From 5f293f0e2fd0561caa940c9799fad623ce953a60 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Fri, 7 Aug 2009 01:55:31 +0800 Subject: Added configuration option to disable post-by-email. This hides the relevant settings from the email settings page and prevents maildaemon.php from processing email if the option is disabled. --- README | 8 ++++++++ actions/emailsettings.php | 14 ++++++++------ config.php.sample | 3 +++ lib/common.php | 2 ++ scripts/maildaemon.php | 6 ++++-- 5 files changed, 25 insertions(+), 8 deletions(-) (limited to 'scripts') diff --git a/README b/README index e4cae0e49..12c465869 100644 --- a/README +++ b/README @@ -1228,6 +1228,14 @@ enabled: Set to true to enable. Default false. server: a string with the hostname of the sphinx server. port: an integer with the port number of the sphinx server. +emailpost +--------- + +For post-by-email. + +enabled: Whether to enable post-by-email. Defaults to true. You will + also need to set up maildaemon.php. + sms --- diff --git a/actions/emailsettings.php b/actions/emailsettings.php index 634388fdd..cdd092829 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -122,7 +122,7 @@ class EmailsettingsAction extends AccountSettingsAction } $this->elementEnd('fieldset'); - if ($user->email) { + if (common_config('emailpost', 'enabled') && $user->email) { $this->elementStart('fieldset', array('id' => 'settings_email_incoming')); $this->element('legend',_('Incoming email')); if ($user->incomingemail) { @@ -173,11 +173,13 @@ class EmailsettingsAction extends AccountSettingsAction _('Allow friends to nudge me and send me an email.'), $user->emailnotifynudge); $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('emailpost', - _('I want to post notices by email.'), - $user->emailpost); - $this->elementEnd('li'); + if (common_config('emailpost', 'enabled')) { + $this->elementStart('li'); + $this->checkbox('emailpost', + _('I want to post notices by email.'), + $user->emailpost); + $this->elementEnd('li'); + } $this->elementStart('li'); $this->checkbox('emailmicroid', _('Publish a MicroID for my email address.'), diff --git a/config.php.sample b/config.php.sample index 91be39f48..21b6865e1 100644 --- a/config.php.sample +++ b/config.php.sample @@ -164,6 +164,9 @@ $config['sphinx']['port'] = 3312; // $config['memcached']['server'] = 'localhost'; // $config['memcached']['port'] = 11211; +// Disable post-by-email +// $config['emailpost']['enabled'] = false; + // Disable SMS // $config['sms']['enabled'] = false; diff --git a/lib/common.php b/lib/common.php index 12a7ac449..6d20534ae 100644 --- a/lib/common.php +++ b/lib/common.php @@ -183,6 +183,8 @@ $config = array('piddir' => '/var/run', 'user' => false, 'group' => false), + 'emailpost' => + array('enabled' => true), 'sms' => array('enabled' => false), 'twitter' => diff --git a/scripts/maildaemon.php b/scripts/maildaemon.php index 3ef4d0638..91c257adb 100755 --- a/scripts/maildaemon.php +++ b/scripts/maildaemon.php @@ -385,5 +385,7 @@ class MailerDaemon } } -$md = new MailerDaemon(); -$md->handle_message('php://stdin'); +if (common_config('emailpost', 'enabled')) { + $md = new MailerDaemon(); + $md->handle_message('php://stdin'); +} -- cgit v1.2.3-54-g00ecf From c03d5932877c15eb673febabda5227df2173fbad Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 6 Aug 2009 22:52:58 +0000 Subject: Make TwitterStatusFetcher extend ParallelizingDaemon --- lib/parallelizingdaemon.php | 12 +- scripts/synctwitterfriends.php | 20 ++- scripts/twitterstatusfetcher.php | 289 ++++++++++++--------------------------- 3 files changed, 110 insertions(+), 211 deletions(-) (limited to 'scripts') diff --git a/lib/parallelizingdaemon.php b/lib/parallelizingdaemon.php index 5ecfd98f3..dc28b5643 100644 --- a/lib/parallelizingdaemon.php +++ b/lib/parallelizingdaemon.php @@ -87,7 +87,7 @@ class ParallelizingDaemon extends Daemon function run() { if (isset($this->_debug)) { - echo $this->name() . " - debugging output enabled.\n"; + echo $this->name() . " - Debugging output enabled.\n"; } do { @@ -107,9 +107,10 @@ class ParallelizingDaemon extends Daemon if ($pid) { // Parent + if (isset($this->_debug)) { echo $this->name() . - " (parent) forked new child - pid $pid.\n"; + " - Forked new child - pid $pid.\n"; } @@ -120,22 +121,25 @@ class ParallelizingDaemon extends Daemon // Child // Do something with each object + $this->childTask($o); exit(); } // Remove child from ps list as it finishes + while (($c = pcntl_wait($status, WNOHANG OR WUNTRACED)) > 0) { if (isset($this->_debug)) { - echo $this->name() . " child $c finished.\n"; + echo $this->name() . " - Child $c finished.\n"; } $this->removePs($this->_children, $c); } // Wait! We have too many damn kids. + if (sizeof($this->_children) >= $this->_max_children) { if (isset($this->_debug)) { @@ -158,7 +162,7 @@ class ParallelizingDaemon extends Daemon while (($c = pcntl_wait($status, WUNTRACED)) > 0) { if (isset($this->_debug)) { - echo $this->name() . " child $c finished.\n"; + echo $this->name() . " - Child $c finished.\n"; } $this->removePs($this->_children, $c); diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php index 1bd75bac1..37f7842a1 100755 --- a/scripts/synctwitterfriends.php +++ b/scripts/synctwitterfriends.php @@ -54,6 +54,18 @@ require_once INSTALLDIR . '/lib/parallelizingdaemon.php'; class SyncTwitterFriendsDaemon extends ParallelizingDaemon { + /** + * Constructor + * + * @param string $id the name/id of this daemon + * @param int $interval sleep this long before doing everything again + * @param int $max_children maximum number of child processes at a time + * @param boolean $debug debug output flag + * + * @return void + * + **/ + function __construct($id = null, $interval = 60, $max_children = 2, $debug = null) { @@ -71,6 +83,12 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon return ('synctwitterfriendsdaemon.' . $this->_id); } + /** + * Find all the Twitter foreign links for users who have requested + * automatically subscribing to their Twitter friends locally. + * + * @return array flinks an array of Foreign_link objects + */ function getObjects() { $flinks = array(); @@ -237,8 +255,6 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon } -declare(ticks = 1); - $id = null; $debug = null; diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php index 67f52a3cc..fa37f894c 100755 --- a/scripts/twitterstatusfetcher.php +++ b/scripts/twitterstatusfetcher.php @@ -56,17 +56,23 @@ require_once INSTALLDIR . '/lib/daemon.php'; // NOTE: an Avatar path MUST be set in config.php for this // script to work: e.g.: $config['avatar']['path'] = '/laconica/avatar'; -class TwitterStatusFetcher extends Daemon +class TwitterStatusFetcher extends ParallelizingDaemon { - private $_children = array(); - - function __construct($id=null, $daemonize=true) + /** + * Constructor + * + * @param string $id the name/id of this daemon + * @param int $interval sleep this long before doing everything again + * @param int $max_children maximum number of child processes at a time + * @param boolean $debug debug output flag + * + * @return void + * + **/ + function __construct($id = null, $interval = 60, + $max_children = 2, $debug = null) { - parent::__construct($daemonize); - - if ($id) { - $this->set_id($id); - } + parent::__construct($id, $interval, $max_children, $debug); } /** @@ -81,123 +87,13 @@ class TwitterStatusFetcher extends Daemon } /** - * Run the daemon - * - * @return void - */ - - function run() - { - if (defined('SCRIPT_DEBUG')) { - common_debug($this->name() . - ': debugging log output enabled.'); - } - - do { - - $flinks = $this->refreshFlinks(); - - foreach ($flinks as $f) { - - $pid = pcntl_fork(); - - if ($pid == -1) { - die ("Couldn't fork!"); - } - - if ($pid) { - - // Parent - if (defined('SCRIPT_DEBUG')) { - common_debug("Parent: forked new status ". - " fetcher process " . $pid); - } - - $this->_children[] = $pid; - - } else { - - // Child - - // Each child ps needs its own DB connection - - // Note: DataObject::getDatabaseConnection() creates - // a new connection if there isn't one already - - global $_DB_DATAOBJECT; - $conn = &$f->getDatabaseConnection(); - - $this->getTimeline($f); - - $conn->disconnect(); - - // XXX: Couldn't find a less brutal way to blow - // away a cached connection - - unset($_DB_DATAOBJECT['CONNECTIONS']); - - exit(); - } - - // Remove child from ps list as it finishes - while (($c = pcntl_wait($status, WNOHANG OR WUNTRACED)) > 0) { - - if (defined('SCRIPT_DEBUG')) { - common_debug("Child $c finished."); - } - - $this->removePs($this->_children, $c); - } - - // Wait! We have too many damn kids. - if (sizeof($this->_children) > MAXCHILDREN) { - - if (defined('SCRIPT_DEBUG')) { - common_debug('Too many children. Waiting...'); - } - - if (($c = pcntl_wait($status, WUNTRACED)) > 0) { - - if (defined('SCRIPT_DEBUG')) { - common_debug("Finished waiting for $c"); - } - - $this->removePs($this->_children, $c); - } - } - } - - // Remove all children from the process list before restarting - while (($c = pcntl_wait($status, WUNTRACED)) > 0) { - - if (defined('SCRIPT_DEBUG')) { - common_debug("Child $c finished."); - } - - $this->removePs($this->_children, $c); - } - - // Rest for a bit before we fetch more statuses - - if (defined('SCRIPT_DEBUG')) { - common_debug('Waiting ' . POLL_INTERVAL . - ' secs before hitting Twitter again.'); - } - - if (POLL_INTERVAL > 0) { - sleep(POLL_INTERVAL); - } - - } while (true); - } - - /** - * Refresh the foreign links for this user + * Find all the Twitter foreign links for users who have requested + * importing of their friends' timelines * - * @return void + * @return array flinks an array of Foreign_link objects */ - function refreshFlinks() + function getObjects() { global $_DB_DATAOBJECT; @@ -205,15 +101,8 @@ class TwitterStatusFetcher extends Daemon $conn = &$flink->getDatabaseConnection(); $flink->service = TWITTER_SERVICE; - $flink->orderBy('last_noticesync'); - - $cnt = $flink->find(); - - if (defined('SCRIPT_DEBUG')) { - common_debug('Updating Twitter friends subscriptions' . - " for $cnt users."); - } + $flink->find(); $flinks = array(); @@ -234,39 +123,39 @@ class TwitterStatusFetcher extends Daemon return $flinks; } - /** - * Unknown - * - * @param array &$plist unknown. - * @param string $ps unknown. - * - * @return unknown - * @todo document - */ + function childTask($flink) { - function removePs(&$plist, $ps) - { - for ($i = 0; $i < sizeof($plist); $i++) { - if ($plist[$i] == $ps) { - unset($plist[$i]); - $plist = array_values($plist); - break; - } - } + // Each child ps needs its own DB connection + + // Note: DataObject::getDatabaseConnection() creates + // a new connection if there isn't one already + + $conn = &$flink->getDatabaseConnection(); + + $this->getTimeline($flink); + + $flink->last_friendsync = common_sql_now(); + $flink->update(); + + $conn->disconnect(); + + // XXX: Couldn't find a less brutal way to blow + // away a cached connection + + global $_DB_DATAOBJECT; + unset($_DB_DATAOBJECT['CONNECTIONS']); } function getTimeline($flink) { if (empty($flink)) { - common_log(LOG_WARNING, - "Can't retrieve Foreign_link for foreign ID $fid"); + common_log(LOG_WARNING, $this->name() . + " - Can't retrieve Foreign_link for foreign ID $fid"); return; } - if (defined('SCRIPT_DEBUG')) { - common_debug('Trying to get timeline for Twitter user ' . - $flink->foreign_id); - } + common_debug($this->name() . ' - Trying to get timeline for Twitter user ' . + $flink->foreign_id); // XXX: Biggest remaining issue - How do we know at which status // to start importing? How many statuses? Right now I'm going @@ -279,28 +168,28 @@ class TwitterStatusFetcher extends Daemon try { $timeline = $client->statuses_friends_timeline(); } catch (OAuthClientCurlException $e) { - common_log(LOG_WARNING, - 'OAuth client unable to get friends timeline for user ' . + common_log(LOG_WARNING, $this->name() . + ' - OAuth client unable to get friends timeline for user ' . $flink->user_id . ' - code: ' . $e->getCode() . 'msg: ' . $e->getMessage()); } if (empty($timeline)) { - common_log(LOG_WARNING, "Empty timeline."); + common_log(LOG_WARNING, $this->name . " - Empty timeline."); return; } // Reverse to preserve order + foreach (array_reverse($timeline) as $status) { // Hacktastic: filter out stuff coming from this Laconica + $source = mb_strtolower(common_config('integration', 'source')); if (preg_match("/$source/", mb_strtolower($status->source))) { - if (defined('SCRIPT_DEBUG')) { - common_debug('Skipping import of status ' . $status->id . - ' with source ' . $source); - } + common_debug($this->name() . ' - Skipping import of status ' . + $status->id . ' with source ' . $source); continue; } @@ -308,6 +197,7 @@ class TwitterStatusFetcher extends Daemon } // Okay, record the time we synced with Twitter for posterity + $flink->last_noticesync = common_sql_now(); $flink->update(); } @@ -319,8 +209,8 @@ class TwitterStatusFetcher extends Daemon $profile = Profile::staticGet($id); if (empty($profile)) { - common_log(LOG_ERR, - 'Problem saving notice. No associated Profile.'); + common_log(LOG_ERR, $this->name() . + ' - Problem saving notice. No associated Profile.'); return null; } @@ -344,7 +234,7 @@ class TwitterStatusFetcher extends Daemon $notice->content = common_shorten_links($status->text); // XXX $notice->rendered = common_render_content($notice->content, $notice); $notice->source = 'twitter'; - $notice->reply_to = null; // XXX lookup reply + $notice->reply_to = null; // XXX: lookup reply $notice->is_local = Notice::GATEWAY; if (Event::handle('StartNoticeSave', array(&$notice))) { @@ -370,24 +260,22 @@ class TwitterStatusFetcher extends Daemon function ensureProfile($user) { // check to see if there's already a profile for this user + $profileurl = 'http://twitter.com/' . $user->screen_name; $profile = Profile::staticGet('profileurl', $profileurl); if (!empty($profile)) { - if (defined('SCRIPT_DEBUG')) { - common_debug("Profile for $profile->nickname found."); - } + common_debug($this->name() . + " - Profile for $profile->nickname found."); // Check to see if the user's Avatar has changed - $this->checkAvatar($user, $profile); + $this->checkAvatar($user, $profile); return $profile->id; } else { - if (defined('SCRIPT_DEBUG')) { - common_debug('Adding profile and remote profile ' . - "for Twitter user: $profileurl"); - } + common_debug($this->name() . ' - Adding profile and remote profile ' . + "for Twitter user: $profileurl."); $profile = new Profile(); $profile->query("BEGIN"); @@ -409,6 +297,7 @@ class TwitterStatusFetcher extends Daemon } // check for remote profile + $remote_pro = Remote_profile::staticGet('uri', $profileurl); if (empty($remote_pro)) { @@ -448,23 +337,18 @@ class TwitterStatusFetcher extends Daemon $oldname = $profile->getAvatar(48)->filename; if ($newname != $oldname) { - - if (defined('SCRIPT_DEBUG')) { - common_debug('Avatar for Twitter user ' . - "$profile->nickname has changed."); - common_debug("old: $oldname new: $newname"); - } + common_debug($this->name() . ' - Avatar for Twitter user ' . + "$profile->nickname has changed."); + common_debug($this->name() . " - old: $oldname new: $newname"); $this->updateAvatars($twitter_user, $profile); } if ($this->missingAvatarFile($profile)) { - - if (defined('SCRIPT_DEBUG')) { - common_debug('Twitter user ' . $profile->nickname . - ' is missing one or more local avatars.'); - common_debug("old: $oldname new: $newname"); - } + common_debug($this->name() . ' - Twitter user ' . + $profile->nickname . + ' is missing one or more local avatars.'); + common_debug($this->name() ." - old: $oldname new: $newname"); $this->updateAvatars($twitter_user, $profile); } @@ -544,23 +428,20 @@ class TwitterStatusFetcher extends Daemon if ($this->fetchAvatar($url, $filename)) { $this->newAvatar($id, $size, $mediatype, $filename); } else { - common_log(LOG_WARNING, "Problem fetching Avatar: $url", __FILE__); + common_log(LOG_WARNING, $this->id() . + " - Problem fetching Avatar: $url"); } } } function updateAvatar($profile_id, $size, $mediatype, $filename) { - if (defined('SCRIPT_DEBUG')) { - common_debug("Updating avatar: $size"); - } + common_debug($this->name() . " - Updating avatar: $size"); $profile = Profile::staticGet($profile_id); if (empty($profile)) { - if (defined('SCRIPT_DEBUG')) { - common_debug("Couldn't get profile: $profile_id!"); - } + common_debug($this->name() . " - Couldn't get profile: $profile_id!"); return; } @@ -568,6 +449,7 @@ class TwitterStatusFetcher extends Daemon $avatar = $profile->getAvatar($sizes[$size]); // Delete the avatar, if present + if ($avatar) { $avatar->delete(); } @@ -605,9 +487,7 @@ class TwitterStatusFetcher extends Daemon $avatar->filename = $filename; $avatar->url = Avatar::url($filename); - if (defined('SCRIPT_DEBUG')) { - common_debug("new filename: $avatar->url"); - } + common_debug($this->name() . " - New filename: $avatar->url"); $avatar->created = common_sql_now(); @@ -618,9 +498,8 @@ class TwitterStatusFetcher extends Daemon return null; } - if (defined('SCRIPT_DEBUG')) { - common_debug("Saved new $size avatar for $profile_id."); - } + common_debug($this->name() . + " - Saved new $size avatar for $profile_id."); return $id; } @@ -633,13 +512,12 @@ class TwitterStatusFetcher extends Daemon $out = fopen($avatarfile, 'wb'); if (!$out) { - common_log(LOG_WARNING, "Couldn't open file $filename", __FILE__); + common_log(LOG_WARNING, $this->name() . + " - Couldn't open file $filename"); return false; } - if (defined('SCRIPT_DEBUG')) { - common_debug("Fetching avatar: $url"); - } + common_debug($this->name() . " - Fetching Twitter avatar: $url"); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); @@ -656,7 +534,8 @@ class TwitterStatusFetcher extends Daemon } } -declare(ticks = 1); +$id = null; +$debug = null; if (have_option('i')) { $id = get_option_value('i'); @@ -669,9 +548,9 @@ if (have_option('i')) { } if (have_option('d') || have_option('debug')) { - define('SCRIPT_DEBUG', true); + $debug = true; } -$fetcher = new TwitterStatusFetcher($id); +$fetcher = new TwitterStatusFetcher($id, 60, 2, $debug); $fetcher->runOnce(); -- cgit v1.2.3-54-g00ecf From fa8433308f5ba1209ec490d6c0835d28da18eacb Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 10 Aug 2009 06:05:43 +0000 Subject: Moved some stuff around. More comments and phpcs compliance. --- actions/twitterauthorization.php | 10 +-- lib/oauthclient.php | 144 ++++++++++++++++++++++++++++++++++---- lib/twitter.php | 2 +- lib/twitteroauthclient.php | 146 +++++++++++++++++++++++++++++++++------ scripts/synctwitterfriends.php | 4 +- scripts/twitterstatusfetcher.php | 4 +- 6 files changed, 264 insertions(+), 46 deletions(-) (limited to 'scripts') diff --git a/actions/twitterauthorization.php b/actions/twitterauthorization.php index cf27d69cf..c40f27164 100644 --- a/actions/twitterauthorization.php +++ b/actions/twitterauthorization.php @@ -48,7 +48,6 @@ if (!defined('LACONICA')) { */ class TwitterauthorizationAction extends Action { - /** * Initialize class members. Looks for 'oauth_token' parameter. * @@ -114,12 +113,13 @@ class TwitterauthorizationAction extends Action // Get a new request token and authorize it $client = new TwitterOAuthClient(); - $req_tok = $client->getRequestToken(); + $req_tok = + $client->getRequestToken(TwitterOAuthClient::$requestTokenURL); // Sock the request token away in the session temporarily $_SESSION['twitter_request_token'] = $req_tok->key; - $_SESSION['twitter_request_token_secret'] = $req_tok->key; + $_SESSION['twitter_request_token_secret'] = $req_tok->secret; $auth_link = $client->getAuthorizeLink($req_tok); @@ -155,12 +155,12 @@ class TwitterauthorizationAction extends Action // Exchange the request token for an access token - $atok = $client->getAccessToken(); + $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL); // Test the access token and get the user's Twitter info $client = new TwitterOAuthClient($atok->key, $atok->secret); - $twitter_user = $client->verify_credentials(); + $twitter_user = $client->verifyCredentials(); } catch (OAuthClientException $e) { $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s', diff --git a/lib/oauthclient.php b/lib/oauthclient.php index 11de991c8..878e47091 100644 --- a/lib/oauthclient.php +++ b/lib/oauthclient.php @@ -1,54 +1,154 @@ . + * + * @category Action + * @package Laconica + * @author Zach Copley + * @copyright 2008 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} -require_once('OAuth.php'); - -class OAuthClientCurlException extends Exception { } +require_once 'OAuth.php'; + +/** + * Exception wrapper for cURL errors + * + * @category Integration + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * + */ +class OAuthClientCurlException extends Exception +{ +} +/** + * Base class for doing OAuth calls as a consumer + * + * @category Integration + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * + */ class OAuthClient { var $consumer; var $token; + /** + * Constructor + * + * Can be initialized with just consumer key and secret for requesting new + * tokens or with additional request token or access token + * + * @param string $consumer_key consumer key + * @param string $consumer_secret consumer secret + * @param string $oauth_token user's token + * @param string $oauth_token_secret user's secret + * + * @return nothing + */ function __construct($consumer_key, $consumer_secret, $oauth_token = null, $oauth_token_secret = null) { $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); - $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); - $this->token = null; + $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); + $this->token = null; if (isset($oauth_token) && isset($oauth_token_secret)) { $this->token = new OAuthToken($oauth_token, $oauth_token_secret); } } - function getRequestToken() + /** + * Gets a request token from the given url + * + * @param string $url OAuth endpoint for grabbing request tokens + * + * @return OAuthToken $token the request token + */ + function getRequestToken($url) { - $response = $this->oAuthGet(TwitterOAuthClient::$requestTokenURL); + $response = $this->oAuthGet($url); parse_str($response); $token = new OAuthToken($oauth_token, $oauth_token_secret); return $token; } - function getAuthorizeLink($request_token, $oauth_callback = null) + /** + * Builds a link that can be redirected to in order to + * authorize a request token. + * + * @param string $url endpoint for authorizing request tokens + * @param OAuthToken $request_token the request token to be authorized + * @param string $oauth_callback optional callback url + * + * @return string $authorize_url the url to redirect to + */ + function getAuthorizeLink($url, $request_token, $oauth_callback = null) { - $url = TwitterOAuthClient::$authorizeURL . '?oauth_token=' . + $authorize_url = $url . '?oauth_token=' . $request_token->key; if (isset($oauth_callback)) { - $url .= '&oauth_callback=' . urlencode($oauth_callback); + $authorize_url .= '&oauth_callback=' . urlencode($oauth_callback); } - return $url; + common_debug("$authorize_url"); + + return $authorize_url; } - function getAccessToken() + /** + * Fetches an access token + * + * @param string $url OAuth endpoint for exchanging authorized request tokens + * for access tokens + * + * @return OAuthToken $token the access token + */ + function getAccessToken($url) { - $response = $this->oAuthPost(TwitterOAuthClient::$accessTokenURL); + $response = $this->oAuthPost($url); parse_str($response); $token = new OAuthToken($oauth_token, $oauth_token_secret); return $token; } + /** + * Use HTTP GET to make a signed OAuth request + * + * @param string $url OAuth endpoint + * + * @return mixed the request + */ function oAuthGet($url) { $request = OAuthRequest::from_consumer_and_token($this->consumer, @@ -59,6 +159,14 @@ class OAuthClient return $this->httpRequest($request->to_url()); } + /** + * Use HTTP POST to make a signed OAuth request + * + * @param string $url OAuth endpoint + * @param array $params additional post parameters + * + * @return mixed the request + */ function oAuthPost($url, $params = null) { $request = OAuthRequest::from_consumer_and_token($this->consumer, @@ -70,6 +178,14 @@ class OAuthClient $request->to_postdata()); } + /** + * Make a HTTP request using cURL. + * + * @param string $url Where to make the + * @param array $params post parameters + * + * @return mixed the request + */ function httpRequest($url, $params = null) { $options = array( @@ -89,7 +205,7 @@ class OAuthClient ); if (isset($params)) { - $options[CURLOPT_POST] = true; + $options[CURLOPT_POST] = true; $options[CURLOPT_POSTFIELDS] = $params; } diff --git a/lib/twitter.php b/lib/twitter.php index 345516997..4e2f67c66 100644 --- a/lib/twitter.php +++ b/lib/twitter.php @@ -165,7 +165,7 @@ function broadcast_twitter($notice) $status = null; try { - $status = $client->statuses_update($statustxt); + $status = $client->statusesUpdate($statustxt); } catch (OAuthClientCurlException $e) { if ($e->getMessage() == 'The requested URL returned error: 401') { diff --git a/lib/twitteroauthclient.php b/lib/twitteroauthclient.php index 2636a3833..c798ac877 100644 --- a/lib/twitteroauthclient.php +++ b/lib/twitteroauthclient.php @@ -1,11 +1,60 @@ . + * + * @category Integration + * @package Laconica + * @author Zach Copley + * @copyright 2008 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} +/** + * Class for talking to the Twitter API with OAuth. + * + * @category Integration + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * + */ class TwitterOAuthClient extends OAuthClient { public static $requestTokenURL = 'https://twitter.com/oauth/request_token'; public static $authorizeURL = 'https://twitter.com/oauth/authorize'; public static $accessTokenURL = 'https://twitter.com/oauth/access_token'; + /** + * Constructor + * + * @param string $oauth_token the user's token + * @param string $oauth_token_secret the user's token secret + * + * @return nothing + */ function __construct($oauth_token = null, $oauth_token_secret = null) { $consumer_key = common_config('twitter', 'consumer_key'); @@ -15,39 +64,72 @@ class TwitterOAuthClient extends OAuthClient $oauth_token, $oauth_token_secret); } - function getAuthorizeLink($request_token) { - return parent::getAuthorizeLink($request_token, + /** + * Builds a link to Twitter's endpoint for authorizing a request token + * + * @param OAuthToken $request_token token to authorize + * + * @return the link + */ + function getAuthorizeLink($request_token) + { + return parent::getAuthorizeLink(self::$authorizeURL, + $request_token, common_local_url('twitterauthorization')); - } - function verify_credentials() + /** + * Calls Twitter's /account/verify_credentials API method + * + * @return mixed the Twitter user + */ + function verifyCredentials() { - $url = 'https://twitter.com/account/verify_credentials.json'; - $response = $this->oAuthGet($url); + $url = 'https://twitter.com/account/verify_credentials.json'; + $response = $this->oAuthGet($url); $twitter_user = json_decode($response); return $twitter_user; } - function statuses_update($status, $in_reply_to_status_id = null) + /** + * Calls Twitter's /stutuses/update API method + * + * @param string $status text of the status + * @param int $in_reply_to_status_id optional id of the status it's + * a reply to + * + * @return mixed the status + */ + function statusesUpdate($status, $in_reply_to_status_id = null) { - $url = 'https://twitter.com/statuses/update.json'; - $params = array('status' => $status, + $url = 'https://twitter.com/statuses/update.json'; + $params = array('status' => $status, 'in_reply_to_status_id' => $in_reply_to_status_id); $response = $this->oAuthPost($url, $params); - $status = json_decode($response); + $status = json_decode($response); return $status; } - function statuses_friends_timeline($since_id = null, $max_id = null, - $cnt = null, $page = null) { + /** + * Calls Twitter's /stutuses/friends_timeline API method + * + * @param int $since_id show statuses after this id + * @param int $max_id show statuses before this id + * @param int $cnt number of statuses to show + * @param int $page page number + * + * @return mixed an array of statuses + */ + function statusesFriendsTimeline($since_id = null, $max_id = null, + $cnt = null, $page = null) + { - $url = 'https://twitter.com/statuses/friends_timeline.json'; + $url = 'https://twitter.com/statuses/friends_timeline.json'; $params = array('since_id' => $since_id, 'max_id' => $max_id, 'count' => $cnt, 'page' => $page); - $qry = http_build_query($params); + $qry = http_build_query($params); if (!empty($qry)) { $url .= "?$qry"; @@ -58,8 +140,18 @@ class TwitterOAuthClient extends OAuthClient return $statuses; } - function statuses_friends($id = null, $user_id = null, $screen_name = null, - $page = null) + /** + * Calls Twitter's /stutuses/friends API method + * + * @param int $id id of the user whom you wish to see friends of + * @param int $user_id numerical user id + * @param int $screen_name screen name + * @param int $page page number + * + * @return mixed an array of twitter users and their latest status + */ + function statusesFriends($id = null, $user_id = null, $screen_name = null, + $page = null) { $url = "https://twitter.com/statuses/friends.json"; @@ -67,18 +159,28 @@ class TwitterOAuthClient extends OAuthClient 'user_id' => $user_id, 'screen_name' => $screen_name, 'page' => $page); - $qry = http_build_query($params); + $qry = http_build_query($params); if (!empty($qry)) { $url .= "?$qry"; } $response = $this->oAuthGet($url); - $ids = json_decode($response); - return $ids; + $friends = json_decode($response); + return $friends; } - function friends_ids($id = null, $user_id = null, $screen_name = null, + /** + * Calls Twitter's /stutuses/friends/ids API method + * + * @param int $id id of the user whom you wish to see friends of + * @param int $user_id numerical user id + * @param int $screen_name screen name + * @param int $page page number + * + * @return mixed a list of ids, 100 per page + */ + function friendsIds($id = null, $user_id = null, $screen_name = null, $page = null) { $url = "https://twitter.com/friends/ids.json"; @@ -87,14 +189,14 @@ class TwitterOAuthClient extends OAuthClient 'user_id' => $user_id, 'screen_name' => $screen_name, 'page' => $page); - $qry = http_build_query($params); + $qry = http_build_query($params); if (!empty($qry)) { $url .= "?$qry"; } $response = $this->oAuthGet($url); - $ids = json_decode($response); + $ids = json_decode($response); return $ids; } diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php index 37f7842a1..39b9ad612 100755 --- a/scripts/synctwitterfriends.php +++ b/scripts/synctwitterfriends.php @@ -145,7 +145,7 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon $client = new TwitterOAuthClient($flink->token, $flink->credentials); try { - $friends_ids = $client->friends_ids(); + $friends_ids = $client->friendsIds(); } catch (OAuthCurlException $e) { common_log(LOG_WARNING, $this->name() . ' - cURL error getting friend ids ' . @@ -174,7 +174,7 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon for ($i = 1; $i <= $pages; $i++) { try { - $more_friends = $client->statuses_friends(null, null, null, $i); + $more_friends = $client->statusesFriends(null, null, null, $i); } catch (OAuthCurlException $e) { common_log(LOG_WARNING, $this->name() . ' - cURL error getting Twitter statuses/friends ' . diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php index fa37f894c..e04a8993f 100755 --- a/scripts/twitterstatusfetcher.php +++ b/scripts/twitterstatusfetcher.php @@ -166,7 +166,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon $timeline = null; try { - $timeline = $client->statuses_friends_timeline(); + $timeline = $client->statusesFriendsTimeline(); } catch (OAuthClientCurlException $e) { common_log(LOG_WARNING, $this->name() . ' - OAuth client unable to get friends timeline for user ' . @@ -175,7 +175,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon } if (empty($timeline)) { - common_log(LOG_WARNING, $this->name . " - Empty timeline."); + common_log(LOG_WARNING, $this->name() . " - Empty timeline."); return; } -- cgit v1.2.3-54-g00ecf From 27548c690350bdf0376d846a5e8e86477359297d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 10 Aug 2009 07:00:59 +0000 Subject: I forgot that we don't do database upgrades for point releases. So I've changed Twitter OAuth to store token and token secret in the same field in foreign_link (credentials). This should be changed in 0.9. --- actions/twitterauthorization.php | 8 +++++--- lib/oauthclient.php | 2 -- lib/twitter.php | 4 +++- lib/twitteroauthclient.php | 17 +++++++++++++++++ scripts/synctwitterfriends.php | 4 +++- scripts/twitterstatusfetcher.php | 4 +++- 6 files changed, 31 insertions(+), 8 deletions(-) (limited to 'scripts') diff --git a/actions/twitterauthorization.php b/actions/twitterauthorization.php index c40f27164..b04f35327 100644 --- a/actions/twitterauthorization.php +++ b/actions/twitterauthorization.php @@ -113,7 +113,7 @@ class TwitterauthorizationAction extends Action // Get a new request token and authorize it $client = new TwitterOAuthClient(); - $req_tok = + $req_tok = $client->getRequestToken(TwitterOAuthClient::$requestTokenURL); // Sock the request token away in the session temporarily @@ -198,8 +198,10 @@ class TwitterauthorizationAction extends Action $flink->user_id = $user->id; $flink->foreign_id = $twitter_user->id; $flink->service = TWITTER_SERVICE; - $flink->token = $access_token->key; - $flink->credentials = $access_token->secret; + + $creds = TwitterOAuthClient::packToken($access_token); + + $flink->credentials = $creds; $flink->created = common_sql_now(); // Defaults: noticesync on, everything else off diff --git a/lib/oauthclient.php b/lib/oauthclient.php index 878e47091..b66a24be4 100644 --- a/lib/oauthclient.php +++ b/lib/oauthclient.php @@ -121,8 +121,6 @@ class OAuthClient $authorize_url .= '&oauth_callback=' . urlencode($oauth_callback); } - common_debug("$authorize_url"); - return $authorize_url; } diff --git a/lib/twitter.php b/lib/twitter.php index 4e2f67c66..280cdb0a3 100644 --- a/lib/twitter.php +++ b/lib/twitter.php @@ -160,7 +160,9 @@ function broadcast_twitter($notice) // XXX: Hack to get around PHP cURL's use of @ being a a meta character $statustxt = preg_replace('/^@/', ' @', $notice->content); - $client = new TwitterOAuthClient($flink->token, $flink->credentials); + $token = TwitterOAuthClient::unpackToken($flink->credentials); + + $client = new TwitterOAuthClient($token->key, $token->secret); $status = null; diff --git a/lib/twitteroauthclient.php b/lib/twitteroauthclient.php index c798ac877..b7dc4a80c 100644 --- a/lib/twitteroauthclient.php +++ b/lib/twitteroauthclient.php @@ -64,6 +64,23 @@ class TwitterOAuthClient extends OAuthClient $oauth_token, $oauth_token_secret); } + // XXX: the following two functions are to support the horrible hack + // of using the credentils field in Foreign_link to store both + // the access token and token secret. This hack should go away with + // 0.9, in which we can make DB changes and add a new column for the + // token itself. + + static function packToken($token) + { + return implode(chr(0), array($token->key, $token->secret)); + } + + static function unpackToken($str) + { + $vals = explode(chr(0), $str); + return new OAuthToken($vals[0], $vals[1]); + } + /** * Builds a link to Twitter's endpoint for authorizing a request token * diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php index 39b9ad612..d13500e97 100755 --- a/scripts/synctwitterfriends.php +++ b/scripts/synctwitterfriends.php @@ -142,7 +142,9 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon { $friends = array(); - $client = new TwitterOAuthClient($flink->token, $flink->credentials); + $token = TwitterOAuthClient::unpackToken($flink->credentials); + + $client = new TwitterOAuthClient($token->key, $token->secret); try { $friends_ids = $client->friendsIds(); diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php index e04a8993f..f5289c5f4 100755 --- a/scripts/twitterstatusfetcher.php +++ b/scripts/twitterstatusfetcher.php @@ -161,7 +161,9 @@ class TwitterStatusFetcher extends ParallelizingDaemon // to start importing? How many statuses? Right now I'm going // with the default last 20. - $client = new TwitterOAuthClient($flink->token, $flink->credentials); + $token = TwitterOAuthClient::unpackToken($flink->credentials); + + $client = new TwitterOAuthClient($token->key, $token->secret); $timeline = null; -- cgit v1.2.3-54-g00ecf From 681bcbf71efa8514b1299abcf7d9162069b99054 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 10 Aug 2009 07:18:09 +0000 Subject: Add synctwitterfriends to daemon startup and stop scripts --- scripts/getvaliddaemons.php | 1 + scripts/stopdaemons.sh | 2 +- scripts/synctwitterfriends.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) (limited to 'scripts') diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index 1e4546dff..9a2b83329 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -44,6 +44,7 @@ if(common_config('twitterbridge','enabled')) { } echo "ombqueuehandler.php "; echo "twitterqueuehandler.php "; +echo "synctwitterfriends.php "; echo "facebookqueuehandler.php "; echo "pingqueuehandler.php "; echo "smsqueuehandler.php "; diff --git a/scripts/stopdaemons.sh b/scripts/stopdaemons.sh index 60ffd83ad..894e5aaff 100755 --- a/scripts/stopdaemons.sh +++ b/scripts/stopdaemons.sh @@ -25,7 +25,7 @@ DIR=`php $SDIR/getpiddir.php` for f in jabberhandler ombhandler publichandler smshandler pinghandler \ xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \ - twitterstatusfetcher; do + twitterstatusfetcher synctwitterfriends; do FILES="$DIR/$f.*.pid" for ff in "$FILES" ; do diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php index d13500e97..2de464bcc 100755 --- a/scripts/synctwitterfriends.php +++ b/scripts/synctwitterfriends.php @@ -80,7 +80,7 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon function name() { - return ('synctwitterfriendsdaemon.' . $this->_id); + return ('synctwitterfriends.' . $this->_id); } /** -- cgit v1.2.3-54-g00ecf From ec88d2650ea4371cf53229171851747b31587e4b Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Mon, 10 Aug 2009 14:48:50 +0200 Subject: Replace own OMB stack with libomb. --- actions/accesstoken.php | 28 +- actions/finishremotesubscribe.php | 316 +++++-------------- actions/postnotice.php | 91 +++--- actions/remotesubscribe.php | 323 ++++--------------- actions/requesttoken.php | 21 +- actions/updateprofile.php | 185 ++--------- actions/userauthorization.php | 406 ++++++------------------ actions/xrds.php | 104 ++----- extlib/libomb/base_url_xrds_mapper.php | 51 +++ extlib/libomb/constants.php | 58 ++++ extlib/libomb/datastore.php | 198 ++++++++++++ extlib/libomb/helper.php | 99 ++++++ extlib/libomb/invalidparameterexception.php | 32 ++ extlib/libomb/invalidyadisexception.php | 31 ++ extlib/libomb/notice.php | 272 ++++++++++++++++ extlib/libomb/omb_yadis_xrds.php | 196 ++++++++++++ extlib/libomb/plain_xrds_writer.php | 124 ++++++++ extlib/libomb/profile.php | 317 +++++++++++++++++++ extlib/libomb/remoteserviceexception.php | 42 +++ extlib/libomb/service_consumer.php | 430 ++++++++++++++++++++++++++ extlib/libomb/service_provider.php | 411 ++++++++++++++++++++++++ extlib/libomb/unsupportedserviceexception.php | 31 ++ extlib/libomb/xrds_mapper.php | 33 ++ extlib/libomb/xrds_writer.php | 33 ++ lib/oauthstore.php | 357 ++++++++++++++++++++- lib/omb.php | 335 ++++++++------------ lib/unqueuemanager.php | 2 +- scripts/ombqueuehandler.php | 2 +- 28 files changed, 3201 insertions(+), 1327 deletions(-) create mode 100755 extlib/libomb/base_url_xrds_mapper.php create mode 100644 extlib/libomb/constants.php create mode 100755 extlib/libomb/datastore.php create mode 100644 extlib/libomb/helper.php create mode 100755 extlib/libomb/invalidparameterexception.php create mode 100755 extlib/libomb/invalidyadisexception.php create mode 100755 extlib/libomb/notice.php create mode 100755 extlib/libomb/omb_yadis_xrds.php create mode 100755 extlib/libomb/plain_xrds_writer.php create mode 100755 extlib/libomb/profile.php create mode 100755 extlib/libomb/remoteserviceexception.php create mode 100755 extlib/libomb/service_consumer.php create mode 100755 extlib/libomb/service_provider.php create mode 100755 extlib/libomb/unsupportedserviceexception.php create mode 100755 extlib/libomb/xrds_mapper.php create mode 100755 extlib/libomb/xrds_writer.php (limited to 'scripts') diff --git a/actions/accesstoken.php b/actions/accesstoken.php index 2a8cd1713..dcd04a1b4 100644 --- a/actions/accesstoken.php +++ b/actions/accesstoken.php @@ -1,6 +1,6 @@ fetch_access_token($req); - common_debug('got this token: "'.print_r($token, true).'"', __FILE__); - common_debug('printing the access token', __FILE__); - print $token; - } catch (OAuthException $e) { + $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), + omb_oauth_server()); + $srv->writeAccessToken(); + } catch (Exception $e) { $this->serverError($e->getMessage()); } } } +?> diff --git a/actions/finishremotesubscribe.php b/actions/finishremotesubscribe.php index 5c764aeb0..13f367823 100644 --- a/actions/finishremotesubscribe.php +++ b/actions/finishremotesubscribe.php @@ -1,5 +1,16 @@ + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -15,285 +26,116 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - */ + **/ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} -require_once(INSTALLDIR.'/lib/omb.php'); +require_once INSTALLDIR.'/extlib/libomb/service_consumer.php'; +require_once INSTALLDIR.'/lib/omb.php'; +/** + * Handler for remote subscription finish callback + * + * When a remote user subscribes a local user, a redirect to this action is + * issued after the remote user authorized his service to subscribe. + * + * @category Action + * @package Laconica + * @author Evan Prodromou + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ class FinishremotesubscribeAction extends Action { + /** + * Class handler. + * + * @param array $args query arguments + * + * @return nothing + * + **/ function handle($args) { - parent::handle($args); - if (common_logged_in()) { - $this->clientError(_('You can use the local subscription!')); - return; - } - - $omb = $_SESSION['oauth_authorization_request']; + /* Restore session data. RemotesubscribeAction should have stored + this entry. */ + $service = unserialize($_SESSION['oauth_authorization_request']); - if (!$omb) { + if (!$service) { $this->clientError(_('Not expecting this response!')); return; } - common_debug('stored request: '.print_r($omb,true), __FILE__); - - common_remove_magic_from_request(); - $req = OAuthRequest::from_request('POST', common_local_url('finishuserauthorization')); + common_debug('stored request: '. print_r($service, true), __FILE__); - $token = $req->get_parameter('oauth_token'); - - # I think this is the success metric - - if ($token != $omb['token']) { - $this->clientError(_('Not authorized.')); - return; - } - - $version = $req->get_parameter('omb_version'); - - if ($version != OMB_VERSION_01) { - $this->clientError(_('Unknown version of OMB protocol.')); - return; - } - - $nickname = $req->get_parameter('omb_listener_nickname'); - - if (!$nickname) { - $this->clientError(_('No nickname provided by remote server.')); - return; - } - - $profile_url = $req->get_parameter('omb_listener_profile'); - - if (!$profile_url) { - $this->clientError(_('No profile URL returned by server.')); - return; - } - - if (!Validate::uri($profile_url, array('allowed_schemes' => array('http', 'https')))) { - $this->clientError(_('Invalid profile URL returned by server.')); - return; - } - - if ($profile_url == common_local_url('showstream', array('nickname' => $nickname))) { - $this->clientError(_('You can use the local subscription!')); - return; - } - - common_debug('listenee: "'.$omb['listenee'].'"', __FILE__); - - $user = User::staticGet('nickname', $omb['listenee']); + /* Create user objects for both users. Do it early for request + validation. */ + $listenee = $service->getListeneeURI(); + $user = User::staticGet('uri', $listenee); if (!$user) { $this->clientError(_('User being listened to doesn\'t exist.')); return; } - $other = User::staticGet('uri', $omb['listener']); + $other = User::staticGet('uri', $service->getListenerURI()); if ($other) { $this->clientError(_('You can use the local subscription!')); return; } - $fullname = $req->get_parameter('omb_listener_fullname'); - $homepage = $req->get_parameter('omb_listener_homepage'); - $bio = $req->get_parameter('omb_listener_bio'); - $location = $req->get_parameter('omb_listener_location'); - $avatar_url = $req->get_parameter('omb_listener_avatar'); - - list($newtok, $newsecret) = $this->access_token($omb); - - if (!$newtok || !$newsecret) { - $this->clientError(_('Couldn\'t convert request tokens to access tokens.')); - return; - } - - # XXX: possible attack point; subscribe and return someone else's profile URI - - $remote = Remote_profile::staticGet('uri', $omb['listener']); - - if ($remote) { - $exists = true; - $profile = Profile::staticGet($remote->id); - $orig_remote = clone($remote); - $orig_profile = clone($profile); - # XXX: compare current postNotice and updateProfile URLs to the ones - # stored in the DB to avoid (possibly...) above attack - } else { - $exists = false; - $remote = new Remote_profile(); - $remote->uri = $omb['listener']; - $profile = new Profile(); - } - - $profile->nickname = $nickname; - $profile->profileurl = $profile_url; - - if (!is_null($fullname)) { - $profile->fullname = $fullname; - } - if (!is_null($homepage)) { - $profile->homepage = $homepage; - } - if (!is_null($bio)) { - $profile->bio = $bio; - } - if (!is_null($location)) { - $profile->location = $location; - } - - if ($exists) { - $profile->update($orig_profile); - } else { - $profile->created = DB_DataObject_Cast::dateTime(); # current time - $id = $profile->insert(); - if (!$id) { - $this->serverError(_('Error inserting new profile')); - return; - } - $remote->id = $id; - } - - if ($avatar_url) { - if (!$this->add_avatar($profile, $avatar_url)) { - $this->serverError(_('Error inserting avatar')); - return; - } - } - - $remote->postnoticeurl = $omb['post_notice_url']; - $remote->updateprofileurl = $omb['update_profile_url']; - - if ($exists) { - if (!$remote->update($orig_remote)) { - $this->serverError(_('Error updating remote profile')); + /* Perform the handling itself via libomb. */ + try { + $service->finishAuthorization($listenee); + } catch (OAuthException $e) { + if ($e->getMessage() == 'The authorized token does not equal the ' . + 'submitted token.') { + $this->clientError(_('Not authorized.')); return; - } - } else { - $remote->created = DB_DataObject_Cast::dateTime(); # current time - if (!$remote->insert()) { - $this->serverError(_('Error inserting remote profile')); + } else { + $this->clientError(_('Couldn\'t convert request token to ' . + 'access token.')); return; } - } - - if ($user->hasBlocked($profile)) { - $this->clientError(_('That user has blocked you from subscribing.')); + } catch (OMB_RemoteServiceException $e) { + $this->clientError(_('Unknown version of OMB protocol.')); + return; + } catch (Exception $e) { + common_debug('Got exception ' . print_r($e, true), __FILE__); + $this->clientError($e->getMessage()); return; } - $sub = new Subscription(); + /* The service URLs are not accessible from datastore, so setting them + after insertion of the profile. */ + $remote = Remote_profile::staticGet('uri', $service->getListenerURI()); - $sub->subscriber = $remote->id; - $sub->subscribed = $user->id; + $orig_remote = clone($remote); - $sub_exists = false; + $remote->postnoticeurl = + $service->getServiceURI(OMB_ENDPOINT_POSTNOTICE); + $remote->updateprofileurl = + $service->getServiceURI(OMB_ENDPOINT_UPDATEPROFILE); - if ($sub->find(true)) { - $sub_exists = true; - $orig_sub = clone($sub); - } else { - $sub_exists = false; - $sub->created = DB_DataObject_Cast::dateTime(); # current time - } - - $sub->token = $newtok; - $sub->secret = $newsecret; - - if ($sub_exists) { - $result = $sub->update($orig_sub); - } else { - $result = $sub->insert(); - } - - if (!$result) { - common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__); - $this->clientError(_('Couldn\'t insert new subscription.')); - return; + if (!$remote->update($orig_remote)) { + $this->serverError(_('Error updating remote profile')); + return; } - # Notify user, if necessary - - mail_subscribe_notify_profile($user, $profile); - - # Clear the data + /* Clear the session data. */ unset($_SESSION['oauth_authorization_request']); - # If we show subscriptions in reverse chron order, this should - # show up close to the top of the page - + /* If we show subscriptions in reverse chronological order, the new one + should show up close to the top of the page. */ common_redirect(common_local_url('subscribers', array('nickname' => $user->nickname)), 303); } - - function add_avatar($profile, $url) - { - $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); - copy($url, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - return $profile->setOriginal($filename); - } - - function access_token($omb) - { - - common_debug('starting request for access token', __FILE__); - - $con = omb_oauth_consumer(); - $tok = new OAuthToken($omb['token'], $omb['secret']); - - common_debug('using request token "'.$tok.'"', __FILE__); - - $url = $omb['access_token_url']; - - common_debug('using access token url "'.$url.'"', __FILE__); - - # XXX: Is this the right thing to do? Strip off GET params and make them - # POST params? Seems wrong to me. - - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - - $req = OAuthRequest::from_consumer_and_token($con, $tok, "POST", $url, $params); - - $req->set_parameter('omb_version', OMB_VERSION_01); - - # XXX: test to see if endpoint accepts this signature method - - $req->sign_request(omb_hmac_sha1(), $con, $tok); - - # We re-use this tool's fetcher, since it's pretty good - - common_debug('posting to access token url "'.$req->get_normalized_http_url().'"', __FILE__); - common_debug('posting request data "'.$req->to_postdata().'"', __FILE__); - - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); - $result = $fetcher->post($req->get_normalized_http_url(), - $req->to_postdata(), - array('User-Agent: Laconica/' . LACONICA_VERSION)); - - common_debug('got result: "'.print_r($result,true).'"', __FILE__); - - if ($result->status != 200) { - return null; - } - - parse_str($result->body, $return); - - return array($return['oauth_token'], $return['oauth_token_secret']); - } } diff --git a/actions/postnotice.php b/actions/postnotice.php index eb2d63b61..74be47119 100644 --- a/actions/postnotice.php +++ b/actions/postnotice.php @@ -1,5 +1,16 @@ + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -17,75 +28,49 @@ * along with this program. If not, see . */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} -require_once(INSTALLDIR.'/lib/omb.php'); +require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; +/** + * Handler for postnotice action + * + * @category Action + * @package Laconica + * @author Evan Prodromou + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ class PostnoticeAction extends Action { function handle($args) { parent::handle($args); + if (!$this->checkNotice()) { + return; + } try { - common_remove_magic_from_request(); - $req = OAuthRequest::from_request('POST', common_local_url('postnotice')); - # Note: server-to-server function! - $server = omb_oauth_server(); - list($consumer, $token) = $server->verify_request($req); - if ($this->save_notice($req, $consumer, $token)) { - print "omb_version=".OMB_VERSION_01; - } - } catch (OAuthException $e) { + $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), + omb_oauth_server()); + $srv->handlePostNotice(); + } catch (Exception $e) { $this->serverError($e->getMessage()); return; } } - function save_notice(&$req, &$consumer, &$token) + function checkNotice() { - $version = $req->get_parameter('omb_version'); - if ($version != OMB_VERSION_01) { - $this->clientError(_('Unsupported OMB version'), 400); - return false; - } - # First, check to see - $listenee = $req->get_parameter('omb_listenee'); - $remote_profile = Remote_profile::staticGet('uri', $listenee); - if (!$remote_profile) { - $this->clientError(_('Profile unknown'), 403); - return false; - } - $sub = Subscription::staticGet('token', $token->key); - if (!$sub) { - $this->clientError(_('No such subscription'), 403); - return false; - } - $content = $req->get_parameter('omb_notice_content'); - $content_shortened = common_shorten_links($content); - if (mb_strlen($content_shortened) > 140) { + $content = common_shorten_links($_POST['omb_notice_content']); + if (mb_strlen($content) > 140) { $this->clientError(_('Invalid notice content'), 400); return false; } - $notice_uri = $req->get_parameter('omb_notice'); - if (!Validate::uri($notice_uri) && - !common_valid_tag($notice_uri)) { - $this->clientError(_('Invalid notice uri'), 400); - return false; - } - $notice_url = $req->get_parameter('omb_notice_url'); - if ($notice_url && !common_valid_http_url($notice_url)) { - $this->clientError(_('Invalid notice url'), 400); - return false; - } - $notice = Notice::staticGet('uri', $notice_uri); - if (!$notice) { - $notice = Notice::saveNew($remote_profile->id, $content, 'omb', false, null, $notice_uri); - if (is_string($notice)) { - common_server_serror($notice, 500); - return false; - } - common_broadcast_notice($notice, true); - } return true; } } +?> diff --git a/actions/remotesubscribe.php b/actions/remotesubscribe.php index e658f8d37..5122c1172 100644 --- a/actions/remotesubscribe.php +++ b/actions/remotesubscribe.php @@ -1,5 +1,16 @@ + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -15,11 +26,26 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - */ + **/ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_consumer.php'; +require_once INSTALLDIR.'/extlib/libomb/profile.php'; -require_once(INSTALLDIR.'/lib/omb.php'); +/** + * Handler for remote subscription + * + * @category Action + * @package Laconica + * @author Evan Prodromou + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ class RemotesubscribeAction extends Action { @@ -36,7 +62,7 @@ class RemotesubscribeAction extends Action return false; } - $this->nickname = $this->trimmed('nickname'); + $this->nickname = $this->trimmed('nickname'); $this->profile_url = $this->trimmed('profile_url'); return true; @@ -47,7 +73,7 @@ class RemotesubscribeAction extends Action parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - # CSRF protection + /* Use a session token for CSRF protection. */ $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { $this->showForm(_('There was a problem with your session token. '. @@ -90,8 +116,8 @@ class RemotesubscribeAction extends Action function showContent() { - # id = remotesubscribe conflicts with the - # button on profile page + /* The id 'remotesubscribe' conflicts with the + button on profile page. */ $this->elementStart('form', array('id' => 'form_remote_subscribe', 'method' => 'post', 'class' => 'form_settings', @@ -117,13 +143,13 @@ class RemotesubscribeAction extends Action function remoteSubscription() { - $user = $this->getUser(); - - if (!$user) { + if (!$this->nickname) { $this->showForm(_('No such user.')); return; } + $user = User::staticGet('nickname', $this->nickname); + $this->profile_url = $this->trimmed('profile_url'); if (!$this->profile_url) { @@ -131,233 +157,37 @@ class RemotesubscribeAction extends Action return; } - if (!Validate::uri($this->profile_url, array('allowed_schemes' => array('http', 'https')))) { + if (!Validate::uri($this->profile_url, + array('allowed_schemes' => array('http', 'https')))) { $this->showForm(_('Invalid profile URL (bad format)')); return; } - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); - $yadis = Auth_Yadis_Yadis::discover($this->profile_url, $fetcher); - - if (!$yadis || $yadis->failed) { - $this->showForm(_('Not a valid profile URL (no YADIS document).')); - return; - } - - # XXX: a little liberal for sites that accidentally put whitespace before the xml declaration - - $xrds =& Auth_Yadis_XRDS::parseXRDS(trim($yadis->response_text)); - - if (!$xrds) { - $this->showForm(_('Not a valid profile URL (no XRDS defined).')); - return; - } - - $omb = $this->getOmb($xrds); - - if (!$omb) { - $this->showForm(_('Not a valid profile URL (incorrect services).')); - return; - } - - if (omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]) == - common_local_url('requesttoken')) - { - $this->showForm(_('That\'s a local profile! Login to subscribe.')); + try { + $service = new OMB_Service_Consumer($this->profile_url, + common_root_url(), + omb_oauth_datastore()); + } catch (OMB_InvalidYadisException $e) { + $this->showForm(_('Not a valid profile URL (no YADIS document or ' . + 'no or invalid XRDS defined).')); return; } - if (User::staticGet('uri', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]))) { + if ($service->getServiceURI(OAUTH_ENDPOINT_REQUEST) == + common_local_url('requesttoken') || + User::staticGet('uri', $service->getRemoteUserURI())) { $this->showForm(_('That\'s a local profile! Login to subscribe.')); return; } - list($token, $secret) = $this->requestToken($omb); - - if (!$token || !$secret) { + try { + $service->requestToken(); + } catch (OMB_RemoteServiceException $e) { $this->showForm(_('Couldn\'t get a request token.')); return; } - $this->requestAuthorization($user, $omb, $token, $secret); - } - - function getUser() - { - $user = null; - if ($this->nickname) { - $user = User::staticGet('nickname', $this->nickname); - } - return $user; - } - - function getOmb($xrds) - { - static $omb_endpoints = array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE); - static $oauth_endpoints = array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE, - OAUTH_ENDPOINT_ACCESS); - $omb = array(); - - # XXX: the following code could probably be refactored to eliminate dupes - - $oauth_services = omb_get_services($xrds, OAUTH_DISCOVERY); - - if (!$oauth_services) { - return null; - } - - $oauth_service = $oauth_services[0]; - - $oauth_xrd = $this->getXRD($oauth_service, $xrds); - - if (!$oauth_xrd) { - return null; - } - - if (!$this->addServices($oauth_xrd, $oauth_endpoints, $omb)) { - return null; - } - - $omb_services = omb_get_services($xrds, OMB_NAMESPACE); - - if (!$omb_services) { - return null; - } - - $omb_service = $omb_services[0]; - - $omb_xrd = $this->getXRD($omb_service, $xrds); - - if (!$omb_xrd) { - return null; - } - - if (!$this->addServices($omb_xrd, $omb_endpoints, $omb)) { - return null; - } - - # XXX: check that we got all the services we needed - - foreach (array_merge($omb_endpoints, $oauth_endpoints) as $type) { - if (!array_key_exists($type, $omb) || !$omb[$type]) { - return null; - } - } - - if (!omb_local_id($omb[OAUTH_ENDPOINT_REQUEST])) { - return null; - } - - return $omb; - } - - function getXRD($main_service, $main_xrds) - { - $uri = omb_service_uri($main_service); - if (strpos($uri, "#") !== 0) { - # FIXME: more rigorous handling of external service definitions - return null; - } - $id = substr($uri, 1); - $nodes = $main_xrds->allXrdNodes; - $parser = $main_xrds->parser; - foreach ($nodes as $node) { - $attrs = $parser->attributes($node); - if (array_key_exists('xml:id', $attrs) && - $attrs['xml:id'] == $id) { - # XXX: trick the constructor into thinking this is the only node - $bogus_nodes = array($node); - return new Auth_Yadis_XRDS($parser, $bogus_nodes); - } - } - return null; - } - - function addServices($xrd, $types, &$omb) - { - foreach ($types as $type) { - $matches = omb_get_services($xrd, $type); - if ($matches) { - $omb[$type] = $matches[0]; - } else { - # no match for type - return false; - } - } - return true; - } - - function requestToken($omb) - { - $con = omb_oauth_consumer(); - - $url = omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]); - - # XXX: Is this the right thing to do? Strip off GET params and make them - # POST params? Seems wrong to me. - - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - - $req = OAuthRequest::from_consumer_and_token($con, null, "POST", $url, $params); - - $listener = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]); - - if (!$listener) { - return null; - } - - $req->set_parameter('omb_listener', $listener); - $req->set_parameter('omb_version', OMB_VERSION_01); - - # XXX: test to see if endpoint accepts this signature method - - $req->sign_request(omb_hmac_sha1(), $con, null); - - # We re-use this tool's fetcher, since it's pretty good - - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); - - $result = $fetcher->post($req->get_normalized_http_url(), - $req->to_postdata(), - array('User-Agent: Laconica/' . LACONICA_VERSION)); - if ($result->status != 200) { - return null; - } - - parse_str($result->body, $return); - - return array($return['oauth_token'], $return['oauth_token_secret']); - } - - function requestAuthorization($user, $omb, $token, $secret) - { - $con = omb_oauth_consumer(); - $tok = new OAuthToken($token, $secret); - - $url = omb_service_uri($omb[OAUTH_ENDPOINT_AUTHORIZE]); - - # XXX: Is this the right thing to do? Strip off GET params and make them - # POST params? Seems wrong to me. - - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - - $req = OAuthRequest::from_consumer_and_token($con, $tok, 'GET', $url, $params); - - # We send over a ton of information. This lets the other - # server store info about our user, and it lets the current - # user decide if they really want to authorize the subscription. - - $req->set_parameter('omb_version', OMB_VERSION_01); - $req->set_parameter('omb_listener', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST])); - $req->set_parameter('omb_listenee', $user->uri); - $req->set_parameter('omb_listenee_profile', common_profile_url($user->nickname)); - $req->set_parameter('omb_listenee_nickname', $user->nickname); - $req->set_parameter('omb_listenee_license', common_config('license', 'url')); - + /* Create an OMB_Profile from $user. */ $profile = $user->getProfile(); if (!$profile) { common_log_db_error($user, 'SELECT', __FILE__); @@ -365,49 +195,16 @@ class RemotesubscribeAction extends Action return; } - if (!is_null($profile->fullname)) { - $req->set_parameter('omb_listenee_fullname', $profile->fullname); - } - if (!is_null($profile->homepage)) { - $req->set_parameter('omb_listenee_homepage', $profile->homepage); - } - if (!is_null($profile->bio)) { - $req->set_parameter('omb_listenee_bio', $profile->bio); - } - if (!is_null($profile->location)) { - $req->set_parameter('omb_listenee_location', $profile->location); - } - $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - if ($avatar) { - $req->set_parameter('omb_listenee_avatar', $avatar->url); - } - - # XXX: add a nonce to prevent replay attacks - - $req->set_parameter('oauth_callback', common_local_url('finishremotesubscribe')); - - # XXX: test to see if endpoint accepts this signature method - - $req->sign_request(omb_hmac_sha1(), $con, $tok); - - # store all our info here - - $omb['listenee'] = $user->nickname; - $omb['listener'] = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]); - $omb['token'] = $token; - $omb['secret'] = $secret; - # call doesn't work after bounce back so we cache; maybe serialization issue...? - $omb['access_token_url'] = omb_service_uri($omb[OAUTH_ENDPOINT_ACCESS]); - $omb['post_notice_url'] = omb_service_uri($omb[OMB_ENDPOINT_POSTNOTICE]); - $omb['update_profile_url'] = omb_service_uri($omb[OMB_ENDPOINT_UPDATEPROFILE]); + $target_url = $service->requestAuthorization( + profile_to_omb_profile($user->uri, $profile), + common_local_url('finishremotesubscribe')); common_ensure_session(); - $_SESSION['oauth_authorization_request'] = $omb; - - # Redirect to authorization service + $_SESSION['oauth_authorization_request'] = serialize($service); - common_redirect($req->to_url(), 303); - return; + /* Redirect to the remote service for authorization. */ + common_redirect($target_url, 303); } } +?> diff --git a/actions/requesttoken.php b/actions/requesttoken.php index 8d1e3f004..8328962f2 100644 --- a/actions/requesttoken.php +++ b/actions/requesttoken.php @@ -34,6 +34,7 @@ if (!defined('LACONICA')) { } require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; /** * Request token action class. @@ -49,17 +50,17 @@ class RequesttokenAction extends Action { /** * Is read only? - * + * * @return boolean false */ - function isReadOnly($args) + function isReadOnly() { return false; } - + /** * Class handler. - * + * * @param array $args array of arguments * * @return void @@ -68,14 +69,12 @@ class RequesttokenAction extends Action { parent::handle($args); try { - common_remove_magic_from_request(); - $req = OAuthRequest::from_request('POST', common_local_url('requesttoken')); - $server = omb_oauth_server(); - $token = $server->fetch_request_token($req); - print $token; - } catch (OAuthException $e) { + $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), + omb_oauth_server()); + $srv->writeRequestToken(); + } catch (Exception $e) { $this->serverError($e->getMessage()); } } } - +?> diff --git a/actions/updateprofile.php b/actions/updateprofile.php index d8b62fb09..345c28b8d 100644 --- a/actions/updateprofile.php +++ b/actions/updateprofile.php @@ -1,5 +1,16 @@ + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -17,167 +28,37 @@ * along with this program. If not, see . */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} -require_once(INSTALLDIR.'/lib/omb.php'); +require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; +/** + * Handle an updateprofile action + * + * @category Action + * @package Laconica + * @author Evan Prodromou + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ class UpdateprofileAction extends Action { - + function handle($args) { parent::handle($args); try { - common_remove_magic_from_request(); - $req = OAuthRequest::from_request('POST', common_local_url('updateprofile')); - # Note: server-to-server function! - $server = omb_oauth_server(); - list($consumer, $token) = $server->verify_request($req); - if ($this->update_profile($req, $consumer, $token)) { - header('HTTP/1.1 200 OK'); - header('Content-type: text/plain'); - print "omb_version=".OMB_VERSION_01; - } - } catch (OAuthException $e) { + $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), + omb_oauth_server()); + $srv->handleUpdateProfile(); + } catch (Exception $e) { $this->serverError($e->getMessage()); return; } } - - function update_profile($req, $consumer, $token) - { - $version = $req->get_parameter('omb_version'); - if ($version != OMB_VERSION_01) { - $this->clientError(_('Unsupported OMB version'), 400); - return false; - } - # First, check to see if listenee exists - $listenee = $req->get_parameter('omb_listenee'); - $remote = Remote_profile::staticGet('uri', $listenee); - if (!$remote) { - $this->clientError(_('Profile unknown'), 404); - return false; - } - # Second, check to see if they should be able to post updates! - # We see if there are any subscriptions to that remote user with - # the given token. - - $sub = new Subscription(); - $sub->subscribed = $remote->id; - $sub->token = $token->key; - if (!$sub->find(true)) { - $this->clientError(_('You did not send us that profile'), 403); - return false; - } - - $profile = Profile::staticGet('id', $remote->id); - if (!$profile) { - # This one is our fault - $this->serverError(_('Remote profile with no matching profile'), 500); - return false; - } - $nickname = $req->get_parameter('omb_listenee_nickname'); - if ($nickname && !Validate::string($nickname, array('min_length' => 1, - 'max_length' => 64, - 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) { - $this->clientError(_('Nickname must have only lowercase letters and numbers and no spaces.')); - return false; - } - $license = $req->get_parameter('omb_listenee_license'); - if ($license && !common_valid_http_url($license)) { - $this->clientError(sprintf(_("Invalid license URL '%s'"), $license)); - return false; - } - $profile_url = $req->get_parameter('omb_listenee_profile'); - if ($profile_url && !common_valid_http_url($profile_url)) { - $this->clientError(sprintf(_("Invalid profile URL '%s'."), $profile_url)); - return false; - } - # optional stuff - $fullname = $req->get_parameter('omb_listenee_fullname'); - if ($fullname && mb_strlen($fullname) > 255) { - $this->clientError(_("Full name is too long (max 255 chars).")); - return false; - } - $homepage = $req->get_parameter('omb_listenee_homepage'); - if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) { - $this->clientError(sprintf(_("Invalid homepage '%s'"), $homepage)); - return false; - } - $bio = $req->get_parameter('omb_listenee_bio'); - if ($bio && mb_strlen($bio) > 140) { - $this->clientError(_("Bio is too long (max 140 chars).")); - return false; - } - $location = $req->get_parameter('omb_listenee_location'); - if ($location && mb_strlen($location) > 255) { - $this->clientError(_("Location is too long (max 255 chars).")); - return false; - } - $avatar = $req->get_parameter('omb_listenee_avatar'); - if ($avatar) { - if (!common_valid_http_url($avatar) || strlen($avatar) > 255) { - $this->clientError(sprintf(_("Invalid avatar URL '%s'"), $avatar)); - return false; - } - $size = @getimagesize($avatar); - if (!$size) { - $this->clientError(sprintf(_("Can't read avatar URL '%s'"), $avatar)); - return false; - } - if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) { - $this->clientError(sprintf(_("Wrong size image at '%s'"), $avatar)); - return false; - } - if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG, - IMAGETYPE_PNG))) { - $this->clientError(sprintf(_("Wrong image type for '%s'"), $avatar)); - return false; - } - } - - $orig_profile = clone($profile); - - /* Use values even if they are an empty string. Parsing an empty string in - updateProfile is the specified way of clearing a parameter in OMB. */ - if (!is_null($nickname)) { - $profile->nickname = $nickname; - } - if (!is_null($profile_url)) { - $profile->profileurl = $profile_url; - } - if (!is_null($fullname)) { - $profile->fullname = $fullname; - } - if (!is_null($homepage)) { - $profile->homepage = $homepage; - } - if (!is_null($bio)) { - $profile->bio = $bio; - } - if (!is_null($location)) { - $profile->location = $location; - } - - if (!$profile->update($orig_profile)) { - $this->serverError(_('Could not save new profile info'), 500); - return false; - } else { - if ($avatar) { - $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar'); - copy($avatar, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - if (!$profile->setOriginal($filename)) { - $this->serverError(_('Could not save avatar info'), 500); - return false; - } - } - return true; - } - } } +?> diff --git a/actions/userauthorization.php b/actions/userauthorization.php index 8dc2c808d..d5b6a6998 100644 --- a/actions/userauthorization.php +++ b/actions/userauthorization.php @@ -1,5 +1,16 @@ + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + * * Laconica - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, Control Yourself, Inc. * @@ -17,9 +28,13 @@ * along with this program. If not, see . */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} -require_once(INSTALLDIR.'/lib/omb.php'); +require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; +require_once INSTALLDIR.'/extlib/libomb/profile.php'; define('TIMESTAMP_THRESHOLD', 300); class UserauthorizationAction extends Action @@ -32,42 +47,58 @@ class UserauthorizationAction extends Action parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - # CSRF protection + /* Use a session token for CSRF protection. */ $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { - $params = $this->getStoredParams(); - $this->showForm($params, _('There was a problem with your session token. '. - 'Try again, please.')); + $srv = $this->getStoredParams(); + $this->showForm($srv->getRemoteUser(), _('There was a problem ' . + 'with your session token. Try again, ' . + 'please.')); return; } - # We've shown the form, now post user's choice + /* We've shown the form, now post user's choice. */ $this->sendAuthorization(); } else { if (!common_logged_in()) { - # Go log in, and then come back + /* Go log in, and then come back. */ common_set_returnto($_SERVER['REQUEST_URI']); common_redirect(common_local_url('login')); return; } + $user = common_current_user(); + $profile = $user->getProfile(); + if (!$profile) { + common_log_db_error($user, 'SELECT', __FILE__); + $this->serverError(_('User without matching profile')); + return; + } + + /* TODO: If no token is passed the user should get a prompt to enter + it according to OAuth Core 1.0. */ try { - $this->validateRequest(); - $this->storeParams($_GET); - $this->showForm($_GET); - } catch (OAuthException $e) { + $this->validateOmb(); + $srv = new OMB_Service_Provider( + profile_to_omb_profile($_GET['omb_listener'], $profile), + omb_oauth_datastore()); + + $remote_user = $srv->handleUserAuth(); + } catch (Exception $e) { $this->clearParams(); $this->clientError($e->getMessage()); return; } + $this->storeParams($srv); + $this->showForm($remote_user); } } function showForm($params, $error=null) { $this->params = $params; - $this->error = $error; + $this->error = $error; $this->showPage(); } @@ -79,23 +110,24 @@ class UserauthorizationAction extends Action function showPageNotice() { $this->element('p', null, _('Please check these details to make sure '. - 'that you want to subscribe to this user\'s notices. '. - 'If you didn\'t just ask to subscribe to someone\'s notices, '. - 'click "Reject".')); + 'that you want to subscribe to this ' . + 'user\'s notices. If you didn\'t just ask ' . + 'to subscribe to someone\'s notices, '. + 'click “Reject”.')); } function showContent() { $params = $this->params; - $nickname = $params['omb_listenee_nickname']; - $profile = $params['omb_listenee_profile']; - $license = $params['omb_listenee_license']; - $fullname = $params['omb_listenee_fullname']; - $homepage = $params['omb_listenee_homepage']; - $bio = $params['omb_listenee_bio']; - $location = $params['omb_listenee_location']; - $avatar = $params['omb_listenee_avatar']; + $nickname = $params->getNickname(); + $profile = $params->getProfileURL(); + $license = $params->getLicenseURL(); + $fullname = $params->getFullname(); + $homepage = $params->getHomepage(); + $bio = $params->getBio(); + $location = $params->getLocation(); + $avatar = $params->getAvatarURL(); $this->elementStart('div', array('class' => 'profile')); $this->elementStart('div', 'entity_profile vcard'); @@ -172,11 +204,14 @@ class UserauthorizationAction extends Action 'id' => 'userauthorization', 'class' => 'form_user_authorization', 'name' => 'userauthorization', - 'action' => common_local_url('userauthorization'))); + 'action' => common_local_url( + 'userauthorization'))); $this->hidden('token', common_session_token()); - $this->submit('accept', _('Accept'), 'submit accept', null, _('Subscribe to this user')); - $this->submit('reject', _('Reject'), 'submit reject', null, _('Reject this subscription')); + $this->submit('accept', _('Accept'), 'submit accept', null, + _('Subscribe to this user')); + $this->submit('reject', _('Reject'), 'submit reject', null, + _('Reject this subscription')); $this->elementEnd('form'); $this->elementEnd('li'); $this->elementEnd('ul'); @@ -186,191 +221,27 @@ class UserauthorizationAction extends Action function sendAuthorization() { - $params = $this->getStoredParams(); + $srv = $this->getStoredParams(); - if (!$params) { + if (is_null($srv)) { $this->clientError(_('No authorization request!')); return; } - $callback = $params['oauth_callback']; - - if ($this->arg('accept')) { - if (!$this->authorizeToken($params)) { - $this->clientError(_('Error authorizing token')); - } - if (!$this->saveRemoteProfile($params)) { - $this->clientError(_('Error saving remote profile')); - } - if (!$callback) { - $this->showAcceptMessage($params['oauth_token']); - } else { - $newparams = array(); - $newparams['oauth_token'] = $params['oauth_token']; - $newparams['omb_version'] = OMB_VERSION_01; - $user = User::staticGet('uri', $params['omb_listener']); - $profile = $user->getProfile(); - if (!$profile) { - common_log_db_error($user, 'SELECT', __FILE__); - $this->serverError(_('User without matching profile')); - return; - } - $newparams['omb_listener_nickname'] = $user->nickname; - $newparams['omb_listener_profile'] = common_local_url('showstream', - array('nickname' => $user->nickname)); - if (!is_null($profile->fullname)) { - $newparams['omb_listener_fullname'] = $profile->fullname; - } - if (!is_null($profile->homepage)) { - $newparams['omb_listener_homepage'] = $profile->homepage; - } - if (!is_null($profile->bio)) { - $newparams['omb_listener_bio'] = $profile->bio; - } - if (!is_null($profile->location)) { - $newparams['omb_listener_location'] = $profile->location; - } - $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - if ($avatar) { - $newparams['omb_listener_avatar'] = $avatar->url; - } - $parts = array(); - foreach ($newparams as $k => $v) { - $parts[] = $k . '=' . OAuthUtil::urlencode_rfc3986($v); - } - $query_string = implode('&', $parts); - $parsed = parse_url($callback); - $url = $callback . (($parsed['query']) ? '&' : '?') . $query_string; - common_redirect($url, 303); - } - } else { - if (!$callback) { - $this->showRejectMessage(); - } else { - # XXX: not 100% sure how to signal failure... just redirect without token? - common_redirect($callback, 303); - } - } - } - - function authorizeToken(&$params) - { - $token_field = $params['oauth_token']; - $rt = new Token(); - $rt->tok = $token_field; - $rt->type = 0; - $rt->state = 0; - if ($rt->find(true)) { - $orig_rt = clone($rt); - $rt->state = 1; # Authorized but not used - if ($rt->update($orig_rt)) { - return true; - } - } - return false; - } - - # XXX: refactor with similar code in finishremotesubscribe.php - - function saveRemoteProfile(&$params) - { - # FIXME: we should really do this when the consumer comes - # back for an access token. If they never do, we've got stuff in a - # weird state. - - $nickname = $params['omb_listenee_nickname']; - $fullname = $params['omb_listenee_fullname']; - $profile_url = $params['omb_listenee_profile']; - $homepage = $params['omb_listenee_homepage']; - $bio = $params['omb_listenee_bio']; - $location = $params['omb_listenee_location']; - $avatar_url = $params['omb_listenee_avatar']; - - $listenee = $params['omb_listenee']; - $remote = Remote_profile::staticGet('uri', $listenee); - - if ($remote) { - $exists = true; - $profile = Profile::staticGet($remote->id); - $orig_remote = clone($remote); - $orig_profile = clone($profile); - } else { - $exists = false; - $remote = new Remote_profile(); - $remote->uri = $listenee; - $profile = new Profile(); - } - - $profile->nickname = $nickname; - $profile->profileurl = $profile_url; - - if (!is_null($fullname)) { - $profile->fullname = $fullname; - } - if (!is_null($homepage)) { - $profile->homepage = $homepage; - } - if (!is_null($bio)) { - $profile->bio = $bio; - } - if (!is_null($location)) { - $profile->location = $location; - } - - if ($exists) { - $profile->update($orig_profile); - } else { - $profile->created = DB_DataObject_Cast::dateTime(); # current time - $id = $profile->insert(); - if (!$id) { - return false; - } - $remote->id = $id; + $accepted = $this->arg('accept'); + try { + list($val, $token) = $srv->continueUserAuth($accepted); + } catch (Exception $e) { + $this->clientError($e->getMessage()); + return; } - - if ($exists) { - if (!$remote->update($orig_remote)) { - return false; - } + if ($val !== false) { + common_redirect($val, 303); + } elseif ($accepted) { + $this->showAcceptMessage($token); } else { - $remote->created = DB_DataObject_Cast::dateTime(); # current time - if (!$remote->insert()) { - return false; - } - } - - if ($avatar_url) { - if (!$this->addAvatar($profile, $avatar_url)) { - return false; - } - } - - $user = common_current_user(); - - $sub = new Subscription(); - $sub->subscriber = $user->id; - $sub->subscribed = $remote->id; - $sub->token = $params['oauth_token']; # NOTE: request token, not valid for use! - $sub->created = DB_DataObject_Cast::dateTime(); # current time - - if (!$sub->insert()) { - return false; + $this->showRejectMessage(); } - - return true; - } - - function addAvatar($profile, $url) - { - $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar'); - copy($url, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - return $profile->setOriginal($filename); } function showAcceptMessage($tok) @@ -378,26 +249,28 @@ class UserauthorizationAction extends Action common_show_header(_('Subscription authorized')); $this->element('p', null, _('The subscription has been authorized, but no '. - 'callback URL was passed. Check with the site\'s instructions for '. - 'details on how to authorize the subscription. Your subscription token is:')); + 'callback URL was passed. Check with the site\'s ' . + 'instructions for details on how to authorize the ' . + 'subscription. Your subscription token is:')); $this->element('blockquote', 'token', $tok); common_show_footer(); } - function showRejectMessage($tok) + function showRejectMessage() { common_show_header(_('Subscription rejected')); $this->element('p', null, _('The subscription has been rejected, but no '. - 'callback URL was passed. Check with the site\'s instructions for '. - 'details on how to fully reject the subscription.')); + 'callback URL was passed. Check with the site\'s ' . + 'instructions for details on how to fully reject ' . + 'the subscription.')); common_show_footer(); } function storeParams($params) { common_ensure_session(); - $_SESSION['userauthorizationparams'] = $params; + $_SESSION['userauthorizationparams'] = serialize($params); } function clearParams() @@ -409,138 +282,65 @@ class UserauthorizationAction extends Action function getStoredParams() { common_ensure_session(); - $params = $_SESSION['userauthorizationparams']; + $params = unserialize($_SESSION['userauthorizationparams']); return $params; } - # Throws an OAuthException if anything goes wrong - - function validateRequest() - { - /* Find token. - TODO: If no token is passed the user should get a prompt to enter it - according to OAuth Core 1.0 */ - $t = new Token(); - $t->tok = $_GET['oauth_token']; - $t->type = 0; - if (!$t->find(true)) { - throw new OAuthException("Invalid request token: " . $_GET['oauth_token']); - } - - $this->validateOmb(); - return true; - } - function validateOmb() { - foreach (array('omb_version', 'omb_listener', 'omb_listenee', - 'omb_listenee_profile', 'omb_listenee_nickname', - 'omb_listenee_license') as $param) - { - if (!isset($_GET[$param]) || is_null($_GET[$param])) { - throw new OAuthException("Required parameter '$param' not found"); - } - } - # Now, OMB stuff - $version = $_GET['omb_version']; - if ($version != OMB_VERSION_01) { - throw new OAuthException("OpenMicroBlogging version '$version' not supported"); - } $listener = $_GET['omb_listener']; + $listenee = $_GET['omb_listenee']; + $nickname = $_GET['omb_listenee_nickname']; + $profile = $_GET['omb_listenee_profile']; + $user = User::staticGet('uri', $listener); if (!$user) { - throw new OAuthException("Listener URI '$listener' not found here"); + throw new Exception("Listener URI '$listener' not found here"); } $cur = common_current_user(); if ($cur->id != $user->id) { - throw new OAuthException("Can't add for another user!"); - } - $listenee = $_GET['omb_listenee']; - if (!Validate::uri($listenee) && - !common_valid_tag($listenee)) { - throw new OAuthException("Listenee URI '$listenee' not a recognizable URI"); - } - if (strlen($listenee) > 255) { - throw new OAuthException("Listenee URI '$listenee' too long"); + throw new Exception('Can\'t subscribe for another user!'); } $other = User::staticGet('uri', $listenee); if ($other) { - throw new OAuthException("Listenee URI '$listenee' is local user"); + throw new Exception("Listenee URI '$listenee' is local user"); } $remote = Remote_profile::staticGet('uri', $listenee); if ($remote) { - $sub = new Subscription(); + $sub = new Subscription(); $sub->subscriber = $user->id; $sub->subscribed = $remote->id; if ($sub->find(true)) { - throw new OAuthException("Already subscribed to user!"); + throw new Exception('You are already subscribed to this user.'); } } - $nickname = $_GET['omb_listenee_nickname']; - if (!Validate::string($nickname, array('min_length' => 1, - 'max_length' => 64, - 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) { - throw new OAuthException('Nickname must have only letters and numbers and no spaces.'); - } - $profile = $_GET['omb_listenee_profile']; - if (!common_valid_http_url($profile)) { - throw new OAuthException("Invalid profile URL '$profile'."); - } - if ($profile == common_local_url('showstream', array('nickname' => $nickname))) { - throw new OAuthException("Profile URL '$profile' is for a local user."); + if ($profile == common_profile_url($nickname)) { + throw new Exception("Profile URL '$profile' is for a local user."); } - $license = $_GET['omb_listenee_license']; - if (!common_valid_http_url($license)) { - throw new OAuthException("Invalid license URL '$license'."); - } + $license = $_GET['omb_listenee_license']; $site_license = common_config('license', 'url'); if (!common_compatible_license($license, $site_license)) { - throw new OAuthException("Listenee stream license '$license' not compatible with site license '$site_license'."); - } - # optional stuff - $fullname = $_GET['omb_listenee_fullname']; - if ($fullname && mb_strlen($fullname) > 255) { - throw new OAuthException("Full name '$fullname' too long."); - } - $homepage = $_GET['omb_listenee_homepage']; - if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) { - throw new OAuthException("Invalid homepage '$homepage'"); - } - $bio = $_GET['omb_listenee_bio']; - if ($bio && mb_strlen($bio) > 140) { - throw new OAuthException("Bio too long '$bio'"); - } - $location = $_GET['omb_listenee_location']; - if ($location && mb_strlen($location) > 255) { - throw new OAuthException("Location too long '$location'"); + throw new Exception("Listenee stream license '$license' is not " . + "compatible with site license '$site_license'."); } $avatar = $_GET['omb_listenee_avatar']; if ($avatar) { if (!common_valid_http_url($avatar) || strlen($avatar) > 255) { - throw new OAuthException("Invalid avatar URL '$avatar'"); + throw new Exception("Invalid avatar URL '$avatar'"); } $size = @getimagesize($avatar); if (!$size) { - throw new OAuthException("Can't read avatar URL '$avatar'"); - } - if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) { - throw new OAuthException("Wrong size image at '$avatar'"); + throw new Exception("Can't read avatar URL '$avatar'."); } if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG))) { - throw new OAuthException("Wrong image type for '$avatar'"); + throw new Exception("Wrong image type for '$avatar'"); } } - $callback = $_GET['oauth_callback']; - if ($callback && !common_valid_http_url($callback)) { - throw new OAuthException("Invalid callback URL '$callback'"); - } - if ($callback && $callback == common_local_url('finishremotesubscribe')) { - throw new OAuthException("Callback URL '$callback' is for local site."); - } } } +?> diff --git a/actions/xrds.php b/actions/xrds.php index 9327a3c83..7518a5f70 100644 --- a/actions/xrds.php +++ b/actions/xrds.php @@ -34,6 +34,8 @@ if (!defined('LACONICA')) { } require_once INSTALLDIR.'/lib/omb.php'; +require_once INSTALLDIR.'/extlib/libomb/service_provider.php'; +require_once INSTALLDIR.'/extlib/libomb/xrds_mapper.php'; /** * XRDS for OpenID @@ -52,7 +54,7 @@ class XrdsAction extends Action * * @return boolean true */ - function isReadOnly($args) + function isReadOnly() { return true; } @@ -85,89 +87,31 @@ class XrdsAction extends Action */ function showXrds($user) { - header('Content-Type: application/xrds+xml'); - $this->startXML(); - $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds')); + $srv = new OMB_Service_Provider(profile_to_omb_profile($user->uri, + $user->getProfile())); + /* Use libomb’s default XRDS Writer. */ + $xrds_writer = null; + $srv->writeXRDS(new Laconica_XRDS_Mapper(), $xrds_writer); + } +} - $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', - 'xml:id' => 'oauth', - 'xmlns:simple' => 'http://xrds-simple.net/core/1.0', - 'version' => '2.0')); - $this->element('Type', null, 'xri://$xrds*simple'); - $this->showService(OAUTH_ENDPOINT_REQUEST, - common_local_url('requesttoken'), - array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY), - array(OAUTH_HMAC_SHA1), - $user->uri); - $this->showService(OAUTH_ENDPOINT_AUTHORIZE, - common_local_url('userauthorization'), - array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY), - array(OAUTH_HMAC_SHA1)); - $this->showService(OAUTH_ENDPOINT_ACCESS, - common_local_url('accesstoken'), - array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY), - array(OAUTH_HMAC_SHA1)); - $this->showService(OAUTH_ENDPOINT_RESOURCE, - null, - array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY), - array(OAUTH_HMAC_SHA1)); - $this->elementEnd('XRD'); +class Laconica_XRDS_Mapper implements OMB_XRDS_Mapper +{ + protected $urls; - // XXX: decide whether to include user's ID/nickname in postNotice URL - $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', - 'xml:id' => 'omb', - 'xmlns:simple' => 'http://xrds-simple.net/core/1.0', - 'version' => '2.0')); - $this->element('Type', null, 'xri://$xrds*simple'); - $this->showService(OMB_ENDPOINT_POSTNOTICE, - common_local_url('postnotice')); - $this->showService(OMB_ENDPOINT_UPDATEPROFILE, - common_local_url('updateprofile')); - $this->elementEnd('XRD'); - $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', - 'version' => '2.0')); - $this->element('Type', null, 'xri://$xrds*simple'); - $this->showService(OAUTH_DISCOVERY, - '#oauth'); - $this->showService(OMB_NAMESPACE, - '#omb'); - $this->elementEnd('XRD'); - $this->elementEnd('XRDS'); - $this->endXML(); + public function __construct() + { + $this->urls = array( + OAUTH_ENDPOINT_REQUEST => 'requesttoken', + OAUTH_ENDPOINT_AUTHORIZE => 'userauthorization', + OAUTH_ENDPOINT_ACCESS => 'accesstoken', + OMB_ENDPOINT_POSTNOTICE => 'postnotice', + OMB_ENDPOINT_UPDATEPROFILE => 'updateprofile'); } - /** - * Show service. - * - * @param string $type XRDS type - * @param string $uri URI - * @param array $params type parameters, null by default - * @param array $sigs type signatures, null by default - * @param string $localId local ID, null by default - * - * @return void - */ - function showService($type, $uri, $params=null, $sigs=null, $localId=null) + public function getURL($action) { - $this->elementStart('Service'); - if ($uri) { - $this->element('URI', null, $uri); - } - $this->element('Type', null, $type); - if ($params) { - foreach ($params as $param) { - $this->element('Type', null, $param); - } - } - if ($sigs) { - foreach ($sigs as $sig) { - $this->element('Type', null, $sig); - } - } - if ($localId) { - $this->element('LocalID', null, $localId); - } - $this->elementEnd('Service'); + return common_local_url($this->urls[$action]); } } - +?> diff --git a/extlib/libomb/base_url_xrds_mapper.php b/extlib/libomb/base_url_xrds_mapper.php new file mode 100755 index 000000000..645459583 --- /dev/null +++ b/extlib/libomb/base_url_xrds_mapper.php @@ -0,0 +1,51 @@ +writeXRDS. + * + * PHP version 5 + * + * LICENSE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +class OMB_Base_URL_XRDS_Mapper implements OMB_XRDS_Mapper { + + protected $urls; + + public function __construct($oauth_base, $omb_base) { + $this->urls = array( + OAUTH_ENDPOINT_REQUEST => $oauth_base . 'requesttoken', + OAUTH_ENDPOINT_AUTHORIZE => $oauth_base . 'userauthorization', + OAUTH_ENDPOINT_ACCESS => $oauth_base . 'accesstoken', + OMB_ENDPOINT_POSTNOTICE => $omb_base . 'postnotice', + OMB_ENDPOINT_UPDATEPROFILE => $omb_base . 'updateprofile'); + } + + public function getURL($action) { + return $this->urls[$action]; + } +} +?> diff --git a/extlib/libomb/constants.php b/extlib/libomb/constants.php new file mode 100644 index 000000000..a097443ac --- /dev/null +++ b/extlib/libomb/constants.php @@ -0,0 +1,58 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +/** + * The OMB constants. + **/ + +define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1'); + +/* The OMB version supported by this libomb version. */ +define('OMB_VERSION', OMB_VERSION_01); + +define('OMB_ENDPOINT_UPDATEPROFILE', OMB_VERSION . '/updateProfile'); +define('OMB_ENDPOINT_POSTNOTICE', OMB_VERSION . '/postNotice'); + +/** + * The OAuth constants. + **/ + +define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/'); + +define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request'); +define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize'); +define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access'); +define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource'); + +define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header'); +define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body'); + +define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1'); + +define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0'); +?> diff --git a/extlib/libomb/datastore.php b/extlib/libomb/datastore.php new file mode 100755 index 000000000..ac51a4ab8 --- /dev/null +++ b/extlib/libomb/datastore.php @@ -0,0 +1,198 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +class OMB_Datastore extends OAuthDataStore { + + /********* + * OAUTH * + *********/ + + /** + * Revoke specified OAuth token + * + * Revokes the authorization token specified by $token_key. + * Throws exceptions in case of error. + * + * @param string $token_key The token to be revoked + * + * @access public + **/ + public function revoke_token($token_key) { + throw new Exception(); + } + + /** + * Authorize specified OAuth token + * + * Authorizes the authorization token specified by $token_key. + * Throws exceptions in case of error. + * + * @param string $token_key The token to be authorized + * + * @access public + **/ + public function authorize_token($token_key) { + throw new Exception(); + } + + /********* + * OMB * + *********/ + + /** + * Get profile by identifying URI + * + * Returns an OMB_Profile object representing the OMB profile identified by + * $identifier_uri. + * Returns null if there is no such OMB profile. + * Throws exceptions in case of other error. + * + * @param string $identifier_uri The OMB identifier URI specifying the + * requested profile + * + * @access public + * + * @return OMB_Profile The corresponding profile + **/ + public function getProfile($identifier_uri) { + throw new Exception(); + } + + /** + * Save passed profile + * + * Stores the OMB profile $profile. Overwrites an existing entry. + * Throws exceptions in case of error. + * + * @param OMB_Profile $profile The OMB profile which should be saved + * + * @access public + **/ + public function saveProfile($profile) { + throw new Exception(); + } + + /** + * Save passed notice + * + * Stores the OMB notice $notice. The datastore may change the passed notice. + * This might by neccessary for URIs depending on a database key. Note that + * it is the user’s duty to present a mechanism for his OMB_Datastore to + * appropriately change his OMB_Notice. TODO: Ugly. + * Throws exceptions in case of error. + * + * @param OMB_Notice $notice The OMB notice which should be saved + * + * @access public + **/ + public function saveNotice(&$notice) { + throw new Exception(); + } + + /** + * Get subscriptions of a given profile + * + * Returns an array containing subscription informations for the specified + * profile. Every array entry should in turn be an array with keys + * 'uri´: The identifier URI of the subscriber + * 'token´: The subscribe token + * 'secret´: The secret token + * Throws exceptions in case of error. + * + * @param string $subscribed_user_uri The OMB identifier URI specifying the + * subscribed profile + * + * @access public + * + * @return mixed An array containing the subscriptions or 0 if no + * subscription has been found. + **/ + public function getSubscriptions($subscribed_user_uri) { + throw new Exception(); + } + + /** + * Delete a subscription + * + * Deletes the subscription from $subscriber_uri to $subscribed_user_uri. + * Throws exceptions in case of error. + * + * @param string $subscriber_uri The OMB identifier URI specifying the + * subscribing profile + * + * @param string $subscribed_user_uri The OMB identifier URI specifying the + * subscribed profile + * + * @access public + **/ + public function deleteSubscription($subscriber_uri, $subscribed_user_uri) { + throw new Exception(); + } + + /** + * Save a subscription + * + * Saves the subscription from $subscriber_uri to $subscribed_user_uri. + * Throws exceptions in case of error. + * + * @param string $subscriber_uri The OMB identifier URI specifying + * the subscribing profile + * + * @param string $subscribed_user_uri The OMB identifier URI specifying + * the subscribed profile + * @param OAuthToken $token The access token + * + * @access public + **/ + public function saveSubscription($subscriber_uri, $subscribed_user_uri, + $token) { + throw new Exception(); + } +} +?> diff --git a/extlib/libomb/helper.php b/extlib/libomb/helper.php new file mode 100644 index 000000000..a1f21f268 --- /dev/null +++ b/extlib/libomb/helper.php @@ -0,0 +1,99 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +class OMB_Helper { + + /** + * Non-scalar constants + * + * The set of OMB and OAuth Services an OMB Server has to implement. + */ + + public static $OMB_SERVICES = + array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE); + public static $OAUTH_SERVICES = + array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE, OAUTH_ENDPOINT_ACCESS); + + /** + * Validate URL + * + * Basic URL validation. Currently http, https, ftp and gopher are supported + * schemes. + * + * @param string $url The URL which is to be validated. + * + * @return bool Whether URL is valid. + * + * @access public + */ + public static function validateURL($url) { + return Validate::uri($url, array('allowed_schemes' => array('http', 'https', + 'gopher', 'ftp'))); + } + + /** + * Validate Media type + * + * Basic Media type validation. Checks for valid maintype and correct format. + * + * @param string $mediatype The Media type which is to be validated. + * + * @return bool Whether media type is valid. + * + * @access public + */ + public static function validateMediaType($mediatype) { + if (0 === preg_match('/^(\w+)\/([\w\d-+.]+)$/', $mediatype, $subtypes)) { + return false; + } + if (!in_array(strtolower($subtypes[1]), array('application', 'audio', 'image', + 'message', 'model', 'multipart', 'text', 'video'))) { + return false; + } + return true; + } + + /** + * Remove escaping from request parameters + * + * Neutralise the evil effects of magic_quotes_gpc in the current request. + * This is used before handing a request off to OAuthRequest::from_request. + * Many thanks to Ciaran Gultnieks for this fix. + * + * @access public + */ + public static function removeMagicQuotesFromRequest() { + if(get_magic_quotes_gpc() == 1) { + $_POST = array_map('stripslashes', $_POST); + $_GET = array_map('stripslashes', $_GET); + } + } +} +?> diff --git a/extlib/libomb/invalidparameterexception.php b/extlib/libomb/invalidparameterexception.php new file mode 100755 index 000000000..163e1dd4c --- /dev/null +++ b/extlib/libomb/invalidparameterexception.php @@ -0,0 +1,32 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ +class OMB_InvalidParameterException extends Exception { + public function __construct($value, $type, $parameter) { + parent::__construct("Invalid value $value for parameter $parameter in $type"); + } +} +?> diff --git a/extlib/libomb/invalidyadisexception.php b/extlib/libomb/invalidyadisexception.php new file mode 100755 index 000000000..797b7b95b --- /dev/null +++ b/extlib/libomb/invalidyadisexception.php @@ -0,0 +1,31 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ +class OMB_InvalidYadisException extends Exception { + +} +?> diff --git a/extlib/libomb/notice.php b/extlib/libomb/notice.php new file mode 100755 index 000000000..9ac36640a --- /dev/null +++ b/extlib/libomb/notice.php @@ -0,0 +1,272 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +class OMB_Notice { + protected $author; + protected $uri; + protected $content; + protected $url; + protected $license_url; /* url is an own addition for clarification. */ + protected $seealso_url; /* url is an own addition for clarification. */ + protected $seealso_disposition; + protected $seealso_mediatype; + protected $seealso_license_url; /* url is an addition for clarification. */ + + /* The notice as OMB param array. Cached and rebuild on usage. + false while outdated. */ + protected $param_array; + + /** + * Constructor for OMB_Notice + * + * Initializes the OMB_Notice object with author, uri and content. + * These parameters are mandatory for postNotice. + * + * @param object $author An OMB_Profile object representing the author of the + * notice. + * @param string $uri The notice URI as defined by the OMB. A unique and + * unchanging identifier for a notice. + * @param string $content The content of the notice. 140 chars recommended, + * but there is no limit. + * + * @access public + */ + public function __construct($author, $uri, $content) { + $this->content = $content; + if (is_null($author)) { + throw new OMB_InvalidParameterException('', 'notice', 'omb_listenee'); + } + $this->author = $author; + + if (!Validate::uri($uri)) { + throw new OMB_InvalidParameterException($uri, 'notice', 'omb_notice'); + } + $this->uri = $uri; + + $this->param_array = false; + } + + /** + * Returns the notice as array + * + * The method returns an array which contains the whole notice as array. The + * array is cached and only rebuilt on changes of the notice. + * Empty optional values are not passed. + * + * @access public + * @returns array The notice as parameter array + */ + public function asParameters() { + if ($this->param_array !== false) { + return $this->param_array; + } + + $this->param_array = array( + 'omb_notice' => $this->uri, + 'omb_notice_content' => $this->content); + + if (!is_null($this->url)) + $this->param_array['omb_notice_url'] = $this->url; + + if (!is_null($this->license_url)) + $this->param_array['omb_notice_license'] = $this->license_url; + + if (!is_null($this->seealso_url)) { + $this->param_array['omb_seealso'] = $this->seealso_url; + + /* This is actually a free interpretation of the OMB standard. We assume + that additional seealso parameters are not of any use if seealso itself + is not set. */ + if (!is_null($this->seealso_disposition)) + $this->param_array['omb_seealso_disposition'] = + $this->seealso_disposition; + + if (!is_null($this->seealso_mediatype)) + $this->param_array['omb_seealso_mediatype'] = $this->seealso_mediatype; + + if (!is_null($this->seealso_license_url)) + $this->param_array['omb_seealso_license'] = $this->seealso_license_url; + } + return $this->param_array; + } + + /** + * Builds an OMB_Notice object from array + * + * The method builds an OMB_Notice object from the passed parameters array. + * The array MUST provide a notice URI and content. The array fields HAVE TO + * be named according to the OMB standard, i. e. omb_notice_* and + * omb_seealso_*. Values are handled as not passed if the corresponding array + * fields are not set or the empty string. + * + * @param object $author An OMB_Profile object representing the author of + * the notice. + * @param string $parameters An array containing the notice parameters. + * + * @access public + * + * @returns OMB_Notice The built OMB_Notice. + */ + public static function fromParameters($author, $parameters) { + $notice = new OMB_Notice($author, $parameters['omb_notice'], + $parameters['omb_notice_content']); + + if (isset($parameters['omb_notice_url'])) { + $notice->setURL($parameters['omb_notice_url']); + } + + if (isset($parameters['omb_notice_license'])) { + $notice->setLicenseURL($parameters['omb_notice_license']); + } + + if (isset($parameters['omb_seealso'])) { + $notice->setSeealsoURL($parameters['omb_seealso']); + } + + if (isset($parameters['omb_seealso_disposition'])) { + $notice->setSeealsoDisposition($parameters['omb_seealso_disposition']); + } + + if (isset($parameters['omb_seealso_mediatype'])) { + $notice->setSeealsoMediatype($parameters['omb_seealso_mediatype']); + } + + if (isset($parameters['omb_seealso_license'])) { + $notice->setSeealsoLicenseURL($parameters['omb_seealso_license']); + } + return $notice; + } + + public function getAuthor() { + return $this->author; + } + + public function getIdentifierURI() { + return $this->uri; + } + + public function getContent() { + return $this->content; + } + + public function getURL() { + return $this->url; + } + + public function getLicenseURL() { + return $this->license_url; + } + + public function getSeealsoURL() { + return $this->seealso_url; + } + + public function getSeealsoDisposition() { + return $this->seealso_disposition; + } + + public function getSeealsoMediatype() { + return $this->seealso_mediatype; + } + + public function getSeealsoLicenseURL() { + return $this->seealso_license_url; + } + + public function setURL($url) { + if ($url === '') { + $url = null; + } elseif (!OMB_Helper::validateURL($url)) { + throw new OMB_InvalidParameterException($url, 'notice', 'omb_notice_url'); + } + $this->url = $url; + $this->param_array = false; + } + + public function setLicenseURL($license_url) { + if ($license_url === '') { + $license_url = null; + } elseif (!OMB_Helper::validateURL($license_url)) { + throw new OMB_InvalidParameterException($license_url, 'notice', + 'omb_notice_license'); + } + $this->license_url = $license_url; + $this->param_array = false; + } + + public function setSeealsoURL($seealso_url) { + if ($seealso_url === '') { + $seealso_url = null; + } elseif (!OMB_Helper::validateURL($seealso_url)) { + throw new OMB_InvalidParameterException($seealso_url, 'notice', + 'omb_seealso'); + } + $this->seealso_url = $seealso_url; + $this->param_array = false; + } + + public function setSeealsoDisposition($seealso_disposition) { + if ($seealso_disposition === '') { + $seealso_disposition = null; + } elseif ($seealso_disposition !== 'link' && $seealso_disposition !== 'inline') { + throw new OMB_InvalidParameterException($seealso_disposition, 'notice', + 'omb_seealso_disposition'); + } + $this->seealso_disposition = $seealso_disposition; + $this->param_array = false; + } + + public function setSeealsoMediatype($seealso_mediatype) { + if ($seealso_mediatype === '') { + $seealso_mediatype = null; + } elseif (!OMB_Helper::validateMediaType($seealso_mediatype)) { + throw new OMB_InvalidParameterException($seealso_mediatype, 'notice', + 'omb_seealso_mediatype'); + } + $this->seealso_mediatype = $seealso_mediatype; + $this->param_array = false; + } + + public function setSeealsoLicenseURL($seealso_license_url) { + if ($seealso_license_url === '') { + $seealso_license_url = null; + } elseif (!OMB_Helper::validateURL($seealso_license_url)) { + throw new OMB_InvalidParameterException($seealso_license_url, 'notice', + 'omb_seealso_license'); + } + $this->seealso_license_url = $seealso_license_url; + $this->param_array = false; + } +} +?> diff --git a/extlib/libomb/omb_yadis_xrds.php b/extlib/libomb/omb_yadis_xrds.php new file mode 100755 index 000000000..89921203b --- /dev/null +++ b/extlib/libomb/omb_yadis_xrds.php @@ -0,0 +1,196 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +class OMB_Yadis_XRDS extends Auth_Yadis_XRDS { + + protected $fetcher; + + /** + * Create an instance from URL + * + * Constructs an OMB_Yadis_XRDS object from a given URL. A full Yadis + * discovery is performed on the URL and the XRDS is parsed. + * Throws an OMB_InvalidYadisException when no Yadis is discovered or the + * detected XRDS file is broken. + * + * @param string $url The URL on which Yadis discovery + * should be performed on + * @param Auth_Yadis_HTTPFetcher $fetcher A fetcher used to get HTTP + * resources + * + * @access public + * + * @return OMB_Yadis_XRDS The initialized object representing the given + * resource + **/ + public static function fromYadisURL($url, $fetcher) { + /* Perform a Yadis discovery. */ + $yadis = Auth_Yadis_Yadis::discover($url, $fetcher); + if ($yadis->failed) { + throw new OMB_InvalidYadisException($url); + } + + /* Parse the XRDS file. */ + $xrds = OMB_Yadis_XRDS::parseXRDS($yadis->response_text); + if ($xrds === null) { + throw new OMB_InvalidYadisException($url); + } + $xrds->fetcher = $fetcher; + return $xrds; + } + + /** + * Get a specific service + * + * Returns the Auth_Yadis_Service object corresponding to the given service + * URI. + * Throws an OMB_UnsupportedServiceException if the service is not available. + * + * @param string $service URI specifier of the requested service + * + * @access public + * + * @return Auth_Yadis_Service The object representing the requested service + **/ + public function getService($service) { + $match = $this->services(array( create_function('$s', + "return in_array('$service', \$s->getTypes());"))); + if ($match === array()) { + throw new OMB_UnsupportedServiceException($service); + } + return $match[0]; + } + + /** + * Get a specific XRD + * + * Returns the OMB_Yadis_XRDS object corresponding to the given URI. + * Throws an OMB_UnsupportedServiceException if the XRD is not available. + * Note that getXRD tries to resolve external XRD parts as well. + * + * @param string $uri URI specifier of the requested XRD + * + * @access public + * + * @return OMB_Yadis_XRDS The object representing the requested XRD + **/ + public function getXRD($uri) { + $nexthash = strpos($uri, '#'); + if ($nexthash !== 0) { + if ($nexthash !== false) { + $cururi = substr($uri, 0, $nexthash); + $nexturi = substr($uri, $nexthash); + } + return + OMB_Yadis_XRDS::fromYadisURL($cururi, $this->fetcher)->getXRD($nexturi); + } + + $id = substr($uri, 1); + foreach ($this->allXrdNodes as $node) { + $attrs = $this->parser->attributes($node); + if (array_key_exists('xml:id', $attrs) && $attrs['xml:id'] == $id) { + /* Trick the constructor into thinking this is the only node. */ + $bogus_nodes = array($node); + return new OMB_Yadis_XRDS($this->parser, $bogus_nodes); + } + } + throw new OMB_UnsupportedServiceException($uri); + } + + /** + * Parse an XML string containing a XRDS document + * + * Parse an XML string (XRDS document) and return either a + * Auth_Yadis_XRDS object or null, depending on whether the + * XRDS XML is valid. + * Copy and paste from parent to select correct constructor. + * + * @param string $xml_string An XRDS XML string. + * + * @access public + * + * @return mixed An instance of OMB_Yadis_XRDS or null, + * depending on the validity of $xml_string + **/ + + public function &parseXRDS($xml_string, $extra_ns_map = null) { + $_null = null; + + if (!$xml_string) { + return $_null; + } + + $parser = Auth_Yadis_getXMLParser(); + + $ns_map = Auth_Yadis_getNSMap(); + + if ($extra_ns_map && is_array($extra_ns_map)) { + $ns_map = array_merge($ns_map, $extra_ns_map); + } + + if (!($parser && $parser->init($xml_string, $ns_map))) { + return $_null; + } + + // Try to get root element. + $root = $parser->evalXPath('/xrds:XRDS[1]'); + if (!$root) { + return $_null; + } + + if (is_array($root)) { + $root = $root[0]; + } + + $attrs = $parser->attributes($root); + + if (array_key_exists('xmlns:xrd', $attrs) && + $attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) { + return $_null; + } else if (array_key_exists('xmlns', $attrs) && + preg_match('/xri/', $attrs['xmlns']) && + $attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) { + return $_null; + } + + // Get the last XRD node. + $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD'); + + if (!$xrd_nodes) { + return $_null; + } + + $xrds = new OMB_Yadis_XRDS($parser, $xrd_nodes); + return $xrds; + } +} diff --git a/extlib/libomb/plain_xrds_writer.php b/extlib/libomb/plain_xrds_writer.php new file mode 100755 index 000000000..b4a6e990b --- /dev/null +++ b/extlib/libomb/plain_xrds_writer.php @@ -0,0 +1,124 @@ +writeXRDS. + * + * PHP version 5 + * + * LICENSE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +class OMB_Plain_XRDS_Writer implements OMB_XRDS_Writer { + public function writeXRDS($user, $mapper) { + header('Content-Type: application/xrds+xml'); + $xw = new XMLWriter(); + $xw->openURI('php://output'); + $xw->setIndent(true); + + $xw->startDocument('1.0', 'UTF-8'); + $this->writeFullElement($xw, 'XRDS', array('xmlns' => 'xri://$xrds'), array( + array('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', + 'xml:id' => 'oauth', + 'xmlns:simple' => 'http://xrds-simple.net/core/1.0', + 'version' => '2.0'), array( + array('Type', null, 'xri://$xrds*simple'), + array('Service', null, array( + array('Type', null, OAUTH_ENDPOINT_REQUEST), + array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_REQUEST)), + array('Type', null, OAUTH_AUTH_HEADER), + array('Type', null, OAUTH_POST_BODY), + array('Type', null, OAUTH_HMAC_SHA1), + array('LocalID', null, $user->getIdentifierURI()) + )), + array('Service', null, array( + array('Type', null, OAUTH_ENDPOINT_AUTHORIZE), + array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_AUTHORIZE)), + array('Type', null, OAUTH_AUTH_HEADER), + array('Type', null, OAUTH_POST_BODY), + array('Type', null, OAUTH_HMAC_SHA1) + )), + array('Service', null, array( + array('Type', null, OAUTH_ENDPOINT_ACCESS), + array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_ACCESS)), + array('Type', null, OAUTH_AUTH_HEADER), + array('Type', null, OAUTH_POST_BODY), + array('Type', null, OAUTH_HMAC_SHA1) + )), + array('Service', null, array( + array('Type', null, OAUTH_ENDPOINT_RESOURCE), + array('Type', null, OAUTH_AUTH_HEADER), + array('Type', null, OAUTH_POST_BODY), + array('Type', null, OAUTH_HMAC_SHA1) + )) + )), + array('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', + 'xml:id' => 'omb', + 'xmlns:simple' => 'http://xrds-simple.net/core/1.0', + 'version' => '2.0'), array( + array('Type', null, 'xri://$xrds*simple'), + array('Service', null, array( + array('Type', null, OMB_ENDPOINT_POSTNOTICE), + array('URI', null, $mapper->getURL(OMB_ENDPOINT_POSTNOTICE)) + )), + array('Service', null, array( + array('Type', null, OMB_ENDPOINT_UPDATEPROFILE), + array('URI', null, $mapper->getURL(OMB_ENDPOINT_UPDATEPROFILE)) + )) + )), + array('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', + 'version' => '2.0'), array( + array('Type', null, 'xri://$xrds*simple'), + array('Service', null, array( + array('Type', null, OAUTH_DISCOVERY), + array('URI', null, '#oauth') + )), + array('Service', null, array( + array('Type', null, OMB_VERSION), + array('URI', null, '#omb') + )) + )) + )); + $xw->endDocument(); + $xw->flush(); + } + + public static function writeFullElement($xw, $tag, $attributes, $content) { + $xw->startElement($tag); + if (!is_null($attributes)) { + foreach ($attributes as $name => $value) { + $xw->writeAttribute($name, $value); + } + } + if (is_array($content)) { + foreach ($content as $values) { + OMB_Plain_XRDS_Writer::writeFullElement($xw, $values[0], $values[1], $values[2]); + } + } else { + $xw->text($content); + } + $xw->fullEndElement(); + } +} +?> diff --git a/extlib/libomb/profile.php b/extlib/libomb/profile.php new file mode 100755 index 000000000..13314d3e8 --- /dev/null +++ b/extlib/libomb/profile.php @@ -0,0 +1,317 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +class OMB_Profile { + protected $identifier_uri; + protected $profile_url; + protected $nickname; + protected $license_url; + protected $fullname; + protected $homepage; + protected $bio; + protected $location; + protected $avatar_url; + + /* The profile as OMB param array. Cached and rebuild on usage. + false while outdated. */ + protected $param_array; + + /** + * Constructor for OMB_Profile + * + * Initializes the OMB_Profile object with an identifier uri. + * + * @param string $identifier_uri The profile URI as defined by the OMB. A unique + * and unchanging identifier for a profile. + * + * @access public + */ + public function __construct($identifier_uri) { + if (!Validate::uri($identifier_uri)) { + throw new OMB_InvalidParameterException($identifier_uri, 'profile', + 'omb_listenee or omb_listener'); + } + $this->identifier_uri = $identifier_uri; + $this->param_array = false; + } + + /** + * Returns the profile as array + * + * The method returns an array which contains the whole profile as array. The + * array is cached and only rebuilt on changes of the profile. + * + * @param bool $force_all Specifies whether empty fields should be added to + * the array as well. This is neccessary to clear + * fields via updateProfile. + * + * @param string $prefix The common prefix to the key for all parameters. + * + * @access public + * + * @return array The profile as parameter array + */ + public function asParameters($prefix, $force_all = false) { + if ($this->param_array === false) { + $this->param_array = array('' => $this->identifier_uri); + + if ($force_all || !is_null($this->profile_url)) { + $this->param_array['_profile'] = $this->profile_url; + } + + if ($force_all || !is_null($this->homepage)) { + $this->param_array['_homepage'] = $this->homepage; + } + + if ($force_all || !is_null($this->nickname)) { + $this->param_array['_nickname'] = $this->nickname; + } + + if ($force_all || !is_null($this->license_url)) { + $this->param_array['_license'] = $this->license_url; + } + + if ($force_all || !is_null($this->fullname)) { + $this->param_array['_fullname'] = $this->fullname; + } + + if ($force_all || !is_null($this->bio)) { + $this->param_array['_bio'] = $this->bio; + } + + if ($force_all || !is_null($this->location)) { + $this->param_array['_location'] = $this->location; + } + + if ($force_all || !is_null($this->avatar_url)) { + $this->param_array['_avatar'] = $this->avatar_url; + } + + } + $ret = array(); + foreach ($this->param_array as $k => $v) { + $ret[$prefix . $k] = $v; + } + return $ret; + } + + /** + * Builds an OMB_Profile object from array + * + * The method builds an OMB_Profile object from the passed parameters array. The + * array MUST provide a profile URI. The array fields HAVE TO be named according + * to the OMB standard. The prefix (omb_listener or omb_listenee) is passed as a + * parameter. + * + * @param string $parameters An array containing the profile parameters. + * @param string $prefix The common prefix of the profile parameter keys. + * + * @access public + * + * @returns OMB_Profile The built OMB_Profile. + */ + public static function fromParameters($parameters, $prefix) { + if (!isset($parameters[$prefix])) { + throw new OMB_InvalidParameterException('', 'profile', $prefix); + } + + $profile = new OMB_Profile($parameters[$prefix]); + $profile->updateFromParameters($parameters, $prefix); + return $profile; + } + + /** + * Update from array + * + * Updates from the passed parameters array. The array does not have to + * provide a profile URI. The array fields HAVE TO be named according to the + * OMB standard. The prefix (omb_listener or omb_listenee) is passed as a + * parameter. + * + * @param string $parameters An array containing the profile parameters. + * @param string $prefix The common prefix of the profile parameter keys. + * + * @access public + */ + public function updateFromParameters($parameters, $prefix) { + if (isset($parameters[$prefix.'_profile'])) { + $this->setProfileURL($parameters[$prefix.'_profile']); + } + + if (isset($parameters[$prefix.'_license'])) { + $this->setLicenseURL($parameters[$prefix.'_license']); + } + + if (isset($parameters[$prefix.'_nickname'])) { + $this->setNickname($parameters[$prefix.'_nickname']); + } + + if (isset($parameters[$prefix.'_fullname'])) { + $this->setFullname($parameters[$prefix.'_fullname']); + } + + if (isset($parameters[$prefix.'_homepage'])) { + $this->setHomepage($parameters[$prefix.'_homepage']); + } + + if (isset($parameters[$prefix.'_bio'])) { + $this->setBio($parameters[$prefix.'_bio']); + } + + if (isset($parameters[$prefix.'_location'])) { + $this->setLocation($parameters[$prefix.'_location']); + } + + if (isset($parameters[$prefix.'_avatar'])) { + $this->setAvatarURL($parameters[$prefix.'_avatar']); + } + } + + public function getIdentifierURI() { + return $this->identifier_uri; + } + + public function getProfileURL() { + return $this->profile_url; + } + + public function getHomepage() { + return $this->homepage; + } + + public function getNickname() { + return $this->nickname; + } + + public function getLicenseURL() { + return $this->license_url; + } + + public function getFullname() { + return $this->fullname; + } + + public function getBio() { + return $this->bio; + } + + public function getLocation() { + return $this->location; + } + + public function getAvatarURL() { + return $this->avatar_url; + } + + public function setProfileURL($profile_url) { + if (!OMB_Helper::validateURL($profile_url)) { + throw new OMB_InvalidParameterException($profile_url, 'profile', + 'omb_listenee_profile or omb_listener_profile'); + } + $this->profile_url = $profile_url; + $this->param_array = false; + } + + public function setNickname($nickname) { + if (!Validate::string($nickname, + array('min_length' => 1, + 'max_length' => 64, + 'format' => VALIDATE_NUM . VALIDATE_ALPHA))) { + throw new OMB_InvalidParameterException($nickname, 'profile', 'nickname'); + } + + $this->nickname = $nickname; + $this->param_array = false; + } + + public function setLicenseURL($license_url) { + if (!OMB_Helper::validateURL($license_url)) { + throw new OMB_InvalidParameterException($license_url, 'profile', + 'omb_listenee_license or omb_listener_license'); + } + $this->license_url = $license_url; + $this->param_array = false; + } + + public function setFullname($fullname) { + if ($fullname === '') { + $fullname = null; + } elseif (!Validate::string($fullname, array('max_length' => 255))) { + throw new OMB_InvalidParameterException($fullname, 'profile', 'fullname'); + } + $this->fullname = $fullname; + $this->param_array = false; + } + + public function setHomepage($homepage) { + if ($homepage === '') { + $homepage = null; + } + $this->homepage = $homepage; + $this->param_array = false; + } + + public function setBio($bio) { + if ($bio === '') { + $bio = null; + } elseif (!Validate::string($bio, array('max_length' => 140))) { + throw new OMB_InvalidParameterException($bio, 'profile', 'fullname'); + } + $this->bio = $bio; + $this->param_array = false; + } + + public function setLocation($location) { + if ($location === '') { + $location = null; + } elseif (!Validate::string($location, array('max_length' => 255))) { + throw new OMB_InvalidParameterException($location, 'profile', 'fullname'); + } + $this->location = $location; + $this->param_array = false; + } + + public function setAvatarURL($avatar_url) { + if ($avatar_url === '') { + $avatar_url = null; + } elseif (!OMB_Helper::validateURL($avatar_url)) { + throw new OMB_InvalidParameterException($avatar_url, 'profile', + 'omb_listenee_avatar or omb_listener_avatar'); + } + $this->avatar_url = $avatar_url; + $this->param_array = false; + } + +} +?> diff --git a/extlib/libomb/remoteserviceexception.php b/extlib/libomb/remoteserviceexception.php new file mode 100755 index 000000000..374d15973 --- /dev/null +++ b/extlib/libomb/remoteserviceexception.php @@ -0,0 +1,42 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ +class OMB_RemoteServiceException extends Exception { + public static function fromYadis($request_uri, $result) { + if ($result->status == 200) { + $err = 'Got wrong response ' . $result->body; + } else { + $err = 'Got error code ' . $result->status . ' with response ' . $result->body; + } + return new OMB_RemoteServiceException($request_uri . ': ' . $err); + } + + public static function forRequest($action_uri, $failure) { + return new OMB_RemoteServiceException("Handler for $action_uri: " . $failure); + } +} +?> diff --git a/extlib/libomb/service_consumer.php b/extlib/libomb/service_consumer.php new file mode 100755 index 000000000..273fd052e --- /dev/null +++ b/extlib/libomb/service_consumer.php @@ -0,0 +1,430 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +class OMB_Service_Consumer { + protected $url; /* The service URL */ + protected $services; /* An array of strings mapping service URI to + service URL */ + + protected $token; /* An OAuthToken */ + + protected $listener_uri; /* The URI identifying the listener, i. e. the + remote user. */ + + protected $listenee_uri; /* The URI identifying the listenee, i. e. the + local user during an auth request. */ + + /** + * According to OAuth Core 1.0, an user authorization request is no full-blown + * OAuth request. nonce, timestamp, consumer_key and signature are not needed + * in this step. See http://laconi.ca/trac/ticket/827 for more informations. + * + * Since Laconica up to version 0.7.2 performs a full OAuth request check, a + * correct request would fail. + **/ + public $performLegacyAuthRequest = true; + + /* Helper stuff we are going to need. */ + protected $fetcher; + protected $oauth_consumer; + protected $datastore; + + /** + * Constructor for OMB_Service_Consumer + * + * Initializes an OMB_Service_Consumer object representing the OMB service + * specified by $service_url. Performs a complete service discovery using + * Yadis. + * Throws OMB_UnsupportedServiceException if XRDS file does not specify a + * complete OMB service. + * + * @param string $service_url The URL of the service + * @param string $consumer_url An URL representing the consumer + * @param OMB_Datastore $datastore An instance of a class implementing + * OMB_Datastore + * + * @access public + **/ + public function __construct ($service_url, $consumer_url, $datastore) { + $this->url = $service_url; + $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); + $this->datastore = $datastore; + $this->oauth_consumer = new OAuthConsumer($consumer_url, ''); + + $xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher); + + /* Detect our services. This performs a validation as well, since + getService und getXRD throw exceptions on failure. */ + $this->services = array(); + + foreach (array(OAUTH_DISCOVERY => OMB_Helper::$OAUTH_SERVICES, + OMB_VERSION => OMB_Helper::$OMB_SERVICES) + as $service_root => $targetservices) { + $uris = $xrds->getService($service_root)->getURIs(); + $xrd = $xrds->getXRD($uris[0]); + foreach ($targetservices as $targetservice) { + $yadis_service = $xrd->getService($targetservice); + if ($targetservice == OAUTH_ENDPOINT_REQUEST) { + $localid = $yadis_service->getElements('xrd:LocalID'); + $this->listener_uri = $yadis_service->parser->content($localid[0]); + } + $uris = $yadis_service->getURIs(); + $this->services[$targetservice] = $uris[0]; + } + } + } + + /** + * Get the handler URI for a service + * + * Returns the URI the remote web service has specified for the given + * service. + * + * @param string $service The URI identifying the service + * + * @access public + * + * @return string The service handler URI + **/ + public function getServiceURI($service) { + return $this->services[$service]; + } + + /** + * Get the remote user’s URI + * + * Returns the URI of the remote user, i. e. the listener. + * + * @access public + * + * @return string The remote user’s URI + **/ + public function getRemoteUserURI() { + return $this->listener_uri; + } + + /** + * Get the listenee’s URI + * + * Returns the URI of the user being subscribed to, i. e. the local user. + * + * @access public + * + * @return string The local user’s URI + **/ + public function getListeneeURI() { + return $this->listenee_uri; + } + + /** + * Request a request token + * + * Performs a token request on the service. Returns an OAuthToken on success. + * Throws an exception if the request fails. + * + * @access public + * + * @return OAuthToken An unauthorized request token + **/ + public function requestToken() { + /* Set the token to null just in case the user called setToken. */ + $this->token = null; + + $result = $this->performAction(OAUTH_ENDPOINT_REQUEST, + array('omb_listener' => $this->listener_uri)); + if ($result->status != 200) { + throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST, + $result); + } + parse_str($result->body, $return); + if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) { + throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST, + $result); + } + $this->setToken($return['oauth_token'], $return['oauth_token_secret']); + return $this->token; + } + + /** + * + * Request authorization + * + * Returns an URL which equals to an authorization request. The end user + * should be redirected to this location to perform authorization. + * The $finish_url should be a local resource which invokes + * OMB_Consumer::finishAuthorization on request. + * + * @param OMB_Profile $profile An OMB_Profile object representing the + * soon-to-be subscribed (i. e. local) user + * @param string $finish_url Target location after successful + * authorization + * + * @access public + * + * @return string An URL representing an authorization request + **/ + public function requestAuthorization($profile, $finish_url) { + if ($this->performLegacyAuthRequest) { + $params = $profile->asParameters('omb_listenee', false); + $params['omb_listener'] = $this->listener_uri; + $params['oauth_callback'] = $finish_url; + + $url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params, 'GET')->to_url(); + } else { + + $params = array( + 'oauth_callback' => $finish_url, + 'oauth_token' => $this->token->key, + 'omb_version' => OMB_VERSION, + 'omb_listener' => $this->listener_uri); + + $params = array_merge($profile->asParameters('omb_listenee', false). $params); + + /* Build result URL. */ + $url = $this->services[OAUTH_ENDPOINT_AUTHORIZE]; + $url .= (strrpos($url, '?') === false ? '?' : '&'); + foreach ($params as $k => $v) { + $url .= OAuthUtil::urlencode_rfc3986($k) . '=' . OAuthUtil::urlencode_rfc3986($v) . '&'; + } + } + + $this->listenee_uri = $profile->getIdentifierURI(); + + return $url; + } + + /** + * Finish authorization + * + * Finish the subscription process by converting the received and authorized + * request token into an access token. After that, the subscriber’s profile + * and the subscription are stored in the database. + * Expects an OAuthRequest in query parameters. + * Throws exceptions on failure. + * + * @access public + **/ + public function finishAuthorization() { + OMB_Helper::removeMagicQuotesFromRequest(); + $req = OAuthRequest::from_request(); + if ($req->get_parameter('oauth_token') != + $this->token->key) { + /* That’s not the token I wanted to get authorized. */ + throw new OAuthException('The authorized token does not equal the ' . + 'submitted token.'); + } + + if ($req->get_parameter('omb_version') != OMB_VERSION) { + throw new OMB_RemoteServiceException('The remote service uses an ' . + 'unsupported OMB version'); + } + + /* Construct the profile to validate it. */ + + /* Fix OMB bug. Listener URI is not passed. */ + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $params = $_POST; + } else { + $params = $_GET; + } + $params['omb_listener'] = $this->listener_uri; + + require_once 'profile.php'; + $listener = OMB_Profile::fromParameters($params, 'omb_listener'); + + /* Ask the remote service to convert the authorized request token into an + access token. */ + + $result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array()); + if ($result->status != 200) { + throw new OAuthException('Could not get access token'); + } + + parse_str($result->body, $return); + if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) { + throw new OAuthException('Could not get access token'); + } + $this->setToken($return['oauth_token'], $return['oauth_token_secret']); + + /* Subscription is finished and valid. Now store the new subscriber and the + subscription in the database. */ + + $this->datastore->saveProfile($listener); + $this->datastore->saveSubscription($this->listener_uri, + $this->listenee_uri, + $this->token); + } + + /** + * Return the URI identifying the listener + * + * Returns the URI for the OMB user who tries to subscribe or already has + * subscribed our user. This method is a workaround for a serious OMB flaw: + * The Listener URI is not passed in the finishauthorization call. + * + * @access public + * + * @return string the listener’s URI + **/ + public function getListenerURI() { + return $this->listener_uri; + } + + /** + * Inform the service about a profile update + * + * Sends an updated profile to the service. + * + * @param OMB_Profile $profile The profile that has changed + * + * @access public + **/ + public function updateProfile($profile) { + $params = $profile->asParameters('omb_listenee', true); + $this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params, $profile->getIdentifierURI()); + } + + /** + * Inform the service about a new notice + * + * Sends a notice to the service. + * + * @param OMB_Notice $notice The notice + * + * @access public + **/ + public function postNotice($notice) { + $params = $notice->asParameters(); + $params['omb_listenee'] = $notice->getAuthor()->getIdentifierURI(); + $this->performOMBAction(OMB_ENDPOINT_POSTNOTICE, $params, $params['omb_listenee']); + } + + /** + * Set the token member variable + * + * Initializes the token based on given token and secret token. + * + * @param string $token The token + * @param string $secret The secret token + * + * @access public + **/ + public function setToken($token, $secret) { + $this->token = new OAuthToken($token, $secret); + } + + /** + * Prepare an OAuthRequest object + * + * Creates an OAuthRequest object mapping the request specified by the + * parameters. + * + * @param string $action_uri The URI specifying the target service + * @param array $params Additional parameters for the service call + * @param string $method The HTTP method used to call the service + * ('POST' or 'GET', usually) + * + * @access protected + * + * @return OAuthRequest the prepared request + **/ + protected function prepareAction($action_uri, $params, $method) { + $url = $this->services[$action_uri]; + + $url_params = array(); + parse_str(parse_url($url, PHP_URL_QUERY), $url_params); + + /* Add OMB version. */ + $url_params['omb_version'] = OMB_VERSION; + + /* Add user-defined parameters. */ + $url_params = array_merge($url_params, $params); + + $req = OAuthRequest::from_consumer_and_token($this->oauth_consumer, + $this->token, $method, $url, $url_params); + + /* Sign the request. */ + $req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(), + $this->oauth_consumer, $this->token); + + return $req; + } + + /** + * Perform a service call + * + * Creates an OAuthRequest object and execute the mapped call as POST request. + * + * @param string $action_uri The URI specifying the target service + * @param array $params Additional parameters for the service call + * + * @access protected + * + * @return Auth_Yadis_HTTPResponse The POST request response + **/ + protected function performAction($action_uri, $params) { + $req = $this->prepareAction($action_uri, $params, 'POST'); + + /* Return result page. */ + return $this->fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array()); + } + + /** + * Perform an OMB action + * + * Executes an OMB action – to date, it’s one of updateProfile or postNotice. + * + * @param string $action_uri The URI specifying the target service + * @param array $params Additional parameters for the service call + * @param string $listenee_uri The URI identifying the local user for whom + * the action is performed + * + * @access protected + **/ + protected function performOMBAction($action_uri, $params, $listenee_uri) { + $result = $this->performAction($action_uri, $params); + if ($result->status == 403) { + /* The remote user unsubscribed us. */ + $this->datastore->deleteSubscription($this->listener_uri, $listenee_uri); + } else if ($result->status != 200 || + strpos($result->body, 'omb_version=' . OMB_VERSION) === false) { + /* The server signaled an error or sent an incorrect response. */ + throw OMB_RemoteServiceException::fromYadis($action_uri, $result); + } + } +} diff --git a/extlib/libomb/service_provider.php b/extlib/libomb/service_provider.php new file mode 100755 index 000000000..b3ad53753 --- /dev/null +++ b/extlib/libomb/service_provider.php @@ -0,0 +1,411 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +class OMB_Service_Provider { + protected $user; /* An OMB_Profile representing the user */ + protected $datastore; /* AN OMB_Datastore */ + + protected $remote_user; /* An OMB_Profile representing the remote user during + the authorization process */ + + protected $oauth_server; /* An OAuthServer; should only be accessed via + getOAuthServer. */ + + /** + * Initialize an OMB_Service_Provider object + * + * Constructs an OMB_Service_Provider instance that provides OMB services + * referring to a particular user. + * + * @param OMB_Profile $user An OMB_Profile; mandatory for XRDS + * output, user auth handling and OMB + * action performing + * @param OMB_Datastore $datastore An OMB_Datastore; mandatory for + * everything but XRDS output + * @param OAuthServer $oauth_server An OAuthServer; used for token writing + * and OMB action handling; will use + * default value if not set + * + * @access public + **/ + public function __construct ($user = null, $datastore = null, $oauth_server = null) { + $this->user = $user; + $this->datastore = $datastore; + $this->oauth_server = $oauth_server; + } + + public function getRemoteUser() { + return $this->remote_user; + } + + /** + * Write a XRDS document + * + * Writes a XRDS document specifying the OMB service. Optionally uses a + * given object of a class implementing OMB_XRDS_Writer for output. Else + * OMB_Plain_XRDS_Writer is used. + * + * @param OMB_XRDS_Mapper $xrds_mapper An object mapping actions to URLs + * @param OMB_XRDS_Writer $xrds_writer Optional; The OMB_XRDS_Writer used to + * write the XRDS document + * + * @access public + * + * @return mixed Depends on the used OMB_XRDS_Writer; OMB_Plain_XRDS_Writer + * returns nothing. + **/ + public function writeXRDS($xrds_mapper, $xrds_writer = null) { + if ($xrds_writer == null) { + require_once 'plain_xrds_writer.php'; + $xrds_writer = new OMB_Plain_XRDS_Writer(); + } + return $xrds_writer->writeXRDS($this->user, $xrds_mapper); + } + + /** + * Echo a request token + * + * Outputs an unauthorized request token for the query found in $_GET or + * $_POST. + * + * @access public + **/ + public function writeRequestToken() { + OMB_Helper::removeMagicQuotesFromRequest(); + echo $this->getOAuthServer()->fetch_request_token(OAuthRequest::from_request()); + } + + /** + * Handle an user authorization request. + * + * Parses an authorization request. This includes OAuth and OMB verification. + * Throws exceptions on failures. Returns an OMB_Profile object representing + * the remote user. + * + * @access public + * + * @return OMB_Profile The profile of the soon-to-be subscribed, i. e. remote + * user + **/ + public function handleUserAuth() { + OMB_Helper::removeMagicQuotesFromRequest(); + + /* Verify the request token. */ + + $this->token = $this->datastore->lookup_token(null, "request", $_GET['oauth_token']); + if (is_null($this->token)) { + throw new OAuthException('The given request token has not been issued ' . + 'by this service.'); + } + + /* Verify the OMB part. */ + + if ($_GET['omb_version'] !== OMB_VERSION) { + throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE, + 'Wrong OMB version ' . $_GET['omb_version']); + } + + if ($_GET['omb_listener'] !== $this->user->getIdentifierURI()) { + throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE, + 'Wrong OMB listener ' . $_GET['omb_listener']); + } + + foreach (array('omb_listenee', 'omb_listenee_profile', + 'omb_listenee_nickname', 'omb_listenee_license') as $param) { + if (!isset($_GET[$param]) || is_null($_GET[$param])) { + throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE, + "Required parameter '$param' not found"); + } + } + + /* Store given callback for later use. */ + if (isset($_GET['oauth_callback']) && $_GET['oauth_callback'] !== '') { + $this->callback = $_GET['oauth_callback']; + } + $this->remote_user = OMB_Profile::fromParameters($_GET, 'omb_listenee'); + + return $this->remote_user; + } + + /** + * Continue the OAuth dance after user authorization + * + * Performs the appropriate actions after user answered the authorization + * request. + * + * @param bool $accepted Whether the user granted authorization + * + * @access public + * + * @return array A two-component array with the values: + * - callback The callback URL or null if none given + * - token The authorized request token or null if not + * authorized. + **/ + public function continueUserAuth($accepted) { + $callback = $this->callback; + if (!$accepted) { + $this->datastore->revoke_token($this->token->key); + $this->token = null; + /* TODO: The handling is probably wrong in terms of OAuth 1.0 but the way + laconica works. Moreover I don’t know the right way either. */ + + } else { + $this->datastore->authorize_token($this->token->key); + $this->datastore->saveProfile($this->remote_user); + $this->datastore->saveSubscription($this->user->getIdentifierURI(), + $this->remote_user->getIdentifierURI(), $this->token); + + if (!is_null($this->callback)) { + /* Callback wants to get some informations as well. */ + $params = $this->user->asParameters('omb_listener', false); + + $params['oauth_token'] = $this->token->key; + $params['omb_version'] = OMB_VERSION; + + $callback .= (parse_url($this->callback, PHP_URL_QUERY) ? '&' : '?'); + foreach ($params as $k => $v) { + $callback .= OAuthUtil::urlencode_rfc3986($k) . '=' . + OAuthUtil::urlencode_rfc3986($v) . '&'; + } + } + } + return array($callback, $this->token); + } + + /** + * Echo an access token + * + * Outputs an access token for the query found in $_GET or $_POST. + * + * @access public + **/ + public function writeAccessToken() { + OMB_Helper::removeMagicQuotesFromRequest(); + echo $this->getOAuthServer()->fetch_access_token(OAuthRequest::from_request()); + } + + /** + * Handle an updateprofile request + * + * Handles an updateprofile request posted to this service. Updates the + * profile through the OMB_Datastore. + * + * @access public + * + * @return OMB_Profile The updated profile + **/ + public function handleUpdateProfile() { + list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_UPDATEPROFILE); + $profile->updateFromParameters($req->get_parameters(), 'omb_listenee'); + $this->datastore->saveProfile($profile); + $this->finishOMBRequest(); + return $profile; + } + + /** + * Handle a postnotice request + * + * Handles a postnotice request posted to this service. + * + * @access public + * + * @return OMB_Notice The received notice + **/ + public function handlePostNotice() { + list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_POSTNOTICE); + require_once 'notice.php'; + $notice = OMB_Notice::fromParameters($profile, $req->get_parameters()); + $this->datastore->saveNotice($notice); + $this->finishOMBRequest(); + return $notice; + } + + /** + * Handle an OMB request + * + * Performs common OMB request handling. + * + * @param string $uri The URI defining the OMB endpoint being served + * + * @access protected + * + * @return array(OAuthRequest, OMB_Profile) + **/ + protected function handleOMBRequest($uri) { + + OMB_Helper::removeMagicQuotesFromRequest(); + $req = OAuthRequest::from_request(); + $listenee = $req->get_parameter('omb_listenee'); + + try { + list($consumer, $token) = $this->getOAuthServer()->verify_request($req); + } catch (OAuthException $e) { + header('HTTP/1.1 403 Forbidden'); + throw OMB_RemoteServiceException::forRequest($uri, + 'Revoked accesstoken for ' . $listenee); + } + + $version = $req->get_parameter('omb_version'); + if ($version !== OMB_VERSION) { + header('HTTP/1.1 400 Bad Request'); + throw OMB_RemoteServiceException::forRequest($uri, + 'Wrong OMB version ' . $version); + } + + $profile = $this->datastore->getProfile($listenee); + if (is_null($profile)) { + header('HTTP/1.1 400 Bad Request'); + throw OMB_RemoteServiceException::forRequest($uri, + 'Unknown remote profile ' . $listenee); + } + + $subscribers = $this->datastore->getSubscriptions($listenee); + if (count($subscribers) === 0) { + header('HTTP/1.1 403 Forbidden'); + throw OMB_RemoteServiceException::forRequest($uri, + 'No subscriber for ' . $listenee); + } + + return array($req, $profile); + } + + /** + * Finishes an OMB request handling + * + * Performs common OMB request handling finishing. + * + * @access protected + **/ + protected function finishOMBRequest() { + header('HTTP/1.1 200 OK'); + header('Content-type: text/plain'); + /* There should be no clutter but the version. */ + echo "omb_version=" . OMB_VERSION; + } + + /** + * Return an OAuthServer + * + * Checks whether the OAuthServer is null. If so, initializes it with a + * default value. Returns the OAuth server. + * + * @access protected + **/ + protected function getOAuthServer() { + if (is_null($this->oauth_server)) { + $this->oauth_server = new OAuthServer($this->datastore); + $this->oauth_server->add_signature_method( + new OAuthSignatureMethod_HMAC_SHA1()); + } + return $this->oauth_server; + } + + /** + * Publish a notice + * + * Posts an OMB notice. This includes storing the notice and posting it to + * subscribed users. + * + * @param OMB_Notice $notice The new notice + * + * @access public + * + * @return array An array mapping subscriber URIs to the exception posting to + * them has raised; Empty array if no exception occured + **/ + public function postNotice($notice) { + $uri = $this->user->getIdentifierURI(); + + /* $notice is passed by reference and may change. */ + $this->datastore->saveNotice($notice); + $subscribers = $this->datastore->getSubscriptions($uri); + + /* No one to post to. */ + if (is_null($subscribers)) { + return array(); + } + + require_once 'service_consumer.php'; + + $err = array(); + foreach($subscribers as $subscriber) { + try { + $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore); + $service->setToken($subscriber['token'], $subscriber['secret']); + $service->postNotice($notice); + } catch (Exception $e) { + $err[$subscriber['uri']] = $e; + continue; + } + } + return $err; + } + + /** + * Publish a profile update + * + * Posts the current profile as an OMB profile update. This includes updating + * the stored profile and posting it to subscribed users. + * + * @access public + * + * @return array An array mapping subscriber URIs to the exception posting to + * them has raised; Empty array if no exception occured + **/ + public function updateProfile() { + $uri = $this->user->getIdentifierURI(); + + $this->datastore->saveProfile($this->user); + $subscribers = $this->datastore->getSubscriptions($uri); + + /* No one to post to. */ + if (is_null($subscribers)) { + return array(); + } + + require_once 'service_consumer.php'; + + $err = array(); + foreach($subscribers as $subscriber) { + try { + $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore); + $service->setToken($subscriber['token'], $subscriber['secret']); + $service->updateProfile($this->user); + } catch (Exception $e) { + $err[$subscriber['uri']] = $e; + continue; + } + } + return $err; + } +} diff --git a/extlib/libomb/unsupportedserviceexception.php b/extlib/libomb/unsupportedserviceexception.php new file mode 100755 index 000000000..4dab63ebe --- /dev/null +++ b/extlib/libomb/unsupportedserviceexception.php @@ -0,0 +1,31 @@ +. + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ +class OMB_UnsupportedServiceException extends Exception { + +} +?> diff --git a/extlib/libomb/xrds_mapper.php b/extlib/libomb/xrds_mapper.php new file mode 100755 index 000000000..7552154e5 --- /dev/null +++ b/extlib/libomb/xrds_mapper.php @@ -0,0 +1,33 @@ +writeXRDS. + * + * PHP version 5 + * + * LICENSE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +interface OMB_XRDS_Mapper { + public function getURL($action); +} +?> diff --git a/extlib/libomb/xrds_writer.php b/extlib/libomb/xrds_writer.php new file mode 100755 index 000000000..31b451b9c --- /dev/null +++ b/extlib/libomb/xrds_writer.php @@ -0,0 +1,33 @@ +writeXRDS. + * + * PHP version 5 + * + * LICENSE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * @package OMB + * @author Adrian Lang + * @copyright 2009 Adrian Lang + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 + **/ + +interface OMB_XRDS_Writer { + public function writeXRDS($user, $mapper); +} +?> diff --git a/lib/oauthstore.php b/lib/oauthstore.php index f224c6c22..87d8cf213 100644 --- a/lib/oauthstore.php +++ b/lib/oauthstore.php @@ -17,15 +17,16 @@ * along with this program. If not, see . */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} -require_once(INSTALLDIR.'/lib/omb.php'); +require_once 'libomb/datastore.php'; -class LaconicaOAuthDataStore extends OAuthDataStore +class LaconicaDataStore extends OMB_Datastore { // We keep a record of who's contacted us - function lookup_consumer($consumer_key) { $con = Consumer::staticGet('consumer_key', $consumer_key); @@ -44,7 +45,9 @@ class LaconicaOAuthDataStore extends OAuthDataStore function lookup_token($consumer, $token_type, $token_key) { $t = new Token(); - $t->consumer_key = $consumer->key; + if (!is_null($consumer)) { + $t->consumer_key = $consumer->key; + } $t->tok = $token_key; $t->type = ($token_type == 'access') ? 1 : 0; if ($t->find(true)) { @@ -154,4 +157,348 @@ class LaconicaOAuthDataStore extends OAuthDataStore { return $this->new_access_token($consumer); } + + + /** + * Revoke specified OAuth token + * + * Revokes the authorization token specified by $token_key. + * Throws exceptions in case of error. + * + * @param string $token_key The token to be revoked + * + * @access public + **/ + public function revoke_token($token_key) { + $rt = new Token(); + $rt->tok = $token_key; + $rt->type = 0; + $rt->state = 0; + if (!$rt->find(true)) { + throw new Exception('Tried to revoke unknown token'); + } + if (!$rt->delete()) { + throw new Exception('Failed to delete revoked token'); + } + } + + /** + * Authorize specified OAuth token + * + * Authorizes the authorization token specified by $token_key. + * Throws exceptions in case of error. + * + * @param string $token_key The token to be authorized + * + * @access public + **/ + public function authorize_token($token_key) { + $rt = new Token(); + $rt->tok = $token_key; + $rt->type = 0; + $rt->state = 0; + if (!$rt->find(true)) { + throw new Exception('Tried to authorize unknown token'); + } + $orig_rt = clone($rt); + $rt->state = 1; # Authorized but not used + if (!$rt->update($orig_rt)) { + throw new Exception('Failed to authorize token'); + } + } + + /** + * Get profile by identifying URI + * + * Returns an OMB_Profile object representing the OMB profile identified by + * $identifier_uri. + * Returns null if there is no such OMB profile. + * Throws exceptions in case of other error. + * + * @param string $identifier_uri The OMB identifier URI specifying the + * requested profile + * + * @access public + * + * @return OMB_Profile The corresponding profile + **/ + public function getProfile($identifier_uri) { + /* getProfile is only used for remote profiles by libomb. + TODO: Make it work with local ones anyway. */ + $remote = Remote_profile::staticGet('uri', $identifier_uri); + if (!$remote) throw new Exception('No such remote profile'); + $profile = Profile::staticGet('id', $remote->id); + if (!$profile) throw new Exception('No profile for remote user'); + + require_once INSTALLDIR.'/lib/omb.php'; + return profile_to_omb_profile($identifier_uri, $profile); + } + + /** + * Save passed profile + * + * Stores the OMB profile $profile. Overwrites an existing entry. + * Throws exceptions in case of error. + * + * @param OMB_Profile $profile The OMB profile which should be saved + * + * @access public + **/ + public function saveProfile($omb_profile) { + if (common_profile_url($omb_profile->getNickname()) == + $omb_profile->getProfileURL()) { + throw new Exception('Not implemented'); + } else { + $remote = Remote_profile::staticGet('uri', $omb_profile->getIdentifierURI()); + + if ($remote) { + $exists = true; + $profile = Profile::staticGet($remote->id); + $orig_remote = clone($remote); + $orig_profile = clone($profile); + # XXX: compare current postNotice and updateProfile URLs to the ones + # stored in the DB to avoid (possibly...) above attack + } else { + $exists = false; + $remote = new Remote_profile(); + $remote->uri = $omb_profile->getIdentifierURI(); + $profile = new Profile(); + } + + $profile->nickname = $omb_profile->getNickname(); + $profile->profileurl = $omb_profile->getProfileURL(); + + $fullname = $omb_profile->getFullname(); + $profile->fullname = is_null($fullname) ? '' : $fullname; + $homepage = $omb_profile->getHomepage(); + $profile->homepage = is_null($homepage) ? '' : $homepage; + $bio = $omb_profile->getBio(); + $profile->bio = is_null($bio) ? '' : $bio; + $location = $omb_profile->getLocation(); + $profile->location = is_null($location) ? '' : $location; + + if ($exists) { + $profile->update($orig_profile); + } else { + $profile->created = DB_DataObject_Cast::dateTime(); # current time + $id = $profile->insert(); + if (!$id) { + throw new Exception(_('Error inserting new profile')); + } + $remote->id = $id; + } + + $avatar_url = $omb_profile->getAvatarURL(); + if ($avatar_url) { + if (!$this->add_avatar($profile, $avatar_url)) { + throw new Exception(_('Error inserting avatar')); + } + } else { + $avatar = $profile->getOriginalAvatar(); + if($avatar) $avatar->delete(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + if($avatar) $avatar->delete(); + $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); + if($avatar) $avatar->delete(); + $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); + if($avatar) $avatar->delete(); + } + + if ($exists) { + if (!$remote->update($orig_remote)) { + throw new Exception(_('Error updating remote profile')); + } + } else { + $remote->created = DB_DataObject_Cast::dateTime(); # current time + if (!$remote->insert()) { + throw new Exception(_('Error inserting remote profile')); + } + } + } + } + + function add_avatar($profile, $url) + { + $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); + copy($url, $temp_filename); + $imagefile = new ImageFile($profile->id, $temp_filename); + $filename = Avatar::filename($profile->id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + return $profile->setOriginal($filename); + } + + /** + * Save passed notice + * + * Stores the OMB notice $notice. The datastore may change the passed notice. + * This might by neccessary for URIs depending on a database key. Note that + * it is the user’s duty to present a mechanism for his OMB_Datastore to + * appropriately change his OMB_Notice. + * Throws exceptions in case of error. + * + * @param OMB_Notice $notice The OMB notice which should be saved + * + * @access public + **/ + public function saveNotice(&$omb_notice) { + if (Notice::staticGet('uri', $omb_notice->getIdentifierURI())) { + throw new Exception(_('Duplicate notice')); + } + $author_uri = $omb_notice->getAuthor()->getIdentifierURI(); + common_log(LOG_DEBUG, $author_uri, __FILE__); + $author = Remote_profile::staticGet('uri', $author_uri); + if (!$author) { + $author = User::staticGet('uri', $author_uri); + } + if (!$author) { + throw new Exception('No such user'); + } + + common_log(LOG_DEBUG, print_r($author, true), __FILE__); + + $notice = Notice::saveNew($author->id, + $omb_notice->getContent(), + 'omb', + false, + null, + $omb_notice->getIdentifierURI()); + if (is_string($notice)) { + throw new Exception($notice); + } + common_broadcast_notice($notice, true); + } + + /** + * Get subscriptions of a given profile + * + * Returns an array containing subscription informations for the specified + * profile. Every array entry should in turn be an array with keys + * 'uri´: The identifier URI of the subscriber + * 'token´: The subscribe token + * 'secret´: The secret token + * Throws exceptions in case of error. + * + * @param string $subscribed_user_uri The OMB identifier URI specifying the + * subscribed profile + * + * @access public + * + * @return mixed An array containing the subscriptions or 0 if no + * subscription has been found. + **/ + public function getSubscriptions($subscribed_user_uri) { + $sub = new Subscription(); + + $user = $this->_getAnyProfile($subscribed_user_uri); + + $sub->subscribed = $user->id; + + if (!$sub->find(true)) { + return 0; + } + + /* Since we do not use OMB_Service_Provider’s action methods, there + is no need to actually return the subscriptions. */ + return 1; + } + + private function _getAnyProfile($uri) + { + $user = Remote_profile::staticGet('uri', $uri); + if (!$user) { + $user = User::staticGet('uri', $uri); + } + if (!$user) { + throw new Exception('No such user'); + } + return $user; + } + + /** + * Delete a subscription + * + * Deletes the subscription from $subscriber_uri to $subscribed_user_uri. + * Throws exceptions in case of error. + * + * @param string $subscriber_uri The OMB identifier URI specifying the + * subscribing profile + * + * @param string $subscribed_user_uri The OMB identifier URI specifying the + * subscribed profile + * + * @access public + **/ + public function deleteSubscription($subscriber_uri, $subscribed_user_uri) + { + $sub = new Subscription(); + + $subscribed = $this->_getAnyProfile($subscribed_user_uri); + $subscriber = $this->_getAnyProfile($subscriber_uri); + + $sub->subscribed = $subscribed->id; + $sub->subscriber = $subscriber->id; + + $sub->delete(); + } + + /** + * Save a subscription + * + * Saves the subscription from $subscriber_uri to $subscribed_user_uri. + * Throws exceptions in case of error. + * + * @param string $subscriber_uri The OMB identifier URI specifying + * the subscribing profile + * + * @param string $subscribed_user_uri The OMB identifier URI specifying + * the subscribed profile + * @param OAuthToken $token The access token + * + * @access public + **/ + public function saveSubscription($subscriber_uri, $subscribed_user_uri, + $token) + { + $sub = new Subscription(); + + $subscribed = $this->_getAnyProfile($subscribed_user_uri); + $subscriber = $this->_getAnyProfile($subscriber_uri); + + $sub->subscribed = $subscribed->id; + $sub->subscriber = $subscriber->id; + + $sub_exists = $sub->find(true); + + if ($sub_exists) { + $orig_sub = clone($sub); + } else { + $sub->created = DB_DataObject_Cast::dateTime(); + } + + $sub->token = $token->key; + $sub->secret = $token->secret; + + if ($sub_exists) { + $result = $sub->update($orig_sub); + } else { + $result = $sub->insert(); + } + + if (!$result) { + common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__); + throw new Exception(_('Couldn\'t insert new subscription.')); + return; + } + + /* Notify user, if necessary. */ + + if ($subscribed instanceof User) { + mail_subscribe_notify_profile($subscribed, + Profile::staticGet($subscriber->id)); + } + } } +?> diff --git a/lib/omb.php b/lib/omb.php index 4f6a96095..b9d0eef64 100644 --- a/lib/omb.php +++ b/lib/omb.php @@ -17,36 +17,22 @@ * along with this program. If not, see . */ -if (!defined('LACONICA')) { exit(1); } - -require_once('OAuth.php'); -require_once(INSTALLDIR.'/lib/oauthstore.php'); - -require_once(INSTALLDIR.'/classes/Consumer.php'); -require_once(INSTALLDIR.'/classes/Nonce.php'); -require_once(INSTALLDIR.'/classes/Token.php'); - -require_once('Auth/Yadis/Yadis.php'); - -define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/'); -define('OMB_NAMESPACE', 'http://openmicroblogging.org/protocol/0.1'); -define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1'); -define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0'); +if (!defined('LACONICA')) { + exit(1); +} -define('OMB_ENDPOINT_UPDATEPROFILE', OMB_NAMESPACE.'/updateProfile'); -define('OMB_ENDPOINT_POSTNOTICE', OMB_NAMESPACE.'/postNotice'); -define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request'); -define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize'); -define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access'); -define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource'); -define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header'); -define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body'); -define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1'); +require_once INSTALLDIR.'/lib/oauthstore.php'; +require_once 'OAuth.php'; +require_once 'libomb/constants.php'; +require_once 'libomb/service_consumer.php'; +require_once 'libomb/notice.php'; +require_once 'libomb/profile.php'; +require_once 'Auth/Yadis/Yadis.php'; function omb_oauth_consumer() { static $con = null; - if (!$con) { + if (is_null($con)) { $con = new OAuthConsumer(common_root_url(), ''); } return $con; @@ -55,7 +41,7 @@ function omb_oauth_consumer() function omb_oauth_server() { static $server = null; - if (!$server) { + if (is_null($server)) { $server = new OAuthServer(omb_oauth_datastore()); $server->add_signature_method(omb_hmac_sha1()); } @@ -65,8 +51,8 @@ function omb_oauth_server() function omb_oauth_datastore() { static $store = null; - if (!$store) { - $store = new LaconicaOAuthDataStore(); + if (is_null($store)) { + $store = new LaconicaDataStore(); } return $store; } @@ -74,57 +60,18 @@ function omb_oauth_datastore() function omb_hmac_sha1() { static $hmac_method = null; - if (!$hmac_method) { + if (is_null($hmac_method)) { $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); } return $hmac_method; } -function omb_get_services($xrd, $type) +function omb_broadcast_notice($notice) { - return $xrd->services(array(omb_service_filter($type))); -} -function omb_service_filter($type) -{ - return create_function('$s', - 'return omb_match_service($s, \''.$type.'\');'); -} + $omb_notice = notice_to_omb_notice($notice); -function omb_match_service($service, $type) -{ - return in_array($type, $service->getTypes()); -} - -function omb_service_uri($service) -{ - if (!$service) { - return null; - } - $uris = $service->getURIs(); - if (!$uris) { - return null; - } - return $uris[0]; -} - -function omb_local_id($service) -{ - if (!$service) { - return null; - } - $els = $service->getElements('xrd:LocalID'); - if (!$els) { - return null; - } - $el = $els[0]; - return $service->parser->content($el); -} - -function omb_broadcast_remote_subscribers($notice) -{ - - # First, get remote users subscribed to this profile + /* Get remote users subscribed to this profile. */ $rp = new Remote_profile(); $rp->query('SELECT postnoticeurl, token, secret ' . @@ -135,170 +82,148 @@ function omb_broadcast_remote_subscribers($notice) $posted = array(); while ($rp->fetch()) { - if (!$posted[$rp->postnoticeurl]) { - common_log(LOG_DEBUG, 'Posting to ' . $rp->postnoticeurl); - if (omb_post_notice_keys($notice, $rp->postnoticeurl, $rp->token, $rp->secret)) { - common_log(LOG_DEBUG, 'Finished to ' . $rp->postnoticeurl); - $posted[$rp->postnoticeurl] = true; - } else { - common_log(LOG_DEBUG, 'Failed posting to ' . $rp->postnoticeurl); - } + if (isset($posted[$rp->postnoticeurl])) { + /* We already posted to this url. */ + continue; } - } - - $rp->free(); - unset($rp); + common_debug('Posting to ' . $rp->postnoticeurl, __FILE__); + + /* Post notice. */ + $service = new Laconica_OMB_Service_Consumer( + array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl)); + try { + $service->setToken($rp->token, $rp->secret); + $service->postNotice($omb_notice); + } catch (Exception $e) { + common_log(LOG_ERR, 'Failed posting to ' . $rp->postnoticeurl); + common_log(LOG_ERR, 'Error status '.$e); + continue; + } + $posted[$rp->postnoticeurl] = true; - return true; -} + common_debug('Finished to ' . $rp->postnoticeurl, __FILE__); + } -function omb_post_notice($notice, $remote_profile, $subscription) -{ - return omb_post_notice_keys($notice, $remote_profile->postnoticeurl, $subscription->token, $subscription->secret); + return; } -function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret) +function omb_broadcast_profile($profile) { - $user = User::staticGet('id', $notice->profile_id); + $user = User::staticGet('id', $profile->id); if (!$user) { return false; } - $con = omb_oauth_consumer(); - - $token = new OAuthToken($tk, $secret); - - $url = $postnoticeurl; - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); + $profile = $user->getProfile(); - $req = OAuthRequest::from_consumer_and_token($con, $token, - 'POST', $url, $params); + $omb_profile = profile_to_omb_profile($user->uri, $profile, true); - $req->set_parameter('omb_version', OMB_VERSION_01); - $req->set_parameter('omb_listenee', $user->uri); - $req->set_parameter('omb_notice', $notice->uri); - $req->set_parameter('omb_notice_content', $notice->content); - $req->set_parameter('omb_notice_url', common_local_url('shownotice', - array('notice' => - $notice->id))); - $req->set_parameter('omb_notice_license', common_config('license', 'url')); - - $user->free(); - unset($user); + /* Get remote users subscribed to this profile. */ + $rp = new Remote_profile(); - $req->sign_request(omb_hmac_sha1(), $con, $token); + $rp->query('SELECT updateprofileurl, token, secret ' . + 'FROM subscription JOIN remote_profile ' . + 'ON subscription.subscriber = remote_profile.id ' . + 'WHERE subscription.subscribed = ' . $profile->id . ' '); - # We re-use this tool's fetcher, since it's pretty good + $posted = array(); - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); + while ($rp->fetch()) { + if (isset($posted[$rp->updateprofileurl])) { + /* We already posted to this url. */ + continue; + } + common_debug('Posting to ' . $rp->updateprofileurl, __FILE__); + + /* Update profile. */ + $service = new Laconica_OMB_Service_Consumer( + array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl)); + try { + $service->setToken($rp->token, $rp->secret); + $service->updateProfile($omb_profile); + } catch (Exception $e) { + common_log(LOG_ERR, 'Failed posting to ' . $rp->updateprofileurl); + common_log(LOG_ERR, 'Error status '.$e); + continue; + } + $posted[$rp->updateprofileurl] = true; - if (!$fetcher) { - common_log(LOG_WARNING, 'Failed to initialize Yadis fetcher.', __FILE__); - return false; + common_debug('Finished to ' . $rp->updateprofileurl, __FILE__); } - $result = $fetcher->post($req->get_normalized_http_url(), - $req->to_postdata(), - array('User-Agent: Laconica/' . LACONICA_VERSION)); - - if ($result->status == 403) { # not authorized, don't send again - common_debug('403 result, deleting subscription', __FILE__); - # FIXME: figure out how to delete this - # $subscription->delete(); - return false; - } else if ($result->status != 200) { - common_debug('Error status '.$result->status, __FILE__); - return false; - } else { # success! - parse_str($result->body, $return); - if ($return['omb_version'] == OMB_VERSION_01) { - return true; - } else { - return false; - } - } + return; } -function omb_broadcast_profile($profile) -{ - # First, get remote users subscribed to this profile - # XXX: use a join here rather than looping through results - $sub = new Subscription(); - $sub->subscribed = $profile->id; - if ($sub->find()) { - $updated = array(); - while ($sub->fetch()) { - $rp = Remote_profile::staticGet('id', $sub->subscriber); - if ($rp) { - if (!array_key_exists($rp->updateprofileurl, $updated)) { - if (omb_update_profile($profile, $rp, $sub)) { - $updated[$rp->updateprofileurl] = true; - } - } - } - } +class Laconica_OMB_Service_Consumer extends OMB_Service_Consumer { + public function __construct($urls) + { + $this->services = $urls; + $this->datastore = omb_oauth_datastore(); + $this->oauth_consumer = omb_oauth_consumer(); + $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); } + } -function omb_update_profile($profile, $remote_profile, $subscription) +function profile_to_omb_profile($uri, $profile, $force = false) { - $user = User::staticGet($profile->id); - $con = omb_oauth_consumer(); - $token = new OAuthToken($subscription->token, $subscription->secret); - $url = $remote_profile->updateprofileurl; - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - $req = OAuthRequest::from_consumer_and_token($con, $token, - "POST", $url, $params); - $req->set_parameter('omb_version', OMB_VERSION_01); - $req->set_parameter('omb_listenee', $user->uri); - $req->set_parameter('omb_listenee_profile', common_profile_url($profile->nickname)); - $req->set_parameter('omb_listenee_nickname', $profile->nickname); - - # We use blanks to force emptying any existing values in these optional fields - - $req->set_parameter('omb_listenee_fullname', - ($profile->fullname) ? $profile->fullname : ''); - $req->set_parameter('omb_listenee_homepage', - ($profile->homepage) ? $profile->homepage : ''); - $req->set_parameter('omb_listenee_bio', - ($profile->bio) ? $profile->bio : ''); - $req->set_parameter('omb_listenee_location', - ($profile->location) ? $profile->location : ''); + $omb_profile = new OMB_Profile($uri); + $omb_profile->setNickname($profile->nickname); + $omb_profile->setLicenseURL(common_config('license', 'url')); + if (!is_null($profile->fullname)) { + $omb_profile->setFullname($profile->fullname); + } elseif ($force) { + $omb_profile->setFullname(''); + } + if (!is_null($profile->homepage)) { + $omb_profile->setHomepage($profile->homepage); + } elseif ($force) { + $omb_profile->setHomepage(''); + } + if (!is_null($profile->bio)) { + $omb_profile->setBio($profile->bio); + } elseif ($force) { + $omb_profile->setBio(''); + } + if (!is_null($profile->location)) { + $omb_profile->setLocation($profile->location); + } elseif ($force) { + $omb_profile->setLocation(''); + } + if (!is_null($profile->profileurl)) { + $omb_profile->setProfileURL($profile->profileurl); + } elseif ($force) { + $omb_profile->setProfileURL(''); + } $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - $req->set_parameter('omb_listenee_avatar', - ($avatar) ? $avatar->url : ''); + if ($avatar) { + $omb_profile->setAvatarURL($avatar->url); + } elseif ($force) { + $omb_profile->setAvatarURL(''); + } + return $omb_profile; +} - $req->sign_request(omb_hmac_sha1(), $con, $token); +function notice_to_omb_notice($notice) +{ + /* Create an OMB_Notice for $notice. */ + $user = User::staticGet('id', $notice->profile_id); - # We re-use this tool's fetcher, since it's pretty good + if (!$user) { + return null; + } - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); + $profile = $user->getProfile(); - $result = $fetcher->post($req->get_normalized_http_url(), - $req->to_postdata(), - array('User-Agent: Laconica/' . LACONICA_VERSION)); + $omb_notice = new OMB_Notice(profile_to_omb_profile($user->uri, $profile), + $notice->uri, + $notice->content); + $omb_notice->setURL(common_local_url('shownotice', array('notice' => + $notice->id))); + $omb_notice->setLicenseURL(common_config('license', 'url')); - if (empty($result) || !$result) { - common_debug("Unable to contact " . $req->get_normalized_http_url()); - } else if ($result->status == 403) { # not authorized, don't send again - common_debug('403 result, deleting subscription', __FILE__); - $subscription->delete(); - return false; - } else if ($result->status != 200) { - common_debug('Error status '.$result->status, __FILE__); - return false; - } else { # success! - parse_str($result->body, $return); - if (isset($return['omb_version']) && $return['omb_version'] === OMB_VERSION_01) { - return true; - } else { - return false; - } - } + return $omb_notice; } +?> diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php index 515461072..34c7188b2 100644 --- a/lib/unqueuemanager.php +++ b/lib/unqueuemanager.php @@ -39,7 +39,7 @@ class UnQueueManager case 'omb': if ($this->_isLocal($notice)) { require_once(INSTALLDIR.'/lib/omb.php'); - omb_broadcast_remote_subscribers($notice); + omb_broadcast_notice($notice); } break; case 'public': diff --git a/scripts/ombqueuehandler.php b/scripts/ombqueuehandler.php index 1587192b6..cc5263ae2 100755 --- a/scripts/ombqueuehandler.php +++ b/scripts/ombqueuehandler.php @@ -57,7 +57,7 @@ class OmbQueueHandler extends QueueHandler $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id); return true; } else { - return omb_broadcast_remote_subscribers($notice); + return omb_broadcast_notice($notice); } } -- cgit v1.2.3-54-g00ecf From a8d35451823b01c074d5417a138d4968331a8130 Mon Sep 17 00:00:00 2001 From: Eric Helgeson Date: Thu, 20 Aug 2009 14:25:50 -0500 Subject: var empty check did not function as expected. made script executable --- scripts/fixup_utf8.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 scripts/fixup_utf8.php (limited to 'scripts') diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php old mode 100644 new mode 100755 index 8c9a9127f..f952af8d3 --- a/scripts/fixup_utf8.php +++ b/scripts/fixup_utf8.php @@ -42,7 +42,7 @@ class UTF8FixerUpper { $this->args = $args; - if (array_key_exists('max_date', $args)) { + if (!empty($args['max_date'])) { $this->max_date = strftime('%Y-%m-%d %H:%M:%S', strtotime($args['max_date'])); } else { $this->max_date = strftime('%Y-%m-%d %H:%M:%S', time()); -- cgit v1.2.3-54-g00ecf From a94a5fb51ae37ccf99218a236c5a1c3c68c7fffa Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 21 Aug 2009 08:19:09 -0400 Subject: correctly check for max notice length in xmppdaemon --- scripts/xmppdaemon.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'scripts') diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php index 69512f243..f033025f8 100755 --- a/scripts/xmppdaemon.php +++ b/scripts/xmppdaemon.php @@ -316,9 +316,11 @@ class XMPPDaemon extends Daemon { $body = trim($pl['body']); $content_shortened = common_shorten_links($body); - if (mb_strlen($content_shortened) > 140) { + if (Notice::contentTooLong($content_shortened)) { $from = jabber_normalize_jid($pl['from']); - $this->from_site($from, "Message too long - maximum is 140 characters, you sent ".mb_strlen($content_shortened)); + $this->from_site($from, sprintf(_("Message too long - maximum is %d characters, you sent %d"), + Notice::maxContent(), + mb_strlen($content_shortened))); return; } $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp'); -- cgit v1.2.3-54-g00ecf From 1285c11d0a913bc1590e91de42a4335c7ac4119d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 21 Aug 2009 08:21:55 -0400 Subject: maildaemon correctly checks max notice length --- scripts/maildaemon.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'scripts') diff --git a/scripts/maildaemon.php b/scripts/maildaemon.php index 3ef4d0638..179f0de09 100755 --- a/scripts/maildaemon.php +++ b/scripts/maildaemon.php @@ -66,9 +66,10 @@ class MailerDaemon } $msg = $this->cleanup_msg($msg); $msg = common_shorten_links($msg); - if (mb_strlen($msg) > 140) { - $this->error($from,_('That\'s too long. '. - 'Max notice size is 140 chars.')); + if (Notice::contentTooLong($msg)) { + $this->error($from, sprintf(_('That\'s too long. '. + 'Max notice size is %d chars.'), + Notice::maxContent())); } $fileRecords = array(); foreach($attachments as $attachment){ @@ -78,9 +79,9 @@ class MailerDaemon die('error() should trigger an exception before reaching here.'); } $filename = $this->saveFile($user, $attachment,$mimetype); - + fclose($attachment); - + if (empty($filename)) { $this->error($from,_('Couldn\'t save file.')); } @@ -96,9 +97,10 @@ class MailerDaemon $short_fileurl = common_shorten_url($fileurl); $msg .= ' ' . $short_fileurl; - if (mb_strlen($msg) > 140) { + if (Notice::contentTooLong($msg)) { $this->deleteFile($filename); - $this->error($from,_('Max notice size is 140 chars, including attachment URL.')); + $this->error($from, sprintf(_('Max notice size is %d chars, including attachment URL.'), + Notice::maxContent())); } // Also, not sure this is necessary -- Zach @@ -123,7 +125,7 @@ class MailerDaemon $stream = stream_get_meta_data($attachment); if (copy($stream['uri'], $filepath) && chmod($filepath,0664)) { return $filename; - } else { + } else { $this->error(null,_('File could not be moved to destination directory.' . $stream['uri'] . ' ' . $filepath)); } } @@ -152,7 +154,7 @@ class MailerDaemon } function maybeAddRedir($file_id, $url) - { + { $file_redir = File_redirection::staticGet('url', $url); if (empty($file_redir)) { @@ -273,7 +275,7 @@ class MailerDaemon } function attachFile($notice, $filerec) - { + { File_to_post::processNew($filerec->id, $notice->id); $this->maybeAddRedir($filerec->id, -- cgit v1.2.3-54-g00ecf