diff options
author | Zach Copley <zach@status.net> | 2010-02-22 01:23:24 -0800 |
---|---|---|
committer | Zach Copley <zach@status.net> | 2010-02-22 01:23:24 -0800 |
commit | 35be39e30eacda1b0425a2ae9f8e58cd0867d157 (patch) | |
tree | df86f17a666e5d0bb775dbcef0fc4eb5b1c777f3 /plugins/OStatus/classes | |
parent | 47300a2ae9a51108fbf59a57cf5ab6e8867b54a6 (diff) | |
parent | 17ed30dffc1c05259baf2f0387089547e39684d7 (diff) |
Merge branch 'testing' of git@gitorious.org:statusnet/mainline into testing
Diffstat (limited to 'plugins/OStatus/classes')
-rw-r--r-- | plugins/OStatus/classes/FeedSub.php | 11 | ||||
-rw-r--r-- | plugins/OStatus/classes/HubSub.php | 123 | ||||
-rw-r--r-- | plugins/OStatus/classes/Ostatus_profile.php | 170 | ||||
-rw-r--r-- | plugins/OStatus/classes/Ostatus_source.php | 114 |
4 files changed, 349 insertions, 69 deletions
diff --git a/plugins/OStatus/classes/FeedSub.php b/plugins/OStatus/classes/FeedSub.php index 31241d3de..b848b6b1d 100644 --- a/plugins/OStatus/classes/FeedSub.php +++ b/plugins/OStatus/classes/FeedSub.php @@ -291,10 +291,9 @@ class FeedSub extends Memcached_DataObject $headers = array('Content-Type: application/x-www-form-urlencoded'); $post = array('hub.mode' => $mode, 'hub.callback' => $callback, - 'hub.verify' => 'async', + 'hub.verify' => 'sync', 'hub.verify_token' => $this->verify_token, 'hub.secret' => $this->secret, - //'hub.lease_seconds' => 0, 'hub.topic' => $this->uri); $client = new HTTPClient(); $response = $client->post($this->huburi, $headers, $post); @@ -317,8 +316,8 @@ class FeedSub extends Memcached_DataObject common_log(LOG_ERR, __METHOD__ . ": error \"{$e->getMessage()}\" hitting hub $this->huburi subscribing to $this->uri"); $orig = clone($this); - $this->verify_token = null; - $this->sub_state = null; + $this->verify_token = ''; + $this->sub_state = 'inactive'; $this->update($orig); unset($orig); @@ -343,7 +342,7 @@ class FeedSub extends Memcached_DataObject } else { $this->sub_end = null; } - $this->lastupdate = common_sql_now(); + $this->modified = common_sql_now(); return $this->update($original); } @@ -362,7 +361,7 @@ class FeedSub extends Memcached_DataObject $this->sub_state = ''; $this->sub_start = ''; $this->sub_end = ''; - $this->lastupdate = common_sql_now(); + $this->modified = common_sql_now(); return $this->update($original); } diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php index a81de68e6..eae2928c3 100644 --- a/plugins/OStatus/classes/HubSub.php +++ b/plugins/OStatus/classes/HubSub.php @@ -30,11 +30,11 @@ class HubSub extends Memcached_DataObject public $topic; public $callback; public $secret; - public $challenge; public $lease; public $sub_start; public $sub_end; public $created; + public $modified; public /*static*/ function staticGet($topic, $callback) { @@ -61,11 +61,11 @@ class HubSub extends Memcached_DataObject 'topic' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, 'callback' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, 'secret' => DB_DATAOBJECT_STR, - 'challenge' => DB_DATAOBJECT_STR, 'lease' => DB_DATAOBJECT_INT, 'sub_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, 'sub_end' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, - 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); + 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL, + 'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); } static function schemaDef() @@ -82,8 +82,6 @@ class HubSub extends Memcached_DataObject 255, false), new ColumnDef('secret', 'text', null, true), - new ColumnDef('challenge', 'varchar', - 32, true), new ColumnDef('lease', 'int', null, true), new ColumnDef('sub_start', 'datetime', @@ -91,6 +89,8 @@ class HubSub extends Memcached_DataObject new ColumnDef('sub_end', 'datetime', null, true), new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('modified', 'datetime', null, false)); } @@ -148,85 +148,106 @@ class HubSub extends Memcached_DataObject } /** - * Send a verification ping to subscriber + * Schedule a future verification ping to the subscriber. + * If queues are disabled, will be immediate. + * + * @param string $mode 'subscribe' or 'unsubscribe' + * @param string $token hub.verify_token value, if provided by client + */ + function scheduleVerify($mode, $token=null, $retries=null) + { + if ($retries === null) { + $retries = intval(common_config('ostatus', 'hub_retries')); + } + $data = array('sub' => clone($this), + 'mode' => $mode, + 'token' => $token, + 'retries' => $retries); + $qm = QueueManager::get(); + $qm->enqueue($data, 'hubverify'); + } + + /** + * Send a verification ping to subscriber, and if confirmed apply the changes. + * This may create, update, or delete the database record. + * * @param string $mode 'subscribe' or 'unsubscribe' * @param string $token hub.verify_token value, if provided by client + * @throws ClientException on failure */ function verify($mode, $token=null) { assert($mode == 'subscribe' || $mode == 'unsubscribe'); - // Is this needed? data object fun... - $clone = clone($this); - $clone->challenge = common_good_rand(16); - $clone->update($this); - $this->challenge = $clone->challenge; - unset($clone); - + $challenge = common_good_rand(32); $params = array('hub.mode' => $mode, 'hub.topic' => $this->topic, - 'hub.challenge' => $this->challenge); + 'hub.challenge' => $challenge); if ($mode == 'subscribe') { $params['hub.lease_seconds'] = $this->lease; } if ($token !== null) { $params['hub.verify_token'] = $token; } - $url = $this->callback . '?' . http_build_query($params, '', '&'); // @fixme ugly urls - try { - $request = new HTTPClient(); - $response = $request->get($url); - $status = $response->getStatus(); - - if ($status >= 200 && $status < 300) { - $fail = false; - } else { - // @fixme how can we schedule a second attempt? - // Or should we? - $fail = "Returned HTTP $status"; - } - } catch (Exception $e) { - $fail = $e->getMessage(); + // Any existing query string parameters must be preserved + $url = $this->callback; + if (strpos('?', $url) !== false) { + $url .= '&'; + } else { + $url .= '?'; } - if ($fail) { - // @fixme how can we schedule a second attempt? - // or save a fail count? - // Or should we? - common_log(LOG_ERR, "Failed to verify $mode for $this->topic at $this->callback: $fail"); - return false; + $url .= http_build_query($params, '', '&'); + + $request = new HTTPClient(); + $response = $request->get($url); + $status = $response->getStatus(); + + if ($status >= 200 && $status < 300) { + common_log(LOG_INFO, "Verified $mode of $this->callback:$this->topic"); } else { - if ($mode == 'subscribe') { - // Establish or renew the subscription! - // This seems unnecessary... dataobject fun! - $clone = clone($this); - $clone->challenge = null; - $clone->setLease($this->lease); - $clone->update($this); - unset($clone); + throw new ClientException("Hub subscriber verification returned HTTP $status"); + } - $this->challenge = null; - $this->setLease($this->lease); - common_log(LOG_ERR, "Verified $mode of $this->callback:$this->topic for $this->lease seconds"); - } else if ($mode == 'unsubscribe') { - common_log(LOG_ERR, "Verified $mode of $this->callback:$this->topic"); - $this->delete(); + $old = HubSub::staticGet($this->topic, $this->callback); + if ($mode == 'subscribe') { + if ($old) { + $this->update($old); + } else { + $ok = $this->insert(); + } + } else if ($mode == 'unsubscribe') { + if ($old) { + $old->delete(); + } else { + // That's ok, we're already unsubscribed. } - return true; } } /** * Insert wrapper; transparently set the hash key from topic and callback columns. - * @return boolean success + * @return mixed success */ function insert() { $this->hashkey = self::hashkey($this->topic, $this->callback); + $this->created = common_sql_now(); + $this->modified = common_sql_now(); return parent::insert(); } /** + * Update wrapper; transparently update modified column. + * @return boolean success + */ + function update($old=null) + { + $this->modified = common_sql_now(); + return parent::update($old); + } + + /** * Schedule delivery of a 'fat ping' to the subscriber's callback * endpoint. If queues are disabled, this will run immediately. * diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index b67e20202..71885bcdc 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -508,13 +508,15 @@ class Ostatus_profile extends Memcached_DataObject } } - // @fixme save detailed ostatus source info // @fixme ensure that groups get handled correctly $saved = Notice::saveNew($oprofile->localProfile()->id, $content, 'ostatus', $params); + + // Record which feed this came through... + Ostatus_source::saveNew($saved, $this, 'push'); } /** @@ -522,7 +524,7 @@ class Ostatus_profile extends Memcached_DataObject * @return Ostatus_profile * @throws FeedSubException */ - public static function ensureProfile($profile_uri) + public static function ensureProfile($profile_uri, $hints=array()) { // Get the canonical feed URI and check it $discover = new FeedDiscovery(); @@ -545,7 +547,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($subject)) { $subjObject = new ActivityObject($subject); - return self::ensureActivityObjectProfile($subjObject, $feeduri, $salmonuri); + return self::ensureActivityObjectProfile($subjObject, $feeduri, $salmonuri, $hints); } // Otherwise, try the feed author @@ -554,7 +556,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($author)) { $authorObject = new ActivityObject($author); - return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri); + return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri, $hints); } // Sheesh. Not a very nice feed! Let's try fingerpoken in the @@ -570,7 +572,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($actor)) { $actorObject = new ActivityObject($actor); - return self::ensureActivityObjectProfile($actorObject, $feeduri, $salmonuri); + return self::ensureActivityObjectProfile($actorObject, $feeduri, $salmonuri, $hints); } @@ -578,7 +580,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($author)) { $authorObject = new ActivityObject($author); - return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri); + return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri, $hints); } } @@ -688,11 +690,11 @@ class Ostatus_profile extends Memcached_DataObject return self::ensureActivityObjectProfile($activity->actor, $feeduri, $salmonuri); } - public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null) + public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array()) { $profile = self::getActivityObjectProfile($object); if (!$profile) { - $profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri); + $profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri, $hints); } return $profile; } @@ -745,10 +747,10 @@ class Ostatus_profile extends Memcached_DataObject self::createActivityObjectProfile($actor, $feeduri, $salmonuri); } - protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null) + protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array()) { $homeuri = $object->id; - $nickname = self::getActivityObjectNickname($object); + $nickname = self::getActivityObjectNickname($object, $hints); $avatar = self::getActivityObjectAvatar($object); if (!$homeuri) { @@ -756,6 +758,18 @@ class Ostatus_profile extends Memcached_DataObject throw new ServerException("No profile URI"); } + if (empty($feeduri)) { + if (array_key_exists('feedurl', $hints)) { + $feeduri = $hints['feedurl']; + } + } + + if (empty($salmonuri)) { + if (array_key_exists('salmon', $hints)) { + $salmonuri = $hints['salmon']; + } + } + if (!$feeduri || !$salmonuri) { // Get the canonical feed URI and check it $discover = new FeedDiscovery(); @@ -773,7 +787,11 @@ class Ostatus_profile extends Memcached_DataObject $profile = new Profile(); $profile->nickname = $nickname; $profile->fullname = $object->title; - $profile->profileurl = $object->link; + if (!empty($object->link)) { + $profile->profileurl = $object->link; + } else if (array_key_exists('profileurl', $hints)) { + $profile->profileurl = $hints['profileurl']; + } $profile->created = common_sql_now(); // @fixme bio @@ -812,12 +830,24 @@ class Ostatus_profile extends Memcached_DataObject } } - protected static function getActivityObjectNickname($object) + protected static function getActivityObjectNickname($object, $hints=array()) { // XXX: check whatever PoCo calls a nickname first + // Try the definitive ID + $nickname = self::nicknameFromURI($object->id); + // Try a Webfinger if one was passed (way) down + + if (empty($nickname)) { + if (array_key_exists('webfinger', $hints)) { + $nickname = self::nicknameFromURI($hints['webfinger']); + } + } + + // Try the name + if (empty($nickname)) { $nickname = common_nicknamize($object->title); } @@ -845,4 +875,120 @@ class Ostatus_profile extends Memcached_DataObject return null; } } + + public static function ensureWebfinger($addr) + { + // First, look it up + + $oprofile = Ostatus_profile::staticGet('uri', 'acct:'.$addr); + + if (!empty($oprofile)) { + return $oprofile; + } + + // Now, try some discovery + + $wf = new Webfinger(); + + $result = $wf->lookup($addr); + + if (!$result) { + return null; + } + + foreach ($result->links as $link) { + switch ($link['rel']) { + case Webfinger::PROFILEPAGE: + $profileUrl = $link['href']; + break; + case 'salmon': + $salmonEndpoint = $link['href']; + break; + case Webfinger::UPDATESFROM: + $feedUrl = $link['href']; + break; + default: + common_log(LOG_NOTICE, "Don't know what to do with rel = '{$link['rel']}'"); + break; + } + } + + $hints = array('webfinger' => $addr, + 'profileurl' => $profileUrl, + 'feedurl' => $feedUrl, + 'salmon' => $salmonEndpoint); + + // If we got a feed URL, try that + + if (isset($feedUrl)) { + try { + $oprofile = self::ensureProfile($feedUrl, $hints); + return $oprofile; + } catch (Exception $e) { + common_log(LOG_WARNING, "Failed creating profile from feed URL '$feedUrl': " . $e->getMessage()); + // keep looking + } + } + + // If we got a profile page, try that! + + if (isset($profileUrl)) { + try { + $oprofile = self::ensureProfile($profileUrl, $hints); + return $oprofile; + } catch (Exception $e) { + common_log(LOG_WARNING, "Failed creating profile from profile URL '$profileUrl': " . $e->getMessage()); + // keep looking + } + } + + // XXX: try hcard + // XXX: try FOAF + + if (isset($salmonEndpoint)) { + + // An account URL, a salmon endpoint, and a dream? Not much to go + // on, but let's give it a try + + $uri = 'acct:'.$addr; + + $profile = new Profile(); + + $profile->nickname = self::nicknameFromUri($uri); + $profile->created = common_sql_now(); + + if (isset($profileUrl)) { + $profile->profileurl = $profileUrl; + } + + $profile_id = $profile->insert(); + + if (!$profile_id) { + common_log_db_error($profile, 'INSERT', __FILE__); + throw new Exception("Couldn't save profile for '$addr'"); + } + + $oprofile = new Ostatus_profile(); + + $oprofile->uri = $uri; + $oprofile->salmonuri = $salmonEndpoint; + $oprofile->profile_id = $profile_id; + $oprofile->created = common_sql_now(); + + if (isset($feedUrl)) { + $profile->feeduri = $feedUrl; + } + + $result = $oprofile->insert(); + + if (!$result) { + common_log_db_error($oprofile, 'INSERT', __FILE__); + throw new Exception("Couldn't save ostatus_profile for '$addr'"); + } + + return $oprofile; + } + + return null; + } } diff --git a/plugins/OStatus/classes/Ostatus_source.php b/plugins/OStatus/classes/Ostatus_source.php new file mode 100644 index 000000000..e6ce7d442 --- /dev/null +++ b/plugins/OStatus/classes/Ostatus_source.php @@ -0,0 +1,114 @@ +<?php +/* + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** + * @package OStatusPlugin + * @maintainer Brion Vibber <brion@status.net> + */ + +class Ostatus_source extends Memcached_DataObject +{ + public $__table = 'ostatus_source'; + + public $notice_id; // notice we're referring to + public $profile_uri; // uri of the ostatus_profile this came through -- may be a group feed + public $method; // push or salmon + + public /*static*/ function staticGet($k, $v=null) + { + return parent::staticGet(__CLASS__, $k, $v); + } + + /** + * return table definition for DB_DataObject + * + * DB_DataObject needs to know something about the table to manipulate + * instances. This method provides all the DB_DataObject needs to know. + * + * @return array array of column definitions + */ + + function table() + { + return array('notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'profile_uri' => DB_DATAOBJECT_STR, + 'method' => DB_DATAOBJECT_STR); + } + + static function schemaDef() + { + return array(new ColumnDef('notice_id', 'integer', + null, false, 'PRI'), + new ColumnDef('profile_uri', 'varchar', + 255, false), + new ColumnDef('method', "ENUM('push','salmon')", + null, false)); + } + + /** + * return key definitions for DB_DataObject + * + * DB_DataObject needs to know about keys that the table has; this function + * defines them. + * + * @return array key definitions + */ + + function keys() + { + return array_keys($this->keyTypes()); + } + + /** + * return key definitions for Memcached_DataObject + * + * Our caching system uses the same key definitions, but uses a different + * method to get them. + * + * @return array key definitions + */ + + function keyTypes() + { + return array('notice_id' => 'K'); + } + + function sequenceKey() + { + return array(false, false, false); + } + + /** + * Save a remote notice source record; this helps indicate how trusted we are. + * @param string $method + */ + public static function saveNew(Notice $notice, Ostatus_profile $oprofile, $method) + { + $osource = new Ostatus_source(); + $osource->notice_id = $notice->id; + $osource->profile_uri = $oprofile->uri; + $osource->method = $method; + if ($osource->insert()) { + return true; + } else { + common_log_db_error($osource, 'INSERT', __FILE__); + return false; + } + } +} |