From e868ebfe77d6a83d3d94add10445a8946480c81f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 20 Oct 2013 15:32:56 +0200 Subject: [PATCH] WebFingerResource introduced, instead of strict Profile object This is the beginning of getting notice URI info via WebFinger *XrdActionLinks is renamed *WebFingerProfileLinks, check EVENTS.txt in WebFinger plugin for new events. --- classes/Notice.php | 6 ++ plugins/OStatus/OStatusPlugin.php | 2 +- plugins/OStatus/lib/discoveryhints.php | 2 +- plugins/OpenID/OpenIDPlugin.php | 2 +- plugins/WebFinger/EVENTS.txt | 26 ++--- plugins/WebFinger/WebFingerPlugin.php | 42 ++++++++ plugins/WebFinger/actions/webfinger.php | 80 ++------------ plugins/WebFinger/lib/webfinger.php | 100 ------------------ plugins/WebFinger/lib/webfingerresource.php | 54 ++++++++++ .../lib/webfingerresource/notice.php | 27 +++++ .../lib/webfingerresource/profile.php | 87 +++++++++++++++ plugins/WebFinger/lib/xrdaction.php | 2 - 12 files changed, 243 insertions(+), 187 deletions(-) delete mode 100644 plugins/WebFinger/lib/webfinger.php create mode 100644 plugins/WebFinger/lib/webfingerresource.php create mode 100644 plugins/WebFinger/lib/webfingerresource/notice.php create mode 100644 plugins/WebFinger/lib/webfingerresource/profile.php diff --git a/classes/Notice.php b/classes/Notice.php index 4812dff91a..2de4a889fa 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -216,6 +216,12 @@ class Notice extends Managed_DataObject return $this->uri; } + public function getUrl() + { + // The risk is we start having empty urls and non-http uris... + return $this->url ?: $this->uri; + } + /** * Extract #hashtags from this notice's content and save them to the database. */ diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 22d51de80a..84f2a5e9ca 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -1314,7 +1314,7 @@ class OStatusPlugin extends Plugin return true; } - function onEndXrdActionLinks(XML_XRD $xrd, Profile $target) + function onEndWebFingerProfileLinks(XML_XRD $xrd, Profile $target) { $xrd->links[] = new XML_XRD_Element_Link(Discovery::UPDATESFROM, common_local_url('ApiTimelineUser', diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index a868e73922..ae2d5ad618 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -26,7 +26,7 @@ class DiscoveryHints { foreach ($xrd->links as $link) { switch ($link->rel) { - case WebFinger::PROFILEPAGE: + case WebFingerResource::PROFILEPAGE: $hints['profileurl'] = $link->href; break; case Salmon::NS_MENTIONS: diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index e48b616f4d..293eb3155a 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -773,7 +773,7 @@ class OpenIDPlugin extends Plugin * @return boolean hook value (always true) */ - function onEndXrdActionLinks(XML_XRD $xrd, Profile $target) + function onEndWebFingerProfileLinks(XML_XRD $xrd, Profile $target) { $xrd->links[] = new XML_XRD_Element_Link( 'http://specs.openid.net/auth/2.0/provider', diff --git a/plugins/WebFinger/EVENTS.txt b/plugins/WebFinger/EVENTS.txt index 81641e9906..05e2b2a0f7 100644 --- a/plugins/WebFinger/EVENTS.txt +++ b/plugins/WebFinger/EVENTS.txt @@ -4,26 +4,28 @@ StartHostMetaLinks: Start /.well-known/host-meta links EndHostMetaLinks: End /.well-known/host-meta links - &links: array containing the links elements to be written -StartWebFingerReconstruction: +StartGetWebFingerResource: Get a WebFingerResource extended object by resource string +- $resource String that contains the requested URI +- &$target WebFingerResource extended object goes here +- $args Array which may contains arguments such as 'rel' filtering values + +EndGetWebFingerResource: Last attempts getting a WebFingerResource object +- $resource String that contains the requested URI +- &$target WebFingerResource extended object goes here +- $args Array which may contains arguments such as 'rel' filtering values + +StartWebFingerReconstruction: Generate an acct: uri from a Profile object - $profile: Profile object for which we want a WebFinger ID - &$acct: String reference where reconstructed ID is stored -EndWebFingerReconstruction: +EndWebFingerReconstruction: Last attempts to generate an acct: uri from a Profile object - $profile: Profile object for which we want a WebFinger ID - &$acct: String reference where reconstructed ID is stored -StartXrdActionAliases: About to set aliases for the XRD for a user +StartWebFingerProfileLinks: About to set links for the resource descriptor of a profile - $xrd: XML_XRD object being shown - $target: Profile being shown -EndXrdActionAliases: Done with aliases for the XRD for a user -- $xrd: XML_XRD object being shown -- $target: Profile being shown - -StartXrdActionLinks: About to set links for the XRD for a profile -- $xrd: XML_XRD object being shown -- $target: Profile being shown - -EndXrdActionLinks: Done with links for the XRD for a profile +EndWebFingerProfileLinks: Done with links for the resource descriptor of a profile - $xrd: XML_XRD object being shown - $target: Profile being shown diff --git a/plugins/WebFinger/WebFingerPlugin.php b/plugins/WebFinger/WebFingerPlugin.php index 8efce27d35..24200a6f36 100644 --- a/plugins/WebFinger/WebFingerPlugin.php +++ b/plugins/WebFinger/WebFingerPlugin.php @@ -58,6 +58,48 @@ class WebFingerPlugin extends Plugin return true; } + public function onEndGetWebFingerResource($resource, WebFingerResource &$target=null, array $args=array()) + { + $profile = null; + if (Discovery::isAcct($resource)) { + $parts = explode('@', substr(urldecode($resource), 5)); // 5 is strlen of 'acct:' + if (count($parts) == 2) { + list($nick, $domain) = $parts; + if ($domain === common_config('site', 'server')) { + $nick = common_canonical_nickname($nick); + $user = User::getKV('nickname', $nick); + if (!($user instanceof User)) { + throw new NoSuchUserException(array('nickname'=>$nick)); + } + $profile = $user->getProfile(); + } else { + throw new Exception(_('Remote profiles not supported via WebFinger yet.')); + } + } + } else { + $user = User::getKV('uri', $resource); + if ($user instanceof User) { + $profile = $user->getProfile(); + } else { + // try and get it by profile url + $profile = Profile::getKV('profileurl', $resource); + } + } + + if ($profile instanceof Profile) { + $target = new WebFingerResource_Profile($profile); + return false; // We got our target, stop handler execution + } + + $notice = Notice::getKV('uri', $resource); + if ($notice instanceof Notice) { + $target = new WebFingerResource_Notice($notice); + return false; + } + + return true; + } + public function onStartHostMetaLinks(array &$links) { foreach (Discovery::supportedMimeTypes() as $type) { diff --git a/plugins/WebFinger/actions/webfinger.php b/plugins/WebFinger/actions/webfinger.php index ff717de400..92ab124147 100644 --- a/plugins/WebFinger/actions/webfinger.php +++ b/plugins/WebFinger/actions/webfinger.php @@ -26,6 +26,9 @@ if (!defined('GNUSOCIAL')) { exit(1); } */ class WebfingerAction extends XrdAction { + protected $resource = null; // string with the resource URI + protected $target = null; // object of the WebFingerResource class + protected function prepare(array $args=array()) { parent::prepare($args); @@ -33,34 +36,8 @@ class WebfingerAction extends XrdAction // throws exception if resource is empty $this->resource = Discovery::normalize($this->trimmed('resource')); - if (Discovery::isAcct($this->resource)) { - $parts = explode('@', substr(urldecode($this->resource), 5)); - if (count($parts) == 2) { - list($nick, $domain) = $parts; - if ($domain === common_config('site', 'server')) { - $nick = common_canonical_nickname($nick); - $user = User::getKV('nickname', $nick); - if (!($user instanceof User)) { - throw new NoSuchUserException(array('nickname'=>$nick)); - } - $this->target = $user->getProfile(); - } else { - throw new Exception(_('Remote profiles not supported via WebFinger yet.')); - } - } - } else { - $user = User::getKV('uri', $this->resource); - if ($user instanceof User) { - $this->target = $user->getProfile(); - } else { - // try and get it by profile url - $this->target = Profile::getKV('profileurl', $this->resource); - } - } - - if (!($this->target instanceof Profile)) { - // TRANS: Client error displayed when user not found for an action. - $this->clientError(_('No such user: ') . var_export($this->resource,true), 404); + if (Event::handle('StartGetWebFingerResource', array($this->resource, &$this->target, $this->args))) { + Event::handle('EndGetWebFingerResource', array($this->resource, &$this->target, $this->args)); } return true; @@ -68,55 +45,18 @@ class WebfingerAction extends XrdAction protected function setXRD() { - if (empty($this->target)) { + if (!($this->target instanceof WebFingerResource)) { throw new Exception(_('Target not set for resource descriptor')); } - // $this->target set in a _child_ class prepare() - $nick = $this->target->nickname; - $this->xrd->subject = $this->resource; - if (Event::handle('StartXrdActionAliases', array($this->xrd, $this->target))) { - $uris = WebFinger::getIdentities($this->target); - foreach ($uris as $uri) { - if ($uri != $this->xrd->subject && !in_array($uri, $this->xrd->aliases)) { - $this->xrd->aliases[] = $uri; - } + foreach ($this->target->getAliases() as $alias) { + if ($alias != $this->xrd->subject && !in_array($alias, $this->xrd->aliases)) { + $this->xrd->aliases[] = $alias; } - Event::handle('EndXrdActionAliases', array($this->xrd, $this->target)); } - if (Event::handle('StartXrdActionLinks', array($this->xrd, $this->target))) { - - $this->xrd->links[] = new XML_XRD_Element_Link(WebFinger::PROFILEPAGE, - $this->target->getUrl(), 'text/html'); - - // XFN - $this->xrd->links[] = new XML_XRD_Element_Link('http://gmpg.org/xfn/11', - $this->target->getUrl(), 'text/html'); - // FOAF - $this->xrd->links[] = new XML_XRD_Element_Link('describedby', - common_local_url('foaf', array('nickname' => $nick)), - 'application/rdf+xml'); - - $link = new XML_XRD_Element_Link('http://apinamespace.org/atom', - common_local_url('ApiAtomService', array('id' => $nick)), - 'application/atomsvc+xml'); -// XML_XRD must implement changing properties first $link['http://apinamespace.org/atom/username'] = $nick; - $this->xrd->links[] = clone $link; - - if (common_config('site', 'fancy')) { - $apiRoot = common_path('api/', true); - } else { - $apiRoot = common_path('index.php/api/', true); - } - - $link = new XML_XRD_Element_Link('http://apinamespace.org/twitter', $apiRoot); -// XML_XRD must implement changing properties first $link['http://apinamespace.org/twitter/username'] = $nick; - $this->xrd->links[] = clone $link; - - Event::handle('EndXrdActionLinks', array($this->xrd, $this->target)); - } + $this->target->updateXRD($this->xrd); } } diff --git a/plugins/WebFinger/lib/webfinger.php b/plugins/WebFinger/lib/webfinger.php deleted file mode 100644 index 2acb430e8b..0000000000 --- a/plugins/WebFinger/lib/webfinger.php +++ /dev/null @@ -1,100 +0,0 @@ -. - * - * @package GNUsocial - * @author Mikael Nordfeldth - * @copyright 2013 Free Software Foundation, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -class WebFinger -{ - const PROFILEPAGE = 'http://webfinger.net/rel/profile-page'; - - /* - * Reconstructs a WebFinger ID from data we know about the profile. - * - * @param Profile $profile The profile we want a WebFinger ID for - * - * @return string $acct acct:user@example.com URI - */ - public static function reconstruct(Profile $profile) - { - $acct = null; - - if (Event::handle('StartWebFingerReconstruction', array($profile, &$acct))) { - // TODO: getUri may not always give us the correct host on remote users? - $host = parse_url($profile->getUri(), PHP_URL_HOST); - if (empty($profile->nickname) || empty($host)) { - throw new WebFingerReconstructionException($profile); - } - $acct = sprintf('acct:%s@%s', $profile->nickname, $host); - - Event::handle('EndWebFingerReconstruction', array($profile, &$acct)); - } - - return $acct; - } - - /* - * Gets all URI aliases for a Profile - * - * @param Profile $profile The profile we want aliases for - * - * @return array $aliases All the Profile's alternative URLs - */ - public static function getAliases(Profile $profile) - { - $aliases = array(); - $aliases[] = $profile->getUri(); - try { - $aliases[] = $profile->getUrl(); - } catch (InvalidUrlException $e) { - common_debug('Profile id='.$profile->id.' has invalid profileurl: ' . - var_export($profile->profileurl, true)); - } - return $aliases; - } - - /* - * Gets all identities for a Profile, includes WebFinger acct: if - * available, as well as alias URLs. - * - * @param Profile $profile The profile we want aliases for - * - * @return array $uris WebFinger acct: URI and alias URLs - */ - public static function getIdentities(Profile $profile) - { - $uris = array(); - try { - $uris[] = self::reconstruct($profile); - } catch (WebFingerReconstructionException $e) { - common_debug('WebFinger reconstruction for Profile failed, ' . - ' (id='.$profile->id.')'); - } - $uris = array_merge($uris, self::getAliases($profile)); - - return $uris; - } -} diff --git a/plugins/WebFinger/lib/webfingerresource.php b/plugins/WebFinger/lib/webfingerresource.php new file mode 100644 index 0000000000..c9fe1727e2 --- /dev/null +++ b/plugins/WebFinger/lib/webfingerresource.php @@ -0,0 +1,54 @@ +object = $object; + } + + public function getObject() + { + if ($this->object === null) { + throw new ServerException('Object is not set'); + } + return $this->object; + } + + public function getAliases() + { + $aliases = array(); + + // Add the URI as an identity, this is _not_ necessarily an HTTP url + $aliases[] = $this->object->getUri(); + + try { + $aliases[] = $this->object->getUrl(); + } catch (InvalidUrlException $e) { + // getUrl failed because no valid URL could be returned, just ignore it + } + + return $aliases; + } + + public function updateXRD(XML_XRD $xrd) { + $xrd->links[] = new XML_XRD_Element_Link(WebFingerResource::PROFILEPAGE, + $this->object->getUrl(), 'text/html'); + } +} diff --git a/plugins/WebFinger/lib/webfingerresource/notice.php b/plugins/WebFinger/lib/webfingerresource/notice.php new file mode 100644 index 0000000000..9510a690f7 --- /dev/null +++ b/plugins/WebFinger/lib/webfingerresource/notice.php @@ -0,0 +1,27 @@ +reconstructAcct(); + } catch (WebFingerReconstructionException $e) { + common_debug("WebFinger reconstruction for Profile failed (id={$this->object->id})"); + } + + return array_merge($aliases, parent::getAliases()); + } + + protected function reconstructAcct() + { + $acct = null; + + if (Event::handle('StartWebFingerReconstruction', array($this->object, &$acct))) { + // TODO: getUri may not always give us the correct host on remote users? + $host = parse_url($this->object->getUri(), PHP_URL_HOST); + if (empty($this->object->nickname) || empty($host)) { + throw new WebFingerReconstructionException($this->object); + } + $acct = sprintf('acct:%s@%s', $this->object->nickname, $host); + + Event::handle('EndWebFingerReconstruction', array($this->object, &$acct)); + } + + return $acct; + } + + public function updateXRD(XML_XRD $xrd) + { + if (Event::handle('StartWebFingerProfileLinks', array($xrd, $this->object))) { + + parent::updateXRD($xrd); + + // XFN + $xrd->links[] = new XML_XRD_Element_Link('http://gmpg.org/xfn/11', + $this->object->getUrl(), 'text/html'); + // FOAF + $xrd->links[] = new XML_XRD_Element_Link('describedby', + common_local_url('foaf', + array('nickname' => $this->object->nickname)), + 'application/rdf+xml'); + + $link = new XML_XRD_Element_Link('http://apinamespace.org/atom', + common_local_url('ApiAtomService', + array('id' => $this->object->nickname)), + 'application/atomsvc+xml'); +// XML_XRD must implement changing properties first $link['http://apinamespace.org/atom/username'] = $this->object->nickname; + $xrd->links[] = clone $link; + + if (common_config('site', 'fancy')) { + $apiRoot = common_path('api/', true); + } else { + $apiRoot = common_path('index.php/api/', true); + } + + $link = new XML_XRD_Element_Link('http://apinamespace.org/twitter', $apiRoot); +// XML_XRD must implement changing properties first $link['http://apinamespace.org/twitter/username'] = $this->object->nickname; + $xrd->links[] = clone $link; + + Event::handle('EndWebFingerProfileLinks', array($xrd, $this->object)); + } + } +} diff --git a/plugins/WebFinger/lib/xrdaction.php b/plugins/WebFinger/lib/xrdaction.php index 5089ad44e0..a57ccdb382 100644 --- a/plugins/WebFinger/lib/xrdaction.php +++ b/plugins/WebFinger/lib/xrdaction.php @@ -31,8 +31,6 @@ abstract class XrdAction extends Action // our back-compatibility with StatusNet <=1.1.1 protected $defaultformat = null; - protected $resource = null; - protected $target = null; protected $xrd = null; public function isReadOnly($args)