We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

Commit a0e107f1 authored by mattl's avatar mattl

Implemented WebFinger and replaced our XRD with PEAR XML_XRD

New plugins:
* LRDD
    LRDD implements client-side RFC6415 and RFC7033 resource descriptor
    discovery procedures. I.e. LRDD, host-meta and WebFinger stuff.

    OStatus and OpenID now depend on the LRDD plugin (XML_XRD).

* WebFinger
    This plugin implements the server-side of RFC6415 and RFC7033. Note:
    WebFinger technically doesn't handle XRD, but we serve both that and
    JRD (JSON Resource Descriptor), depending on Accept header and one
    ugly hack to check for old StatusNet installations.

    WebFinger depends on LRDD.

We might make this even prettier by using Net_WebFinger, but it is not
currently RFC7033 compliant (no /.well-known/webfinger resource GETs).

Disabling the WebFinger plugin would effectively render your site non-
federated (which might be desired on a private site).

Disabling the LRDD plugin would make your site unable to do modern web
URI lookups (making life just a little bit harder).
parent 44f7ad61
......@@ -585,12 +585,6 @@ EndPublicXRDS: End XRDS output (right before the closing XRDS tag)
- $action: the current action
- &$xrdsoutputter - XRDSOutputter object to write to
StartHostMetaLinks: Start /.well-known/host-meta links
- &links: array containing the links elements to be written
EndHostMetaLinks: End /.well-known/host-meta links
- &links: array containing the links elements to be written
StartCheckPassword: Check a username/password
- $nickname: The nickname to check
- $password: The password to check
......@@ -987,22 +981,6 @@ EndAtomPubNewActivity: When a new activity comes in through Atom Pub API
- $user: user publishing the entry
- $notice: notice that was created
StartXrdActionAliases: About to set aliases for the XRD object for a user
- &$xrd: XRD object being shown
- $user: User being shown
EndXrdActionAliases: Done with aliases for the XRD object for a user
- &$xrd: XRD object being shown
- $user: User being shown
StartXrdActionLinks: About to set links for the XRD object for a user
- &$xrd: XRD object being shown
- $user: User being shown
EndXrdActionLinks: Done with links for the XRD object for a user
- &$xrd: XRD object being shown
- $user: User being shown
AdminPanelCheck: When checking whether the current user can access a given admin panel
- $name: Name of the admin panel
- &$isOK: Boolean whether the user is allowed to use the panel
......
......@@ -1327,12 +1327,26 @@ class Profile extends Managed_DataObject
return $noun->asString('activity:' . $element);
}
/**
* Returns the profile's canonical url, not necessarily a uri/unique id
*
* @return string $profileurl
*/
public function getUrl()
{
if (empty($this->profileurl) ||
!filter_var($this->profileurl, FILTER_VALIDATE_URL)) {
throw new InvalidUrlException($this->profileurl);
}
return $this->profileurl;
}
/**
* Returns the best URI for a profile. Plugins may override.
*
* @return string $uri
*/
function getUri()
public function getUri()
{
$uri = null;
......
......@@ -907,6 +907,10 @@ class User extends Managed_DataObject
self::cacheSet('user:site_owner', $owner);
}
if (!($owner instanceof User)) {
throw new ServerException(_('No site owner configured.'));
}
return $owner;
}
......@@ -936,14 +940,13 @@ class User extends Managed_DataObject
// try the site owner.
if (empty($user)) {
$user = User::siteOwner();
}
if (!empty($user)) {
return $user;
} else {
// TRANS: Server exception.
throw new ServerException(_('No single user defined for single-user mode.'));
try {
$user = User::siteOwner();
return $user;
} catch (ServerException $e) {
// TRANS: Server exception.
throw new ServerException(_('No single user defined for single-user mode.'));
}
}
} else {
// TRANS: Server exception.
......
......@@ -203,7 +203,7 @@ function setupRW()
function isLoginAction($action)
{
static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp', 'opensearch', 'rsd', 'hostmeta');
static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp', 'opensearch', 'rsd');
$login = null;
......
......@@ -109,18 +109,11 @@ class AccountMover extends QueueHandler
$svcDocUrl = null;
$username = null;
foreach ($xrd->links as $link) {
if ($link['rel'] == 'http://apinamespace.org/atom' &&
$link['type'] == 'application/atomsvc+xml') {
$svcDocUrl = $link['href'];
if (!empty($link['property'])) {
foreach ($link['property'] as $property) {
if ($property['type'] == 'http://apinamespace.org/atom/username') {
$username = $property['value'];
break;
}
}
}
$link = $xrd->links->get('http://apinamespace.org/atom', 'application/atomsvc+xml');
if (!is_null($link)) {
$svcDocUrl = $link->href;
if (isset($link['http://apinamespace.org/atom/username'])) {
$username = $link['http://apinamespace.org/atom/username'];
break;
}
}
......
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* Use Hammer discovery stack to find out interesting things about an URI
*
* 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/>.
*
* @category Discovery
* @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/
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* This class implements LRDD-based service discovery based on the "Hammer Draft"
* (including webfinger)
*
* @category Discovery
* @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/
*
* @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();
/**
* Constructor for a discovery object
*
* Registers different discovery methods.
*
* @return Discovery this
*/
public function __construct()
{
$this->registerMethod('Discovery_LRDD_Host_Meta');
$this->registerMethod('Discovery_LRDD_Link_Header');
$this->registerMethod('Discovery_LRDD_Link_HTML');
}
/**
* Register a discovery class
*
* @param string $class Class name
*
* @return void
*/
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.
*
* @param string $user_id User ID to normalize
*
* @return string normalized acct: or http(s)?: URI
*/
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;
}
/**
* Determine if a string is a Webfinger ID
*
* Webfinger IDs look like foo@example.com or acct:foo@example.com
*
* @param string $user_id ID to check
*
* @return boolean true if $user_id is a Webfinger, else false
*/
public static function isWebfinger($user_id)
{
$uri = Discovery::normalize($user_id);
return (substr($uri, 0, 5) == 'acct:');
}
/**
* Given a user ID, return the first available XRD
*
* @param string $id User ID URI
*
* @return XRD XRD object for the user
*/
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 (!empty($link['template'])) {
$xrd_uri = Discovery::applyTemplate($link['template'], $uri);
} else {
$xrd_uri = $link['href'];
}
$xrd = $this->fetchXrd($xrd_uri);
if ($xrd) {
return $xrd;
}
}
}
// TRANS: Exception. %s is an ID.
throw new Exception(sprintf(_('Unable to find services for %s.'), $id));
}
/**
* Given an array of links, returns the matching service
*
* @param array $links Links to check
* @param string $service Service to find
*
* @return array $link assoc array representing the link
*/
public static function getService($links, $service)
{
if (!is_array($links)) {
return false;
}
foreach ($links as $link) {
if ($link['rel'] == $service) {
return $link;
}
}
}
/**
* Apply a template using an ID
*
* Replaces {uri} in template string with the ID given.
*
* @param string $template Template to match
* @param string $id User ID to replace with
*
* @return string replaced values
*/
public static function applyTemplate($template, $id)
{
$template = str_replace('{uri}', urlencode($id), $template);
return $template;
}
/**
* Fetch an XRD file and parse
*
* @param string $url URL of the XRD
*
* @return XRD object representing the XRD file
*/
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());
}
}
/**
* Abstract interface for discovery
*
* Objects that implement this interface can retrieve an array of
* XRD links for the URI.
*
* @category Discovery
* @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/
*/
interface Discovery_LRDD
{
/**
* Discover interesting info about the URI
*
* @param string $uri URI to inquire about
*
* @return array Links in the XRD file
*/
public function discover($uri);
}
/**
* Implementation of discovery using host-meta file
*
* Discovers XRD file for a user by going to the organization's
* host-meta file and trying to find a template for LRDD.
*
* @category Discovery
* @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/
*/
class Discovery_LRDD_Host_Meta implements Discovery_LRDD
{
/**
* Discovery core method
*
* For Webfinger and HTTP URIs, fetch the host-meta file
* and look for LRDD templates
*
* @param string $uri URI to inquire about
*
* @return array Links in the XRD file
*/
public function discover($uri)
{
if (Discovery::isWebfinger($uri)) {
// We have a webfinger acct: - start with host-meta
list($name, $domain) = explode('@', $uri);
} else {
$domain = parse_url($uri, PHP_URL_HOST);
}
$url = 'http://'. $domain .'/.well-known/host-meta';
$xrd = Discovery::fetchXrd($url);
if ($xrd) {
return $xrd->links;
}
}
}
/**
* Implementation of discovery using HTTP Link header
*
* Discovers XRD file for a user by fetching the URL and reading any
* Link: headers in the HTTP response.
*
* @category Discovery
* @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/
*/
class Discovery_LRDD_Link_Header implements Discovery_LRDD
{
/**
* Discovery core method
*
* For HTTP IDs fetch the URL and look for Link headers.
*
* @param string $uri URI to inquire about
*
* @return array Links in the XRD file
*
* @todo fail out of Webfinger URIs faster
*/
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 array(Discovery_LRDD_Link_Header::parseHeader($link_header));
}
/**
* Given a string or array of headers, returns XRD-like assoc array
*
* @param string|array $header string or array of strings for headers
*
* @return array Link header in XRD-like format
*/
protected static function parseHeader($header)
{
$lh = new LinkHeader($header);
return array('href' => $lh->href,
'rel' => $lh->rel,
'type' => $lh->type);
}
}
/**
* Implementation of discovery using HTML <link> element
*
* Discovers XRD file for a user by fetching the URL and reading any
* <link> elements in the HTML response.
*
* @category Discovery
* @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/
*/
class Discovery_LRDD_Link_HTML implements Discovery_LRDD
{
/**
* Discovery core method
*
* For HTTP IDs, fetch the URL and look for <link> elements
* in the HTML response.
*
* @param string $uri URI to inquire about
*
* @return array Links in XRD-ish assoc array
*
* @todo fail out of Webfinger URIs faster
*/
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());
}
/**
* Parse HTML and return <link> elements
*
* Given an HTML string, scans the string for <link> elements
*
* @param string $html HTML to scan
*
* @return array array of associative arrays in XRD-ish format
*/
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;
}
}
......@@ -27,7 +27,7 @@
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
if (!defined('GNUSOCIAL')) {
exit(1);
}
......@@ -242,7 +242,7 @@ class HTTPClient extends HTTP_Request2
}
/**
* Pulls up StatusNet's customized user-agent string, so services
* Pulls up GNU Social's customized user-agent string, so services
* we hit can track down the responsible software.
*
* @return string
......
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Class for an exception when a URL is invalid
*
* 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.