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.
This commit is contained in:
Mikael Nordfeldth 2013-10-20 15:32:56 +02:00
parent d632df320a
commit e868ebfe77
12 changed files with 243 additions and 187 deletions

View File

@ -216,6 +216,12 @@ class Notice extends Managed_DataObject
return $this->uri; 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. * Extract #hashtags from this notice's content and save them to the database.
*/ */

View File

@ -1314,7 +1314,7 @@ class OStatusPlugin extends Plugin
return true; 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, $xrd->links[] = new XML_XRD_Element_Link(Discovery::UPDATESFROM,
common_local_url('ApiTimelineUser', common_local_url('ApiTimelineUser',

View File

@ -26,7 +26,7 @@ class DiscoveryHints {
foreach ($xrd->links as $link) { foreach ($xrd->links as $link) {
switch ($link->rel) { switch ($link->rel) {
case WebFinger::PROFILEPAGE: case WebFingerResource::PROFILEPAGE:
$hints['profileurl'] = $link->href; $hints['profileurl'] = $link->href;
break; break;
case Salmon::NS_MENTIONS: case Salmon::NS_MENTIONS:

View File

@ -773,7 +773,7 @@ class OpenIDPlugin extends Plugin
* @return boolean hook value (always true) * @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( $xrd->links[] = new XML_XRD_Element_Link(
'http://specs.openid.net/auth/2.0/provider', 'http://specs.openid.net/auth/2.0/provider',

View File

@ -4,26 +4,28 @@ StartHostMetaLinks: Start /.well-known/host-meta links
EndHostMetaLinks: End /.well-known/host-meta links EndHostMetaLinks: End /.well-known/host-meta links
- &links: array containing the links elements to be written - &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 - $profile: Profile object for which we want a WebFinger ID
- &$acct: String reference where reconstructed ID is stored - &$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 - $profile: Profile object for which we want a WebFinger ID
- &$acct: String reference where reconstructed ID is stored - &$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 - $xrd: XML_XRD object being shown
- $target: Profile being shown - $target: Profile being shown
EndXrdActionAliases: Done with aliases for the XRD for a user EndWebFingerProfileLinks: Done with links for the resource descriptor of a profile
- $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
- $xrd: XML_XRD object being shown - $xrd: XML_XRD object being shown
- $target: Profile being shown - $target: Profile being shown

View File

@ -58,6 +58,48 @@ class WebFingerPlugin extends Plugin
return true; 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) public function onStartHostMetaLinks(array &$links)
{ {
foreach (Discovery::supportedMimeTypes() as $type) { foreach (Discovery::supportedMimeTypes() as $type) {

View File

@ -26,6 +26,9 @@ if (!defined('GNUSOCIAL')) { exit(1); }
*/ */
class WebfingerAction extends XrdAction 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()) protected function prepare(array $args=array())
{ {
parent::prepare($args); parent::prepare($args);
@ -33,34 +36,8 @@ class WebfingerAction extends XrdAction
// throws exception if resource is empty // throws exception if resource is empty
$this->resource = Discovery::normalize($this->trimmed('resource')); $this->resource = Discovery::normalize($this->trimmed('resource'));
if (Discovery::isAcct($this->resource)) { if (Event::handle('StartGetWebFingerResource', array($this->resource, &$this->target, $this->args))) {
$parts = explode('@', substr(urldecode($this->resource), 5)); Event::handle('EndGetWebFingerResource', array($this->resource, &$this->target, $this->args));
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);
} }
return true; return true;
@ -68,55 +45,18 @@ class WebfingerAction extends XrdAction
protected function setXRD() protected function setXRD()
{ {
if (empty($this->target)) { if (!($this->target instanceof WebFingerResource)) {
throw new Exception(_('Target not set for resource descriptor')); 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; $this->xrd->subject = $this->resource;
if (Event::handle('StartXrdActionAliases', array($this->xrd, $this->target))) { foreach ($this->target->getAliases() as $alias) {
$uris = WebFinger::getIdentities($this->target); if ($alias != $this->xrd->subject && !in_array($alias, $this->xrd->aliases)) {
foreach ($uris as $uri) { $this->xrd->aliases[] = $alias;
if ($uri != $this->xrd->subject && !in_array($uri, $this->xrd->aliases)) {
$this->xrd->aliases[] = $uri;
} }
} }
Event::handle('EndXrdActionAliases', array($this->xrd, $this->target));
}
if (Event::handle('StartXrdActionLinks', array($this->xrd, $this->target))) { $this->target->updateXRD($this->xrd);
$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));
}
} }
} }

View File

@ -1,100 +0,0 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* WebFinger functions
*
* 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 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;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* WebFinger resource parent class
*
* @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/
*/
abstract class WebFingerResource
{
const PROFILEPAGE = 'http://webfinger.net/rel/profile-page';
protected $identities = array();
protected $object = null;
protected $type = null;
public function __construct(Managed_DataObject $object)
{
$this->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');
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* WebFinger resource for Notice objects
*
* @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 WebFingerResource_Notice extends WebFingerResource
{
public function __construct(Notice $object)
{
// The type argument above verifies that it's our class
parent::__construct($object);
}
public function updateXRD(XML_XRD $xrd)
{
parent::updateXRD($xrd);
// TODO: Add atom and json representation links here
// TODO: Add Salmon/callback links and stuff here
}
}

View File

@ -0,0 +1,87 @@
<?php
/**
* WebFinger resource for Profile objects
*
* @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 WebFingerResource_Profile extends WebFingerResource
{
public function __construct(Profile $object)
{
// The type argument above verifies that it's our class
parent::__construct($object);
}
public function getAliases()
{
$aliases = array();
try {
// Try to create an acct: URI if we're dealing with a profile
$aliases[] = $this->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));
}
}
}

View File

@ -31,8 +31,6 @@ abstract class XrdAction extends Action
// our back-compatibility with StatusNet <=1.1.1 // our back-compatibility with StatusNet <=1.1.1
protected $defaultformat = null; protected $defaultformat = null;
protected $resource = null;
protected $target = null;
protected $xrd = null; protected $xrd = null;
public function isReadOnly($args) public function isReadOnly($args)