diff options
-rw-r--r-- | EVENTS.txt | 17 | ||||
-rw-r--r-- | actions/deleteuser.php | 31 | ||||
-rw-r--r-- | lib/action.php | 2 | ||||
-rw-r--r-- | lib/adminpanelaction.php | 29 | ||||
-rw-r--r-- | plugins/Blacklist/BlacklistPlugin.php | 238 | ||||
-rw-r--r-- | plugins/Blacklist/blacklistadminpanel.php | 222 | ||||
-rw-r--r-- | plugins/OStatus/OStatusPlugin.php | 37 | ||||
-rw-r--r-- | plugins/OStatus/actions/hostmeta.php | 16 | ||||
-rw-r--r-- | plugins/OStatus/actions/ostatusinit.php | 6 | ||||
-rw-r--r-- | plugins/OStatus/actions/xrd.php (renamed from plugins/OStatus/actions/webfinger.php) | 24 | ||||
-rw-r--r-- | plugins/OStatus/classes/Magicsig.php | 6 | ||||
-rw-r--r-- | plugins/OStatus/classes/Ostatus_profile.php | 211 | ||||
-rw-r--r-- | plugins/OStatus/lib/discovery.php | 310 | ||||
-rw-r--r-- | plugins/OStatus/lib/magicenvelope.php | 25 | ||||
-rw-r--r-- | plugins/OStatus/lib/ostatusqueuehandler.php | 2 | ||||
-rw-r--r-- | plugins/OStatus/lib/salmon.php | 35 | ||||
-rw-r--r-- | plugins/OStatus/lib/salmonqueuehandler.php | 4 | ||||
-rw-r--r-- | plugins/OStatus/lib/webfinger.php | 164 | ||||
-rw-r--r-- | scripts/createsim.php | 3 | ||||
-rw-r--r-- | tests/ActivityParseTests.php | 8 |
20 files changed, 995 insertions, 395 deletions
diff --git a/EVENTS.txt b/EVENTS.txt index c387274c0..a2b405acc 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -769,3 +769,20 @@ StartShowSubscriptionsContent: before showing the subscriptions content EndShowSubscriptionsContent: after showing the subscriptions content - $action: the current action + +StartDeleteUserForm: starting the data in the form for deleting a user +- $action: action being shown +- $user: user being deleted + +EndDeleteUserForm: Ending the data in the form for deleting a user +- $action: action being shown +- $user: user being deleted + +StartDeleteUser: handling the post for deleting a user +- $action: action being shown +- $user: user being deleted + +EndDeleteUser: handling the post for deleting a user +- $action: action being shown +- $user: user being deleted + diff --git a/actions/deleteuser.php b/actions/deleteuser.php index 32b703aa7..c4f84fad2 100644 --- a/actions/deleteuser.php +++ b/actions/deleteuser.php @@ -131,18 +131,21 @@ class DeleteuserAction extends ProfileFormAction $this->elementStart('fieldset'); $this->hidden('token', common_session_token()); $this->element('legend', _('Delete user')); - $this->element('p', null, - _('Are you sure you want to delete this user? '. - 'This will clear all data about the user from the '. - 'database, without a backup.')); - $this->element('input', array('id' => 'deleteuserto-' . $id, - 'name' => 'profileid', - 'type' => 'hidden', - 'value' => $id)); - foreach ($this->args as $k => $v) { - if (substr($k, 0, 9) == 'returnto-') { - $this->hidden($k, $v); + if (Event::handle('StartDeleteUserForm', array($this, $this->user))) { + $this->element('p', null, + _('Are you sure you want to delete this user? '. + 'This will clear all data about the user from the '. + 'database, without a backup.')); + $this->element('input', array('id' => 'deleteuserto-' . $id, + 'name' => 'profileid', + 'type' => 'hidden', + 'value' => $id)); + foreach ($this->args as $k => $v) { + if (substr($k, 0, 9) == 'returnto-') { + $this->hidden($k, $v); + } } + Event::handle('EndDeleteUserForm', array($this, $this->user)); } $this->submit('form_action-no', _('No'), 'submit form_action-primary', 'no', _("Do not block this user")); $this->submit('form_action-yes', _('Yes'), 'submit form_action-secondary', 'yes', _('Delete this user')); @@ -158,7 +161,9 @@ class DeleteuserAction extends ProfileFormAction function handlePost() { - $this->user->delete(); + if (Event::handle('StartDeleteUser', array($this, $this->user))) { + $this->user->delete(); + Event::handle('EndDeleteUser', array($this, $this->user)); + } } } - diff --git a/lib/action.php b/lib/action.php index fa9ddb911..a7e0eb33b 100644 --- a/lib/action.php +++ b/lib/action.php @@ -976,7 +976,7 @@ class Action extends HTMLOutputter // lawsuit if (is_null($arg)) { return $def; - } else if (in_array($arg, array('true', 'yes', '1'))) { + } else if (in_array($arg, array('true', 'yes', '1', 'on'))) { return true; } else if (in_array($arg, array('false', 'no', '0'))) { return false; diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php index f05627b31..536d97cdf 100644 --- a/lib/adminpanelaction.php +++ b/lib/adminpanelaction.php @@ -103,7 +103,7 @@ class AdminPanelAction extends Action $name = mb_substr($name, 0, -10); - if (!in_array($name, common_config('admin', 'panels'))) { + if (!self::canAdmin($name)) { $this->clientError(_('Changes to that panel are not allowed.'), 403); return false; } @@ -262,6 +262,17 @@ class AdminPanelAction extends Action return $result; } + + function canAdmin($name) + { + $isOK = false; + + if (Event::handle('AdminPanelCheck', array($name, &$isOK))) { + $isOK = in_array($name, common_config('admin', 'panels')); + } + + return $isOK; + } } /** @@ -307,32 +318,32 @@ class AdminPanelNav extends Widget if (Event::handle('StartAdminPanelNav', array($this))) { - if ($this->canAdmin('site')) { + if (AdminPanelAction::canAdmin('site')) { $this->out->menuItem(common_local_url('siteadminpanel'), _('Site'), _('Basic site configuration'), $action_name == 'siteadminpanel', 'nav_site_admin_panel'); } - if ($this->canAdmin('design')) { + if (AdminPanelAction::canAdmin('design')) { $this->out->menuItem(common_local_url('designadminpanel'), _('Design'), _('Design configuration'), $action_name == 'designadminpanel', 'nav_design_admin_panel'); } - if ($this->canAdmin('user')) { + if (AdminPanelAction::canAdmin('user')) { $this->out->menuItem(common_local_url('useradminpanel'), _('User'), _('User configuration'), $action_name == 'useradminpanel', 'nav_design_admin_panel'); } - if ($this->canAdmin('access')) { + if (AdminPanelAction::canAdmin('access')) { $this->out->menuItem(common_local_url('accessadminpanel'), _('Access'), _('Access configuration'), $action_name == 'accessadminpanel', 'nav_design_admin_panel'); } - if ($this->canAdmin('paths')) { + if (AdminPanelAction::canAdmin('paths')) { $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'), _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel'); } - if ($this->canAdmin('sessions')) { + if (AdminPanelAction::canAdmin('sessions')) { $this->out->menuItem(common_local_url('sessionsadminpanel'), _('Sessions'), _('Sessions configuration'), $action_name == 'sessionsadminpanel', 'nav_design_admin_panel'); } @@ -342,8 +353,4 @@ class AdminPanelNav extends Widget $this->action->elementEnd('ul'); } - function canAdmin($name) - { - return in_array($name, common_config('admin', 'panels')); - } } diff --git a/plugins/Blacklist/BlacklistPlugin.php b/plugins/Blacklist/BlacklistPlugin.php index 84a2cb616..fb8f7306f 100644 --- a/plugins/Blacklist/BlacklistPlugin.php +++ b/plugins/Blacklist/BlacklistPlugin.php @@ -22,7 +22,7 @@ * @category Action * @package StatusNet * @author Evan Prodromou <evan@status.net> - * @copyright 2009 StatusNet Inc. + * @copyright 2010 StatusNet Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -47,6 +47,55 @@ class BlacklistPlugin extends Plugin public $nicknames = array(); public $urls = array(); + public $canAdmin = true; + + private $_nicknamePatterns = array(); + private $_urlPatterns = array(); + + /** + * Initialize the plugin + * + * @return void + */ + + function initialize() + { + $confNicknames = $this->_configArray('blacklist', 'nicknames'); + + $this->_nicknamePatterns = array_merge($this->nicknames, + $confNicknames); + + $confURLs = $this->_configArray('blacklist', 'urls'); + + $this->_urlPatterns = array_merge($this->urls, + $confURLs); + } + + /** + * Retrieve an array from configuration + * + * Carefully checks a section. + * + * @param string $section Configuration section + * @param string $setting Configuration setting + * + * @return array configuration values + */ + + function _configArray($section, $setting) + { + $config = common_config($section, $setting); + + if (empty($config)) { + return array(); + } else if (is_array($config)) { + return $config; + } else if (is_string($config)) { + return explode("\r\n", $config); + } else { + throw new Exception("Unknown data type for config $section + $setting"); + } + } /** * Hook registration to prevent blacklisted homepages or nicknames @@ -173,7 +222,8 @@ class BlacklistPlugin extends Plugin private function _checkUrl($url) { - foreach ($this->urls as $pattern) { + foreach ($this->_urlPatterns as $pattern) { + common_debug("Checking $url against $pattern"); if (preg_match("/$pattern/", $url)) { return false; } @@ -194,7 +244,8 @@ class BlacklistPlugin extends Plugin private function _checkNickname($nickname) { - foreach ($this->nicknames as $pattern) { + foreach ($this->_nicknamePatterns as $pattern) { + common_debug("Checking $nickname against $pattern"); if (preg_match("/$pattern/", $nickname)) { return false; } @@ -203,14 +254,191 @@ class BlacklistPlugin extends Plugin return true; } + /** + * Add our actions to the URL router + * + * @param Net_URL_Mapper $m URL mapper for this hit + * + * @return boolean hook return + */ + + function onRouterInitialized($m) + { + $m->connect('admin/blacklist', array('action' => 'blacklistadminpanel')); + return true; + } + + /** + * Auto-load our classes if called + * + * @param string $cls Class to load + * + * @return boolean hook return + */ + + function onAutoload($cls) + { + switch (strtolower($cls)) + { + case 'blacklistadminpanelaction': + $base = strtolower(mb_substr($cls, 0, -6)); + include_once INSTALLDIR.'/plugins/Blacklist/'.$base.'.php'; + return false; + default: + return true; + } + } + + /** + * Plugin version data + * + * @param array &$versions array of version blocks + * + * @return boolean hook value + */ + function onPluginVersion(&$versions) { $versions[] = array('name' => 'Blacklist', 'version' => self::VERSION, 'author' => 'Evan Prodromou', - 'homepage' => 'http://status.net/wiki/Plugin:Blacklist', + 'homepage' => + 'http://status.net/wiki/Plugin:Blacklist', 'description' => - _m('Keep a blacklist of forbidden nickname and URL patterns.')); + _m('Keep a blacklist of forbidden nickname '. + 'and URL patterns.')); + return true; + } + + /** + * Determines if our admin panel can be shown + * + * @param string $name name of the admin panel + * @param boolean &$isOK result + * + * @return boolean hook value + */ + + function onAdminPanelCheck($name, &$isOK) + { + if ($name == 'blacklist') { + $isOK = $this->canAdmin; + return false; + } + return true; } + + /** + * Add our tab to the admin panel + * + * @param Widget $nav Admin panel nav + * + * @return boolean hook value + */ + + function onEndAdminPanelNav($nav) + { + if (AdminPanelAction::canAdmin('blacklist')) { + + $action_name = $nav->action->trimmed('action'); + + $nav->out->menuItem(common_local_url('blacklistadminpanel'), + _('Blacklist'), + _('Blacklist configuration'), + $action_name == 'blacklistadminpanel', + 'nav_blacklist_admin_panel'); + } + + return true; + } + + function onEndDeleteUserForm($action, $user) + { + $cur = common_current_user(); + + if (empty($cur) || !$cur->hasRight(Right::CONFIGURESITE)) { + return; + } + + $profile = $user->getProfile(); + + if (empty($profile)) { + return; + } + + $action->elementStart('ul', 'form_data'); + $action->elementStart('li'); + $this->checkboxAndText($action, + 'blacklistnickname', + _('Add this nickname pattern to blacklist'), + 'blacklistnicknamepattern', + $this->patternizeNickname($user->nickname)); + $action->elementEnd('li'); + + if (!empty($profile->homepage)) { + $action->elementStart('li'); + $this->checkboxAndText($action, + 'blacklisthomepage', + _('Add this homepage pattern to blacklist'), + 'blacklisthomepagepattern', + $this->patternizeHomepage($profile->homepage)); + $action->elementEnd('li'); + } + + $action->elementEnd('ul'); + } + + function onEndDeleteUser($action, $user) + { + common_debug("Action args: " . print_r($action->args, true)); + + if ($action->boolean('blacklisthomepage')) { + $pattern = $action->trimmed('blacklisthomepagepattern'); + $confURLs = $this->_configArray('blacklist', 'urls'); + $confURLs[] = $pattern; + Config::save('blacklist', 'urls', implode("\r\n", $confURLs)); + } + + if ($action->boolean('blacklistnickname')) { + $pattern = $action->trimmed('blacklistnicknamepattern'); + $confNicknames = $this->_configArray('blacklist', 'nicknames'); + $confNicknames[] = $pattern; + Config::save('blacklist', 'nicknames', implode("\r\n", $confNicknames)); + } + + return true; + } + + function checkboxAndText($action, $checkID, $label, $textID, $value) + { + $action->element('input', array('name' => $checkID, + 'type' => 'checkbox', + 'class' => 'checkbox', + 'id' => $checkID)); + + $action->text(' '); + + $action->element('label', array('class' => 'checkbox', + 'for' => $checkID), + $label); + + $action->text(' '); + + $action->element('input', array('name' => $textID, + 'type' => 'text', + 'id' => $textID, + 'value' => $value)); + } + + function patternizeNickname($nickname) + { + return $nickname; + } + + function patternizeHomepage($homepage) + { + $hostname = parse_url($homepage, PHP_URL_HOST); + return $hostname; + } } diff --git a/plugins/Blacklist/blacklistadminpanel.php b/plugins/Blacklist/blacklistadminpanel.php new file mode 100644 index 000000000..98d07080d --- /dev/null +++ b/plugins/Blacklist/blacklistadminpanel.php @@ -0,0 +1,222 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Blacklist administration panel + * + * PHP version 5 + * + * LICENCE: 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/>. + * + * @category Settings + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Administer blacklist + * + * @category Admin + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class BlacklistadminpanelAction extends AdminPanelAction +{ + /** + * title of the admin panel + * + * @return string title + */ + + function title() + { + return _('Blacklist'); + } + + /** + * Panel instructions + * + * @return string instructions + */ + + function getInstructions() + { + return _('Blacklisted URLs and nicknames'); + } + + /** + * Show the actual form + * + * @return void + * + * @see BlacklistAdminPanelForm + */ + + function showForm() + { + $form = new BlacklistAdminPanelForm($this); + $form->show(); + return; + } + + /** + * Save the form settings + * + * @return void + */ + + function saveSettings() + { + static $settings = array( + 'blacklist' => array('nicknames', 'urls'), + ); + + $values = array(); + + foreach ($settings as $section => $parts) { + foreach ($parts as $setting) { + $values[$section][$setting] = $this->trimmed("$section-$setting"); + } + } + + // This throws an exception on validation errors + + $this->validate($values); + + // assert(all values are valid); + + $config = new Config(); + + $config->query('BEGIN'); + + foreach ($settings as $section => $parts) { + foreach ($parts as $setting) { + Config::save($section, $setting, $values[$section][$setting]); + } + } + + $config->query('COMMIT'); + + return; + } + + /** + * Validate the values + * + * @param array &$values 2d array of values to check + * + * @return boolean success flag + */ + + function validate(&$values) + { + return true; + } +} + +/** + * Admin panel form for blacklist panel + * + * @category Admin + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class BlacklistAdminPanelForm extends Form +{ + /** + * ID of the form + * + * @return string ID + */ + + function id() + { + return 'blacklistadminpanel'; + } + + /** + * Class of the form + * + * @return string class + */ + + function formClass() + { + return 'form_settings'; + } + + /** + * Action we post to + * + * @return string action URL + */ + + function action() + { + return common_local_url('blacklistadminpanel'); + } + + /** + * Show the form controls + * + * @return void + */ + + function formData() + { + $this->out->elementStart('ul', 'form_data'); + + $this->out->elementStart('li'); + $this->out->textarea('blacklist-nicknames', _m('Nicknames'), + common_config('blacklist', 'nicknames'), + _('Patterns of nicknames to block, one per line')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->textarea('blacklist-urls', _m('URLs'), + common_config('blacklist', 'urls'), + _('Patterns of URLs to block, one per line')); + $this->out->elementEnd('li'); + + $this->out->elementEnd('ul'); + } + + /** + * Buttons for submitting + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', + _('Save'), + 'submit', + null, + _('Save site settings')); + } +} diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 7f75b7b2b..720dedd0a 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -43,8 +43,8 @@ class OStatusPlugin extends Plugin // Discovery actions $m->connect('.well-known/host-meta', array('action' => 'hostmeta')); - $m->connect('main/webfinger', - array('action' => 'webfinger')); + $m->connect('main/xrd', + array('action' => 'xrd')); $m->connect('main/ostatus', array('action' => 'ostatusinit')); $m->connect('main/ostatus?nickname=:nickname', @@ -103,6 +103,20 @@ class OStatusPlugin extends Plugin } /** + * Add a link header for LRDD Discovery + */ + function onStartShowHTML($action) + { + if ($action instanceof ShowstreamAction) { + $acct = 'acct:'. $action->profile->nickname .'@'. common_config('site', 'server'); + $url = common_local_url('xrd'); + $url.= '?uri='. $acct; + + header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="application/xrd+xml"'); + } + } + + /** * Set up a PuSH hub link to our internal link for canonical timeline * Atom feeds for users and groups. */ @@ -135,7 +149,8 @@ class OStatusPlugin extends Plugin // Also, we'll add in the salmon link $salmon = common_local_url($salmonAction, array('id' => $id)); - $feed->addLink($salmon, array('rel' => 'salmon')); + $feed->addLink($salmon, array('rel' => Salmon::NS_REPLIES)); + $feed->addLink($salmon, array('rel' => Salmon::NS_MENTIONS)); } return true; @@ -400,7 +415,7 @@ class OStatusPlugin extends Plugin $act->actor = ActivityObject::fromProfile($subscriber); $act->object = ActivityObject::fromProfile($other); - $oprofile->notifyActivity($act); + $oprofile->notifyActivity($act, $subscriber); return true; } @@ -448,7 +463,7 @@ class OStatusPlugin extends Plugin $act->actor = ActivityObject::fromProfile($profile); $act->object = ActivityObject::fromProfile($other); - $oprofile->notifyActivity($act); + $oprofile->notifyActivity($act, $profile); return true; } @@ -490,7 +505,7 @@ class OStatusPlugin extends Plugin $member->getBestName(), $oprofile->getBestName()); - if ($oprofile->notifyActivity($act)) { + if ($oprofile->notifyActivity($act, $member)) { return true; } else { $oprofile->garbageCollect(); @@ -540,7 +555,7 @@ class OStatusPlugin extends Plugin $member->getBestName(), $oprofile->getBestName()); - $oprofile->notifyActivity($act); + $oprofile->notifyActivity($act, $member); } } @@ -583,7 +598,7 @@ class OStatusPlugin extends Plugin $act->actor = ActivityObject::fromProfile($profile); $act->object = ActivityObject::fromNotice($notice); - $oprofile->notifyActivity($act); + $oprofile->notifyActivity($act, $profile); return true; } @@ -627,7 +642,7 @@ class OStatusPlugin extends Plugin $act->actor = ActivityObject::fromProfile($profile); $act->object = ActivityObject::fromNotice($notice); - $oprofile->notifyActivity($act); + $oprofile->notifyActivity($act, $profile); return true; } @@ -644,7 +659,7 @@ class OStatusPlugin extends Plugin function onStartUserGroupHomeUrl($group, &$url) { - return $this->onStartUserGroupPermalink($group, &$url); + return $this->onStartUserGroupPermalink($group, $url); } function onStartUserGroupPermalink($group, &$url) @@ -716,7 +731,7 @@ class OStatusPlugin extends Plugin $act->object = $act->actor; while ($oprofile->fetch()) { - $oprofile->notifyDeferred($act); + $oprofile->notifyDeferred($act, $profile); } return true; diff --git a/plugins/OStatus/actions/hostmeta.php b/plugins/OStatus/actions/hostmeta.php index 850b8a0fe..3d00b98ae 100644 --- a/plugins/OStatus/actions/hostmeta.php +++ b/plugins/OStatus/actions/hostmeta.php @@ -31,12 +31,18 @@ class HostMetaAction extends Action { parent::handle(); - $w = new Webfinger(); - - $domain = common_config('site', 'server'); - $url = common_local_url('webfinger'); + $url = common_local_url('xrd'); $url.= '?uri={uri}'; - print $w->getHostMeta($domain, $url); + + $xrd = new XRD(); + + $xrd = new XRD(); + $xrd->host = $domain; + $xrd->links[] = array('rel' => Discovery::LRDD_REL, + 'template' => $url, + 'title' => array('Resource Descriptor')); + + print $xrd->toXML(); } } diff --git a/plugins/OStatus/actions/ostatusinit.php b/plugins/OStatus/actions/ostatusinit.php index 3f2f6368f..8ba8dcdcc 100644 --- a/plugins/OStatus/actions/ostatusinit.php +++ b/plugins/OStatus/actions/ostatusinit.php @@ -131,9 +131,9 @@ class OStatusInitAction extends Action function connectWebfinger($acct) { - $w = new Webfinger; + $disco = new Discovery; - $result = $w->lookup($acct); + $result = $disco->lookup($acct); if (!$result) { $this->clientError(_m("Couldn't look up OStatus account profile.")); } @@ -144,7 +144,7 @@ class OStatusInitAction extends Action $user = User::staticGet('nickname', $this->nickname); $target_profile = common_local_url('userbyid', array('id' => $user->id)); - $url = $w->applyTemplate($link['template'], $target_profile); + $url = Discovery::applyTemplate($link['template'], $target_profile); common_log(LOG_INFO, "Sending remote subscriber $acct to $url"); common_redirect($url, 303); } diff --git a/plugins/OStatus/actions/webfinger.php b/plugins/OStatus/actions/xrd.php index e292ccec9..f574b60ee 100644 --- a/plugins/OStatus/actions/webfinger.php +++ b/plugins/OStatus/actions/xrd.php @@ -24,7 +24,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -class WebfingerAction extends Action +class XrdAction extends Action { public $uri; @@ -40,11 +40,11 @@ class WebfingerAction extends Action function handle() { - $acct = Webfinger::normalize($this->uri); + $acct = Discovery::normalize($this->uri); $xrd = new XRD(); - list($nick, $domain) = explode('@', urldecode($acct)); + list($nick, $domain) = explode('@', substr(urldecode($acct), 5)); $nick = common_canonical_nickname($nick); $this->user = User::staticGet('nickname', $nick); @@ -55,18 +55,18 @@ class WebfingerAction extends Action $xrd->subject = $this->uri; $xrd->alias[] = common_profile_url($nick); - $xrd->links[] = array('rel' => Webfinger::PROFILEPAGE, + $xrd->links[] = array('rel' => Discovery::PROFILEPAGE, 'type' => 'text/html', 'href' => common_profile_url($nick)); - $xrd->links[] = array('rel' => Webfinger::UPDATESFROM, + $xrd->links[] = array('rel' => Discovery::UPDATESFROM, 'href' => common_local_url('ApiTimelineUser', array('id' => $this->user->id, 'format' => 'atom')), 'type' => 'application/atom+xml'); // hCard - $xrd->links[] = array('rel' => Webfinger::HCARD, + $xrd->links[] = array('rel' => Discovery::HCARD, 'type' => 'text/html', 'href' => common_local_url('hcard', array('nickname' => $nick))); @@ -80,10 +80,14 @@ class WebfingerAction extends Action 'href' => common_local_url('foaf', array('nickname' => $nick))); - $salmon_url = common_local_url('salmon', + // Salmon + $salmon_url = common_local_url('usersalmon', array('id' => $this->user->id)); - $xrd->links[] = array('rel' => 'salmon', + $xrd->links[] = array('rel' => Salmon::NS_REPLIES, + 'href' => $salmon_url); + + $xrd->links[] = array('rel' => Salmon::NS_MENTIONS, 'href' => $salmon_url); // Get this user's keypair @@ -91,11 +95,11 @@ class WebfingerAction extends Action if (!$magickey) { // No keypair yet, let's generate one. $magickey = new Magicsig(); - $magickey->generate(); + $magickey->generate($this->user->id); } $xrd->links[] = array('rel' => Magicsig::PUBLICKEYREL, - 'href' => 'data:application/magic-public-key;'. $magickey->keypair); + 'href' => 'data:application/magic-public-key;'. $magickey->toString(false)); // TODO - finalize where the redirect should go on the publisher $url = common_local_url('ostatussub') . '?profile={uri}'; diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 681aec184..751527c81 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -49,7 +49,8 @@ class Magicsig extends Memcached_DataObject public /*static*/ function staticGet($k, $v=null) { - return parent::staticGet(__CLASS__, $k, $v); + $obj = parent::staticGet(__CLASS__, $k, $v); + return Magicsig::fromString($obj->keypair); } @@ -90,7 +91,7 @@ class Magicsig extends Memcached_DataObject return parent::insert(); } - public function generate($key_length = 512) + public function generate($user_id, $key_length = 512) { PEAR::pushErrorHandling(PEAR_ERROR_RETURN); @@ -101,6 +102,7 @@ class Magicsig extends Memcached_DataObject $this->_rsa = new Crypt_RSA($params); PEAR::popErrorHandling(); + $this->user_id = $user_id; $this->insert(); } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 75b4bef41..35539bff7 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -150,27 +150,7 @@ class Ostatus_profile extends Memcached_DataObject function asActivityObject() { if ($this->isGroup()) { - $object = new ActivityObject(); - $object->type = 'http://activitystrea.ms/schema/1.0/group'; - $object->id = $this->uri; - $self = $this->localGroup(); - - // @fixme put a standard getAvatar() interface on groups too - if ($self->homepage_logo) { - $object->avatar = $self->homepage_logo; - $map = array('png' => 'image/png', - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'gif' => 'image/gif'); - $extension = pathinfo(parse_url($object->avatar, PHP_URL_PATH), PATHINFO_EXTENSION); - if (isset($map[$extension])) { - // @fixme this ain't used/saved yet - $object->avatarType = $map[$extension]; - } - } - - $object->link = $this->uri; // @fixme accurate? - return $object; + return ActivityObject::fromGroup($this->localGroup()); } else { return ActivityObject::fromProfile($this->localProfile()); } @@ -189,57 +169,13 @@ class Ostatus_profile extends Memcached_DataObject */ function asActivityNoun($element) { - $xs = new XMLStringer(true); - $avatarHref = Avatar::defaultImage(AVATAR_PROFILE_SIZE); - $avatarType = 'image/png'; if ($this->isGroup()) { - $type = 'http://activitystrea.ms/schema/1.0/group'; - $self = $this->localGroup(); - - // @fixme put a standard getAvatar() interface on groups too - if ($self->homepage_logo) { - $avatarHref = $self->homepage_logo; - $map = array('png' => 'image/png', - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'gif' => 'image/gif'); - $extension = pathinfo(parse_url($avatarHref, PHP_URL_PATH), PATHINFO_EXTENSION); - if (isset($map[$extension])) { - $avatarType = $map[$extension]; - } - } + $noun = ActivityObject::fromGroup($this->localGroup()); + return $noun->asString('activity:' . $element); } else { - $type = 'http://activitystrea.ms/schema/1.0/person'; - $self = $this->localProfile(); - $avatar = $self->getAvatar(AVATAR_PROFILE_SIZE); - if ($avatar) { - $avatarHref = $avatar->url; - $avatarType = $avatar->mediatype; - } + $noun = ActivityObject::fromProfile($this->localProfile()); + return $noun->asString('activity:' . $element); } - $xs->elementStart('activity:' . $element); - $xs->element( - 'activity:object-type', - null, - $type - ); - $xs->element( - 'id', - null, - $this->uri); // ? - $xs->element('title', null, $self->getBestName()); - - $xs->element( - 'link', array( - 'type' => $avatarType, - 'href' => $avatarHref - ), - '' - ); - - $xs->elementEnd('activity:' . $element); - - return $xs->getString(); } /** @@ -421,7 +357,7 @@ class Ostatus_profile extends Memcached_DataObject common_log(LOG_INFO, "Posting to Salmon endpoint $this->salmonuri: $xml"); $salmon = new Salmon(); // ? - return $salmon->post($this->salmonuri, $xml); + return $salmon->post($this->salmonuri, $xml, $actor); } return false; } @@ -433,11 +369,11 @@ class Ostatus_profile extends Memcached_DataObject * @param mixed $entry XML string, Notice, or Activity * @return boolean success */ - public function notifyActivity($entry) + public function notifyActivity($entry, $actor) { if ($this->salmonuri) { $salmon = new Salmon(); - return $salmon->post($this->salmonuri, $this->notifyPrepXml($entry)); + return $salmon->post($this->salmonuri, $this->notifyPrepXml($entry), $actor); } return false; @@ -450,11 +386,12 @@ class Ostatus_profile extends Memcached_DataObject * @param mixed $entry XML string, Notice, or Activity * @return boolean success */ - public function notifyDeferred($entry) + public function notifyDeferred($entry, $actor) { if ($this->salmonuri) { $data = array('salmonuri' => $this->salmonuri, - 'entry' => $this->notifyPrepXml($entry)); + 'entry' => $this->notifyPrepXml($entry), + 'actor' => $actor->id); $qm = QueueManager::get(); return $qm->enqueue($data, 'salmon'); @@ -486,36 +423,6 @@ class Ostatus_profile extends Memcached_DataObject } } - function atomFeed($actor) - { - $feed = new Atom10Feed(); - // @fixme should these be set up somewhere else? - $feed->addNamespace('activity', 'http://activitystrea.ms/spec/1.0/'); - $feed->addNamespace('thr', 'http://purl.org/syndication/thread/1.0'); - $feed->addNamespace('georss', 'http://www.georss.org/georss'); - $feed->addNamespace('ostatus', 'http://ostatus.org/schema/1.0'); - - $taguribase = common_config('integration', 'taguri'); - $feed->setId("tag:{$taguribase}:UserTimeline:{$actor->id}"); // ??? - - $feed->setTitle($actor->getBestName() . ' timeline'); // @fixme - $feed->setUpdated(time()); - $feed->setPublished(time()); - - $feed->addLink(common_local_url('ApiTimelineUser', - array('id' => $actor->id, - 'type' => 'atom')), - array('rel' => 'self', - 'type' => 'application/atom+xml')); - - $feed->addLink(common_local_url('userbyid', - array('id' => $actor->id)), - array('rel' => 'alternate', - 'type' => 'text/html')); - - return $feed; - } - /** * Read and post notices for updates from the feed. * Currently assumes that all items in the feed are new, @@ -791,11 +698,18 @@ class Ostatus_profile extends Memcached_DataObject { // Get the canonical feed URI and check it $discover = new FeedDiscovery(); - $feeduri = $discover->discoverFromURL($profile_uri); + if ($hints['feedurl']) { + $feeduri = $hints['feedurl']; + $feeduri = $discover->discoverFromFeedURL($feeduri); + } else { + $feeduri = $discover->discoverFromURL($profile_uri); + $hints['feedurl'] = $feeduri; + } - //$feedsub = FeedSub::ensureFeed($feeduri, $discover->feed); $huburi = $discover->getAtomLink('hub'); - $salmonuri = $discover->getAtomLink('salmon'); + $hints['hub'] = $huburi; + $salmonuri = $discover->getAtomLink(Salmon::NS_REPLIES); + $hints['salmon'] = $salmonuri; if (!$huburi) { // We can only deal with folks with a PuSH hub @@ -810,7 +724,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($subject)) { $subjObject = new ActivityObject($subject); - return self::ensureActivityObjectProfile($subjObject, $feeduri, $salmonuri, $hints); + return self::ensureActivityObjectProfile($subjObject, $hints); } // Otherwise, try the feed author @@ -819,7 +733,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($author)) { $authorObject = new ActivityObject($author); - return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri, $hints); + return self::ensureActivityObjectProfile($authorObject, $hints); } // Sheesh. Not a very nice feed! Let's try fingerpoken in the @@ -835,7 +749,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($actor)) { $actorObject = new ActivityObject($actor); - return self::ensureActivityObjectProfile($actorObject, $feeduri, $salmonuri, $hints); + return self::ensureActivityObjectProfile($actorObject, $hints); } @@ -843,7 +757,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($author)) { $authorObject = new ActivityObject($author); - return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri, $hints); + return self::ensureActivityObjectProfile($authorObject, $hints); } } @@ -988,18 +902,18 @@ class Ostatus_profile extends Memcached_DataObject * @return Ostatus_profile */ - public static function ensureActorProfile($activity, $feeduri=null, $salmonuri=null) + public static function ensureActorProfile($activity, $hints=array()) { - return self::ensureActivityObjectProfile($activity->actor, $feeduri, $salmonuri); + return self::ensureActivityObjectProfile($activity->actor, $hints); } - public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array()) + public static function ensureActivityObjectProfile($object, $hints=array()) { $profile = self::getActivityObjectProfile($object); if ($profile) { $profile->updateFromActivityObject($object, $hints); } else { - $profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri, $hints); + $profile = self::createActivityObjectProfile($object, $hints); } return $profile; } @@ -1045,58 +959,55 @@ class Ostatus_profile extends Memcached_DataObject * @fixme validate stuff somewhere */ - protected static function createActorProfile($activity, $feeduri=null, $salmonuri=null) - { - $actor = $activity->actor; - - self::createActivityObjectProfile($actor, $feeduri, $salmonuri); - } - /** * Create local ostatus_profile and profile/user_group entries for * the provided remote user or group. * * @param ActivityObject $object - * @param string $feeduri - * @param string $salmonuri * @param array $hints * - * @fixme fold $feeduri/$salmonuri into $hints * @return Ostatus_profile */ - protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array()) + protected static function createActivityObjectProfile($object, $hints=array()) { - $homeuri = $object->id; + $homeuri = $object->id; + $discover = false; if (!$homeuri) { common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true)); throw new ServerException("No profile URI"); } - if (empty($feeduri)) { - if (array_key_exists('feedurl', $hints)) { - $feeduri = $hints['feedurl']; - } + if (array_key_exists('feedurl', $hints)) { + $feeduri = $hints['feedurl']; + } else { + $discover = new FeedDiscovery(); + $feeduri = $discover->discoverFromURL($homeuri); } - if (empty($salmonuri)) { - if (array_key_exists('salmon', $hints)) { - $salmonuri = $hints['salmon']; + if (array_key_exists('salmon', $hints)) { + $salmonuri = $hints['salmon']; + } else { + if (!$discover) { + $discover = new FeedDiscovery(); + $discover->discoverFromFeedURL($hints['feedurl']); } + $salmonuri = $discover->getAtomLink(Salmon::NS_REPLIES); } - if (!$feeduri || !$salmonuri) { - // Get the canonical feed URI and check it - $discover = new FeedDiscovery(); - $feeduri = $discover->discoverFromURL($homeuri); - + if (array_key_exists('hub', $hints)) { + $huburi = $hints['hub']; + } else { + if (!$discover) { + $discover = new FeedDiscovery(); + $discover->discoverFromFeedURL($hints['feedurl']); + } $huburi = $discover->getAtomLink('hub'); - $salmonuri = $discover->getAtomLink('salmon'); + } - if (!$huburi) { - // We can only deal with folks with a PuSH hub - throw new FeedSubNoHubException(); - } + if (!$huburi) { + // We can only deal with folks with a PuSH hub + throw new FeedSubNoHubException(); } $oprofile = new Ostatus_profile(); @@ -1375,9 +1286,9 @@ class Ostatus_profile extends Memcached_DataObject // Now, try some discovery - $wf = new Webfinger(); + $disco = new Discovery(); - $result = $wf->lookup($addr); + $result = $disco->lookup($addr); if (!$result) { self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null); @@ -1386,16 +1297,16 @@ class Ostatus_profile extends Memcached_DataObject foreach ($result->links as $link) { switch ($link['rel']) { - case Webfinger::PROFILEPAGE: + case Discovery::PROFILEPAGE: $profileUrl = $link['href']; break; - case 'salmon': + case Salmon::NS_REPLIES: $salmonEndpoint = $link['href']; break; - case Webfinger::UPDATESFROM: + case Discovery::UPDATESFROM: $feedUrl = $link['href']; break; - case Webfinger::HCARD: + case Discovery::HCARD: $hcardUrl = $link['href']; break; default: diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php new file mode 100644 index 000000000..388df0a28 --- /dev/null +++ b/plugins/OStatus/lib/discovery.php @@ -0,0 +1,310 @@ +<?php +/** + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * A sample module to show best practices for StatusNet plugins + * + * PHP version 5 + * + * 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 StatusNet + * @author James Walker <james@status.net> + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +/** + * This class implements LRDD-based service discovery based on the "Hammer Draft" + * (including webfinger) + * + * @see http://groups.google.com/group/webfinger/browse_thread/thread/9f3d93a479e91bbf + */ +class Discovery +{ + + const LRDD_REL = 'lrdd'; + const PROFILEPAGE = 'http://webfinger.net/rel/profile-page'; + const UPDATESFROM = 'http://schemas.google.com/g/2010#updates-from'; + const HCARD = 'http://microformats.org/profile/hcard'; + + public $methods = array(); + + public function __construct() + { + $this->registerMethod('Discovery_LRDD_Host_Meta'); + $this->registerMethod('Discovery_LRDD_Link_Header'); + $this->registerMethod('Discovery_LRDD_Link_HTML'); + } + + + public function registerMethod($class) + { + $this->methods[] = $class; + } + + /** + * Given a "user id" make sure it's normalized to either a webfinger + * acct: uri or a profile HTTP URL. + */ + public static function normalize($user_id) + { + if (substr($user_id, 0, 5) == 'http:' || + substr($user_id, 0, 6) == 'https:' || + substr($user_id, 0, 5) == 'acct:') { + return $user_id; + } + + if (strpos($user_id, '@') !== FALSE) { + return 'acct:' . $user_id; + } + + return 'http://' . $user_id; + } + + public static function isWebfinger($user_id) + { + $uri = Discovery::normalize($user_id); + + return (substr($uri, 0, 5) == 'acct:'); + } + + /** + * This implements the actual lookup procedure + */ + public function lookup($id) + { + // Normalize the incoming $id to make sure we have a uri + $uri = $this->normalize($id); + + foreach ($this->methods as $class) { + $links = call_user_func(array($class, 'discover'), $uri); + if ($link = Discovery::getService($links, Discovery::LRDD_REL)) { + // Load the LRDD XRD + if ($link['template']) { + $xrd_uri = Discovery::applyTemplate($link['template'], $uri); + } else { + $xrd_uri = $link['href']; + } + + $xrd = $this->fetchXrd($xrd_uri); + if ($xrd) { + return $xrd; + } + } + } + + throw new Exception('Unable to find services for '. $id); + } + + public static function getService($links, $service) { + if (!is_array($links)) { + return false; + } + + foreach ($links as $link) { + if ($link['rel'] == $service) { + return $link; + } + } + } + + + public static function applyTemplate($template, $id) + { + $template = str_replace('{uri}', urlencode($id), $template); + + return $template; + } + + + public static function fetchXrd($url) + { + try { + $client = new HTTPClient(); + $response = $client->get($url); + } catch (HTTP_Request2_Exception $e) { + return false; + } + + if ($response->getStatus() != 200) { + return false; + } + + return XRD::parse($response->getBody()); + } +} + +interface Discovery_LRDD +{ + public function discover($uri); +} + +class Discovery_LRDD_Host_Meta implements Discovery_LRDD +{ + public function discover($uri) + { + if (!Discovery::isWebfinger($uri)) { + return false; + } + + // We have a webfinger acct: - start with host-meta + list($name, $domain) = explode('@', $uri); + $url = 'http://'. $domain .'/.well-known/host-meta'; + + $xrd = Discovery::fetchXrd($url); + + if ($xrd) { + if ($xrd->host != $domain) { + return false; + } + + return $xrd->links; + } + } +} + +class Discovery_LRDD_Link_Header implements Discovery_LRDD +{ + public function discover($uri) + { + try { + $client = new HTTPClient(); + $response = $client->get($uri); + } catch (HTTP_Request2_Exception $e) { + return false; + } + + if ($response->getStatus() != 200) { + return false; + } + + $link_header = $response->getHeader('Link'); + if (!$link_header) { + // return false; + } + + return Discovery_LRDD_Link_Header::parseHeader($link_header); + } + + protected static function parseHeader($header) + { + preg_match('/^<[^>]+>/', $header, $uri_reference); + //if (empty($uri_reference)) return; + + $links = array(); + + $link_uri = trim($uri_reference[0], '<>'); + $link_rel = array(); + $link_type = null; + + // remove uri-reference from header + $header = substr($header, strlen($uri_reference[0])); + + // parse link-params + $params = explode(';', $header); + + foreach ($params as $param) { + if (empty($param)) continue; + list($param_name, $param_value) = explode('=', $param, 2); + $param_name = trim($param_name); + $param_value = preg_replace('(^"|"$)', '', trim($param_value)); + + // for now we only care about 'rel' and 'type' link params + // TODO do something with the other links-params + switch ($param_name) { + case 'rel': + $link_rel = trim($param_value); + break; + + case 'type': + $link_type = trim($param_value); + } + } + + $links[] = array( + 'href' => $link_uri, + 'rel' => $link_rel, + 'type' => $link_type); + + return $links; + } +} + +class Discovery_LRDD_Link_HTML implements Discovery_LRDD +{ + public function discover($uri) + { + try { + $client = new HTTPClient(); + $response = $client->get($uri); + } catch (HTTP_Request2_Exception $e) { + return false; + } + + if ($response->getStatus() != 200) { + return false; + } + + return Discovery_LRDD_Link_HTML::parse($response->getBody()); + } + + + public function parse($html) + { + $links = array(); + + preg_match('/<head(\s[^>]*)?>(.*?)<\/head>/is', $html, $head_matches); + $head_html = $head_matches[2]; + + preg_match_all('/<link\s[^>]*>/i', $head_html, $link_matches); + + foreach ($link_matches[0] as $link_html) { + $link_url = null; + $link_rel = null; + $link_type = null; + + preg_match('/\srel=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $rel_matches); + if ( isset($rel_matches[3]) ) { + $link_rel = $rel_matches[3]; + } else if ( isset($rel_matches[1]) ) { + $link_rel = $rel_matches[1]; + } + + preg_match('/\shref=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $href_matches); + if ( isset($href_matches[3]) ) { + $link_uri = $href_matches[3]; + } else if ( isset($href_matches[1]) ) { + $link_uri = $href_matches[1]; + } + + preg_match('/\stype=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $type_matches); + if ( isset($type_matches[3]) ) { + $link_type = $type_matches[3]; + } else if ( isset($type_matches[1]) ) { + $link_type = $type_matches[1]; + } + + $links[] = array( + 'href' => $link_url, + 'rel' => $link_rel, + 'type' => $link_type, + ); + } + + return $links; + } +} diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index 81f4609c5..f33119b8f 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -50,19 +50,26 @@ class MagicEnvelope public function getKeyPair($signer_uri) { - return 'RSA.79_L2gq-TD72Nsb5yGS0r9stLLpJZF5AHXyxzWmQmlqKl276LEJEs8CppcerLcR90MbYQUwt-SX9slx40Yq3vA==.AQAB.AR-jo5KMfSISmDAT2iMs2_vNFgWRjl5rbJVvA0SpGIEWyPdCGxlPtCbTexp8-0ZEIe8a4SyjatBECH5hxgMTpw=='; - } - + $disco = new Discovery(); - public function signMessage($text, $mimetype, $signer_uri) - { - $signer_uri = $this->normalizeUser($signer_uri); - - if (!$this->checkAuthor($text, $signer_uri)) { + try { + $xrd = $disco->lookup($signer_uri); + } catch (Exception $e) { return false; } + if ($xrd->links) { + if ($link = Discovery::getService($xrd->links, Magicsig::PUBLICKEYREL)) { + list($type, $keypair) = explode(';', $link['href']); + return $keypair; + } + } + throw new Exception('Unable to locate signer public key'); + } - $signature_alg = Magicsig::fromString($this->getKeyPair($signer_uri)); + + public function signMessage($text, $mimetype, $keypair) + { + $signature_alg = Magicsig::fromString($keypair); $armored_text = base64_encode($text); return array( diff --git a/plugins/OStatus/lib/ostatusqueuehandler.php b/plugins/OStatus/lib/ostatusqueuehandler.php index 0da85600f..6ca31c485 100644 --- a/plugins/OStatus/lib/ostatusqueuehandler.php +++ b/plugins/OStatus/lib/ostatusqueuehandler.php @@ -87,7 +87,7 @@ class OStatusQueueHandler extends QueueHandler // remote user or group. // @fixme as an optimization we can skip this if the // remote profile is subscribed to the author. - $oprofile->notifyDeferred($this->notice); + $oprofile->notifyDeferred($this->notice, $this->user); } } diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index b5f178cc6..6e2459544 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -28,6 +28,11 @@ */ class Salmon { + + const NS_REPLIES = "http://salmon-protocol.org/ns/salmon-replies"; + + const NS_MENTIONS = "http://salmon-protocol.org/ns/salmon-mention"; + /** * Sign and post the given Atom entry as a Salmon message. * @@ -37,14 +42,14 @@ class Salmon * @param string $xml * @return boolean success */ - public function post($endpoint_uri, $xml) + public function post($endpoint_uri, $xml, $actor) { if (empty($endpoint_uri)) { return false; } if (!common_config('ostatus', 'skip_signatures')) { - $xml = $this->createMagicEnv($xml); + $xml = $this->createMagicEnv($xml, $actor); } $headers = array('Content-Type: application/atom+xml'); @@ -65,15 +70,31 @@ class Salmon return true; } - public function createMagicEnv($text) + public function createMagicEnv($text, $actor) { + common_log(LOG_DEBUG, "Got actor as : ". print_r($actor, true)); $magic_env = new MagicEnvelope(); - // TODO: Should probably be getting the signer uri as an argument? - $signer_uri = $magic_env->getAuthor($text); - - $env = $magic_env->signMessage($text, 'application/atom+xml', $signer_uri); + $user = User::staticGet('id', $actor->id); + if ($user->id) { + // Use local key + $magickey = Magicsig::staticGet('user_id', $user->id); + if (!$magickey) { + // No keypair yet, let's generate one. + $magickey = new Magicsig(); + $magickey->generate($user->id); + } + common_log(LOG_DEBUG, "Salmon: Loaded key for ". $user->id); + } else { + throw new Exception("Salmon invalid actor for signing"); + } + try { + $env = $magic_env->signMessage($text, 'application/atom+xml', $magickey->toString()); + } catch (Exception $e) { + common_log(LOG_ERR, "Salmon signing failed: ". $e->getMessage()); + return $text; + } return $magic_env->unfold($env); } diff --git a/plugins/OStatus/lib/salmonqueuehandler.php b/plugins/OStatus/lib/salmonqueuehandler.php index aa97018dc..7eeb5f8e9 100644 --- a/plugins/OStatus/lib/salmonqueuehandler.php +++ b/plugins/OStatus/lib/salmonqueuehandler.php @@ -35,8 +35,10 @@ class SalmonQueueHandler extends QueueHandler assert(is_string($data['salmonuri'])); assert(is_string($data['entry'])); + $actor = Profile::staticGet($data['actor']); + $salmon = new Salmon(); - $salmon->post($data['salmonuri'], $data['entry']); + $salmon->post($data['salmonuri'], $data['entry'], $actor); // @fixme detect failure and attempt to resend return true; diff --git a/plugins/OStatus/lib/webfinger.php b/plugins/OStatus/lib/webfinger.php deleted file mode 100644 index 4b777c9a0..000000000 --- a/plugins/OStatus/lib/webfinger.php +++ /dev/null @@ -1,164 +0,0 @@ -<?php -/** - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2010, StatusNet, Inc. - * - * A sample module to show best practices for StatusNet plugins - * - * PHP version 5 - * - * 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 StatusNet - * @author James Walker <james@status.net> - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -define('WEBFINGER_SERVICE_REL_VALUE', 'lrdd'); - -/** - * Implement the webfinger protocol. - */ - -class Webfinger -{ - const PROFILEPAGE = 'http://webfinger.net/rel/profile-page'; - const UPDATESFROM = 'http://schemas.google.com/g/2010#updates-from'; - const HCARD = 'http://microformats.org/profile/hcard'; - - /** - * Perform a webfinger lookup given an account. - */ - - public function lookup($id) - { - $id = $this->normalize($id); - list($name, $domain) = explode('@', $id); - - $links = $this->getServiceLinks($domain); - if (!$links) { - return false; - } - - $services = array(); - foreach ($links as $link) { - if ($link['template']) { - return $this->getServiceDescription($link['template'], $id); - } - if ($link['href']) { - return $this->getServiceDescription($link['href'], $id); - } - } - } - - /** - * Normalize an account ID - */ - function normalize($id) - { - if (substr($id, 0, 7) == 'acct://') { - return substr($id, 7); - } else if (substr($id, 0, 5) == 'acct:') { - return substr($id, 5); - } - - return $id; - } - - function getServiceLinks($domain) - { - $url = 'http://'. $domain .'/.well-known/host-meta'; - - $content = $this->fetchURL($url); - - if (empty($content)) { - common_log(LOG_DEBUG, 'Error fetching host-meta'); - return false; - } - - $result = XRD::parse($content); - - // Ensure that the host == domain (spec may include signing later) - if ($result->host != $domain) { - return false; - } - - $links = array(); - foreach ($result->links as $link) { - if ($link['rel'] == WEBFINGER_SERVICE_REL_VALUE) { - $links[] = $link; - } - - } - return $links; - } - - function getServiceDescription($template, $id) - { - $url = $this->applyTemplate($template, 'acct:' . $id); - - $content = $this->fetchURL($url); - - if (!$content) { - return false; - } - - return XRD::parse($content); - } - - function fetchURL($url) - { - try { - $c = Cache::instance(); - $content = $c->get('webfinger:url:'.$url); - if ($content !== false) { - return $content; - } - $client = new HTTPClient(); - $response = $client->get($url); - } catch (HTTP_Request2_Exception $e) { - return false; - } - - if ($response->getStatus() != 200) { - return false; - } - - $body = $response->getBody(); - - $c->set('webfinger:url:'.$url, $body); - - return $body; - } - - function applyTemplate($template, $id) - { - $template = str_replace('{uri}', urlencode($id), $template); - - return $template; - } - - function getHostMeta($domain, $template) { - $xrd = new XRD(); - $xrd->host = $domain; - $xrd->links[] = array('rel' => 'lrdd', - 'template' => $template, - 'title' => array('Resource Descriptor')); - - return $xrd->toXML(); - } -} - diff --git a/scripts/createsim.php b/scripts/createsim.php index 882d74456..e0b5fc906 100644 --- a/scripts/createsim.php +++ b/scripts/createsim.php @@ -119,6 +119,9 @@ function newSub($i) function main($usercount, $noticeavg, $subsavg, $tagmax) { + global $config; + $config['site']['dupelimit'] = -1; + $n = 1; newUser(0); diff --git a/tests/ActivityParseTests.php b/tests/ActivityParseTests.php index d1d871734..7bf9cec7c 100644 --- a/tests/ActivityParseTests.php +++ b/tests/ActivityParseTests.php @@ -121,10 +121,14 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase $this->assertEquals($act->actor->title, 'Test User'); $this->assertEquals($act->actor->id, 'http://example.net/mysite/user/3'); $this->assertEquals($act->actor->link, 'http://example.net/mysite/testuser'); + + $avatars = $act->actor->avatarLinks; + $this->assertEquals( - $act->actor->avatar, - 'http://example.net/mysite/avatar/3-96-20100224004207.jpeg' + $avatars[0]->url, + 'http://example.net/mysite/avatar/3-96-20100224004207.jpeg' ); + $this->assertEquals($act->actor->displayName, 'Test User'); $poco = $act->actor->poco; |