Proper AP Notices (fixed #46)

Explorer now works both for local and remote URIs
Fixed various serious errors (most of them in Explorer
and some in AP Profiles)
This commit is contained in:
Diogo Cordeiro
2018-07-29 02:35:04 +01:00
parent 5c351efb06
commit edb3633bcd
16 changed files with 278 additions and 129 deletions

View File

@@ -34,6 +34,9 @@ require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "di
require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "explorer.php"; require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "explorer.php";
require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "postman.php"; require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "postman.php";
// So that this isn't hardcoded everywhere
define('ACTIVITYPUB_BASE_INSTANCE_URI', common_root_url()."index.php/user/");
/** /**
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
@@ -54,7 +57,7 @@ class ActivityPubPlugin extends Plugin
public static function actor_uri($profile) public static function actor_uri($profile)
{ {
if ($profile->isLocal()) { if ($profile->isLocal()) {
return common_root_url()."index.php/user/".$profile->getID(); return ACTIVITYPUB_BASE_INSTANCE_URI.$profile->getID();
} else { } else {
return $profile->getUri(); return $profile->getUri();
} }
@@ -74,46 +77,30 @@ class ActivityPubPlugin extends Plugin
} }
/** /**
* Get remote user's ActivityPub_profile via a identifier * Returns a notice from its URL since GNU Social doesn't provide
* this functionality
* *
* @author GNU Social
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $arg A remote user identifier * @param string $url Notice's URL
* @return Activitypub_profile|null Valid profile in success | null otherwise * @return Notice The Notice object
* @throws Exception This function or provides a Notice or fails with exception
*/ */
public static function pull_remote_profile($arg) public static function get_local_notice_from_url($url)
{ {
if (preg_match('!^((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)$!', $arg)) {
// webfinger lookup
try { try {
return Activitypub_profile::ensure_web_finger($arg); return Notice::getByUri($data->object->inReplyTo);
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_ERR, 'Webfinger lookup failed for ' .
$arg . ': ' . $e->getMessage());
}
}
// Look for profile URLs, with or without scheme:
$urls = array();
if (preg_match('!^https?://((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!', $arg)) {
$urls[] = $arg;
}
if (preg_match('!^((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!', $arg)) {
$schemes = array('http', 'https');
foreach ($schemes as $scheme) {
$urls[] = "$scheme://$arg";
}
}
foreach ($urls as $url) {
try { try {
return Activitypub_profile::get_from_uri($url); $candidate = Notice::getByID(intval(substr($url, strlen(common_local_url('shownotice', ['notice' => ''])))));
if ($candidate->getUrl() == $url) {
return $candidate;
} else {
throw new Exception("Notice not found.");
}
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_ERR, 'Profile lookup failed for ' . throw new Exception("Notice not found.");
$arg . ': ' . $e->getMessage());
} }
} }
return null;
} }
/** /**
@@ -234,6 +221,49 @@ class ActivityPubPlugin extends Plugin
* WebFinger Events * * WebFinger Events *
********************************************************/ ********************************************************/
/**
* Get remote user's ActivityPub_profile via a identifier
*
* @author GNU Social
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $arg A remote user identifier
* @return Activitypub_profile|null Valid profile in success | null otherwise
*/
public static function pull_remote_profile($arg)
{
if (preg_match('!^((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)$!', $arg)) {
// webfinger lookup
try {
return Activitypub_profile::ensure_web_finger($arg);
} catch (Exception $e) {
common_log(LOG_ERR, 'Webfinger lookup failed for ' .
$arg . ': ' . $e->getMessage());
}
}
// Look for profile URLs, with or without scheme:
$urls = array();
if (preg_match('!^https?://((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!', $arg)) {
$urls[] = $arg;
}
if (preg_match('!^((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!', $arg)) {
$schemes = array('http', 'https');
foreach ($schemes as $scheme) {
$urls[] = "$scheme://$arg";
}
}
foreach ($urls as $url) {
try {
return Activitypub_profile::fromUri($url);
} catch (Exception $e) {
common_log(LOG_ERR, 'Profile lookup failed for ' .
$arg . ': ' . $e->getMessage());
}
}
return null;
}
/** /**
* Webfinger matches: @user@example.com or even @user--one.george_orwell@1984.biz * Webfinger matches: @user@example.com or even @user--one.george_orwell@1984.biz
* *
@@ -357,7 +387,7 @@ class ActivityPubPlugin extends Plugin
$url = "$scheme://$target"; $url = "$scheme://$target";
$this->log(LOG_INFO, "Checking profile address '$url'"); $this->log(LOG_INFO, "Checking profile address '$url'");
try { try {
$aprofile = Activitypub_profile::get_from_uri($url); $aprofile = Activitypub_profile::fromUri($url);
$profile = $aprofile->local_profile(); $profile = $aprofile->local_profile();
$displayName = !empty($profile->nickname) && mb_strlen($profile->nickname) < mb_strlen($target) ? $displayName = !empty($profile->nickname) && mb_strlen($profile->nickname) < mb_strlen($target) ?
$profile->nickname : $target; $profile->nickname : $target;
@@ -432,7 +462,7 @@ class ActivityPubPlugin extends Plugin
{ {
$aprofile = Activitypub_profile::getKV('profile_id', $profile->id); $aprofile = Activitypub_profile::getKV('profile_id', $profile->id);
if ($aprofile instanceof Activitypub_profile) { if ($aprofile instanceof Activitypub_profile) {
$uri = $aprofile->get_uri(); $uri = $aprofile->getUri();
return false; return false;
} }
return true; return true;
@@ -449,27 +479,9 @@ class ActivityPubPlugin extends Plugin
*/ */
public function onStartGetProfileFromURI($uri, &$profile) public function onStartGetProfileFromURI($uri, &$profile)
{ {
// Don't want to do Web-based discovery on our own server,
// so we check locally first. This duplicates the functionality
// in the Profile class, since the plugin always runs before
// that local lookup, but since we return false it won't run double.
$user = User::getKV('uri', $uri);
if ($user instanceof User) {
$profile = $user->getProfile();
return false;
} else {
$group = User_group::getKV('uri', $uri);
if ($group instanceof User_group) {
$profile = $group->getProfile();
return false;
}
}
// Now, check remotely
try { try {
$aprofile = Activitypub_profile::get_from_uri($uri); $explorer = new Activitypub_explorer();
$profile = $aprofile->local_profile(); $profile = $explorer->lookup($uri)[0];
return false; return false;
} catch (Exception $e) { } catch (Exception $e) {
return true; // It's not an ActivityPub profile as far as we know, continue event handling return true; // It's not an ActivityPub profile as far as we know, continue event handling

View File

@@ -58,6 +58,10 @@ class apActorFollowersAction extends ManagedAction
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.");
}
if (!isset($_GET["page"])) { if (!isset($_GET["page"])) {
$page = 1; $page = 1;
} else { } else {
@@ -93,7 +97,7 @@ class apActorFollowersAction extends ManagedAction
/* Get followers' URLs */ /* Get followers' URLs */
$subs = array(); $subs = array();
while ($sub->fetch()) { while ($sub->fetch()) {
$subs[] = $sub->profileurl; $subs[] = ActivityPubPlugin::actor_uri($sub);
} }
$res = [ $res = [

View File

@@ -58,6 +58,10 @@ class apActorFollowingAction extends ManagedAction
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.");
}
if (!isset($_GET["page"])) { if (!isset($_GET["page"])) {
$page = 1; $page = 1;
} else { } else {
@@ -93,7 +97,7 @@ class apActorFollowingAction extends ManagedAction
/* Get followed' URLs */ /* Get followed' URLs */
$subs = array(); $subs = array();
while ($sub->fetch()) { while ($sub->fetch()) {
$subs[] = $sub->profileurl; $subs[] = ActivityPubPlugin::actor_uri($sub);
} }
$res = [ $res = [

View File

@@ -57,6 +57,10 @@ class apActorInboxAction extends ManagedAction
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.");
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
ActivityPubReturn::error("C2S not implemented just yet."); ActivityPubReturn::error("C2S not implemented just yet.");
} }

View File

@@ -58,6 +58,10 @@ class apActorLikedAction extends ManagedAction
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.");
}
$limit = intval($this->trimmed('limit')); $limit = intval($this->trimmed('limit'));
$since_id = intval($this->trimmed('since_id')); $since_id = intval($this->trimmed('since_id'));
$max_id = intval($this->trimmed('max_id')); $max_id = intval($this->trimmed('max_id'));

View File

@@ -30,7 +30,7 @@ if (!defined('GNUSOCIAL')) {
} }
try { try {
Notice::getByUri($data->object->id)->repeat($actor_profile, "ActivityPub"); ActivityPubPlugin::get_local_notice_from_url($data->object->id)->repeat($actor_profile, "ActivityPub");
ActivityPubReturn::answer("Notice repeated successfully."); ActivityPubReturn::answer("Notice repeated successfully.");
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error($e->getMessage(), 403); ActivityPubReturn::error($e->getMessage(), 403);

View File

@@ -32,8 +32,10 @@ if (!defined('GNUSOCIAL')) {
$valid_object_types = array("Note"); $valid_object_types = array("Note");
// Validate data // Validate data
if (!isset($data->id)) { if (!isset($data->object->id)) {
ActivityPubReturn::error("Id not specified."); ActivityPubReturn::error("Object ID not specified.");
} elseif (!filter_var($data->object->id, FILTER_VALIDATE_URL)) {
ActivityPubReturn::error("Invalid Object ID.");
} }
if (!(isset($data->object->type) && in_array($data->object->type, $valid_object_types))) { if (!(isset($data->object->type) && in_array($data->object->type, $valid_object_types))) {
ActivityPubReturn::error("Invalid Object type."); ActivityPubReturn::error("Invalid Object type.");
@@ -60,19 +62,19 @@ $act->actor = $actor_profile->asActivityObject();
$act->context = new ActivityContext(); $act->context = new ActivityContext();
// Is this a reply? // Is this a reply?
if (isset($data->object->reply_to)) { if (isset($data->object->inReplyTo)) {
try { try {
$reply_to = Notice::getByUri($data->object->reply_to); $inReplyTo = ActivityPubPlugin::get_local_notice_from_url($data->object->inReplyTo);
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error("Invalid Object reply_to value."); ActivityPubReturn::error("Invalid Object inReplyTo value.");
} }
$act->context->replyToID = $reply_to->getUri(); $act->context->replyToID = $inReplyTo->getUri();
$act->context->replyToUrl = $reply_to->getUrl(); $act->context->replyToUrl = $inReplyTo->getUrl();
} else { } else {
$reply_to = null; $inReplyTo = null;
} }
$act->context->attention = common_get_attentions($content, $actor_profile, $reply_to); $act->context->attention = common_get_attentions($content, $actor_profile, $inReplyTo);
$discovery = new Activitypub_explorer; $discovery = new Activitypub_explorer;
if ($to_profiles == "https://www.w3.org/ns/activitystreams#Public") { if ($to_profiles == "https://www.w3.org/ns/activitystreams#Public") {
@@ -86,22 +88,43 @@ if (is_array($data->object->to)) {
try { try {
$to_profiles = array_merge($to_profiles, $discovery->lookup($to_url)); $to_profiles = array_merge($to_profiles, $discovery->lookup($to_url));
} catch (Exception $e) { } catch (Exception $e) {
// XXX: Invalid actor found, not sure how we handle those // Invalid actor found, just let it go.
} }
} }
} elseif (empty($data->object->to) || in_array($data->object->to, $public_to)) { } elseif (empty($data->object->to) || in_array($data->object->to, $public_to)) {
// No need to do anything else at this point, let's just break out the if // No need to do anything else at this point, let's just break out the if
} else { } else {
try { try {
$to_profiles[]= $discovery->lookup($data->object->to); $to_profiles = array_merge($to_profiles, $discovery->lookup($data->object->to));
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error("Invalid Actor.", 404); // Invalid actor found, just let it go.
} }
} }
// Generate Cc objects
if (isset($data->object->cc) && is_array($data->object->cc)) {
// Remove duplicates from Cc actors set
array_unique($data->object->to);
foreach ($data->object->cc as $cc_url) {
try {
$to_profiles = array_merge($to_profiles, $discovery->lookup($cc_url));
} catch (Exception $e) {
// Invalid actor found, just let it go.
}
}
} elseif (empty($data->object->cc) || in_array($data->object->cc, $public_to)) {
// No need to do anything else at this point, let's just break out the if
} else {
try {
$to_profiles = array_merge($to_profiles, $discovery->lookup($data->object->cc));
} catch (Exception $e) {
// Invalid actor found, just let it go.
}
}
unset($discovery); unset($discovery);
foreach ($to_profiles as $to) { foreach ($to_profiles as $tp) {
$act->context->attention[ActivityPubPlugin::actor_uri($to)] = "http://activitystrea.ms/schema/1.0/person"; $act->context->attention[ActivityPubPlugin::actor_uri($tp)] = "http://activitystrea.ms/schema/1.0/person";
} }
// Reject notice if it is too long (without the HTML) // Reject notice if it is too long (without the HTML)
@@ -116,7 +139,7 @@ ToSelector::fillActivity($this, $act, $options);
$actobj = new ActivityObject(); $actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE; $actobj->type = ActivityObject::NOTE;
$actobj->content = common_render_content($content, $actor_profile, $reply_to); $actobj->content = common_render_content($content, $actor_profile, $inReplyTo);
// Finally add the activity object to our activity // Finally add the activity object to our activity
$act->objects[] = $actobj; $act->objects[] = $actobj;

View File

@@ -30,7 +30,7 @@ if (!defined('GNUSOCIAL')) {
} }
try { try {
$notice = Notice::getByUri($data->object->id); $notice = ActivityPubPlugin::get_local_notice_from_url($data->object->id);
$notice_to_array = Activitypub_notice::notice_to_array($notice); $notice_to_array = Activitypub_notice::notice_to_array($notice);
$notice->deleteAs($actor_profile); $notice->deleteAs($actor_profile);
ActivityPubReturn::answer(Activitypub_delete::delete_to_array($notice_to_array)); ActivityPubReturn::answer(Activitypub_delete::delete_to_array($notice_to_array));

View File

@@ -34,8 +34,13 @@ if (!isset($data->object->id)) {
} }
try { try {
Fave::addNew($actor_profile, Notice::getByUri($data->object->id)); try {
ActivityPubReturn::answer(Activitypub_like::like_to_array(Activitypub_notice::notice_to_array($data->actor, json_decode($data->object)))); $object_notice = ActivityPubPlugin::get_local_notice_from_url($data->object->id);
} catch (Exception $e) {
ActivityPubReturn::error("Invalid Object ID value.");
}
Fave::addNew($actor_profile, $object_notice);
ActivityPubReturn::answer(Activitypub_like::like_to_array($data->actor, Activitypub_notice::notice_to_array($object_notice)));
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error($e->getMessage(), 403); ActivityPubReturn::error($e->getMessage(), 403);
} }

View File

@@ -41,7 +41,7 @@ case "Like":
if (!isset($data->object->object->id)) { if (!isset($data->object->object->id)) {
ActivityPubReturn::error("Notice ID was not specified."); ActivityPubReturn::error("Notice ID was not specified.");
} }
Fave::removeEntry($actor_profile, Notice::getByUri($data->object->object->id)); Fave::removeEntry($actor_profile, ActivityPubPlugin::get_local_notice_from_url($data->object->object->id));
// Notice disfavorited successfully. // Notice disfavorited successfully.
ActivityPubReturn::answer( ActivityPubReturn::answer(
Activitypub_undo::undo_to_array( Activitypub_undo::undo_to_array(

View File

@@ -34,7 +34,6 @@ if (!defined('GNUSOCIAL')) {
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
* @author Daniel Supernault <danielsupernault@gmail.com>
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/ * @link http://www.gnu.org/software/social/
@@ -44,13 +43,13 @@ class Activitypub_notice extends Managed_DataObject
/** /**
* Generates a pretty notice from a Notice object * Generates a pretty notice from a Notice object
* *
* @author Daniel Supernault <danielsupernault@gmail.com>
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice * @param Notice $notice
* @return pretty array to be used in a response * @return pretty array to be used in a response
*/ */
public static function notice_to_array($notice) public static function notice_to_array($notice)
{ {
$profile = $notice->getProfile();
$attachments = array(); $attachments = array();
foreach ($notice->attachments() as $attachment) { foreach ($notice->attachments() as $attachment) {
$attachments[] = Activitypub_attachment::attachment_to_array($attachment); $attachments[] = Activitypub_attachment::attachment_to_array($attachment);
@@ -63,7 +62,7 @@ class Activitypub_notice extends Managed_DataObject
} }
} }
$to = array(); $to = [];
foreach ($notice->getAttentionProfiles() as $to_profile) { foreach ($notice->getAttentionProfiles() as $to_profile) {
$to[] = $to_profile->getUri(); $to[] = $to_profile->getUri();
} }
@@ -72,16 +71,18 @@ class Activitypub_notice extends Managed_DataObject
} }
$item = [ $item = [
'id' => $notice->getUri(), 'id' => $notice->getUrl(),
'type' => 'Note', 'type' => 'Note',
'actor' => $notice->getProfile()->getUrl(), 'inReplyTo' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl(),
'published' => $notice->getCreated(), 'published' => $notice->getCreated(),
'to' => $to,
'content' => $notice->getContent(),
'url' => $notice->getUrl(), 'url' => $notice->getUrl(),
'reply_to' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUri(), 'atributedTo' => ActivityPubPlugin::actor_uri($profile),
'is_local' => $notice->isLocal(), 'to' => $to,
'atomUri' => $notice->getUrl(),
'inReplyToAtomUri' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl(),
'conversation' => $notice->getConversationUrl(), 'conversation' => $notice->getConversationUrl(),
'content' => $notice->getContent(),
'is_local' => $notice->isLocal(),
'attachment' => $attachments, 'attachment' => $attachments,
'tag' => $tags 'tag' => $tags
]; ];

View File

@@ -41,6 +41,18 @@ if (!defined('GNUSOCIAL')) {
class Activitypub_profile extends Managed_DataObject class Activitypub_profile extends Managed_DataObject
{ {
public $__table = 'Activitypub_profile'; public $__table = 'Activitypub_profile';
public $uri; // text() not_null
public $profile_id; // int(4) primary_key not_null
public $inboxuri; // text() not_null
public $sharedInboxuri; // text()
public $nickname; // varchar(64) multiple_key not_null
public $fullname; // text()
public $profileurl; // text()
public $homepage; // text()
public $bio; // text() multiple_key
public $location; // text()
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/** /**
* Return table definition for Schema setup and DB_DataObject usage. * Return table definition for Schema setup and DB_DataObject usage.
@@ -108,7 +120,7 @@ class Activitypub_profile extends Managed_DataObject
'liked' => common_local_url("apActorLiked", array("id" => $id)), 'liked' => common_local_url("apActorLiked", array("id" => $id)),
'inbox' => common_local_url("apActorInbox", array("id" => $id)), 'inbox' => common_local_url("apActorInbox", array("id" => $id)),
'preferredUsername' => $profile->getNickname(), 'preferredUsername' => $profile->getNickname(),
'name' => $profile->getFullname(), 'name' => $profile->getBestName(),
'summary' => ($desc = $profile->getDescription()) == null ? "" : $desc, 'summary' => ($desc = $profile->getDescription()) == null ? "" : $desc,
'url' => $profile->getUrl(), 'url' => $profile->getUrl(),
'manuallyApprovesFollowers' => false, 'manuallyApprovesFollowers' => false,
@@ -280,11 +292,33 @@ class Activitypub_profile extends Managed_DataObject
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @return string URI * @return string URI
*/ */
public function get_uri() public function getUri()
{ {
return $this->uri; return $this->uri;
} }
/**
* Getter for url property
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return string URL
*/
public function getUrl()
{
return $this->getUri();
}
/**
* Getter for id property
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return int32
*/
public function getID()
{
return $this->profile_id;
}
/** /**
* Ensures a valid Activitypub_profile when provided with a valid URI. * Ensures a valid Activitypub_profile when provided with a valid URI.
* *
@@ -293,7 +327,7 @@ class Activitypub_profile extends Managed_DataObject
* @return Activitypub_profile * @return Activitypub_profile
* @throws Exception if it isn't possible to return an Activitypub_profile * @throws Exception if it isn't possible to return an Activitypub_profile
*/ */
public static function get_from_uri($url) public static function fromUri($url)
{ {
$explorer = new Activitypub_explorer(); $explorer = new Activitypub_explorer();
$profiles_found = $explorer->lookup($url); $profiles_found = $explorer->lookup($url);
@@ -331,7 +365,7 @@ class Activitypub_profile extends Managed_DataObject
throw new Exception(_m('Not a valid webfinger address (via cache).')); throw new Exception(_m('Not a valid webfinger address (via cache).'));
} }
try { try {
return self::get_from_uri($uri); return self::fromUri($uri);
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_ERR, sprintf(__METHOD__ . ': Webfinger address cache inconsistent with database, did not find Activitypub_profile uri==%s', $uri)); common_log(LOG_ERR, sprintf(__METHOD__ . ': Webfinger address cache inconsistent with database, did not find Activitypub_profile uri==%s', $uri));
self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), false); self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), false);
@@ -372,8 +406,8 @@ class Activitypub_profile extends Managed_DataObject
$profileUrl = $hints['profileurl']; $profileUrl = $hints['profileurl'];
try { try {
common_log(LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl"); common_log(LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl");
$aprofile = self::get_from_uri($hints['profileurl']); $aprofile = self::fromUri($hints['profileurl']);
self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), $aprofile->get_uri()); self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), $aprofile->getUri());
return $aprofile; return $aprofile;
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_WARNING, "Failed creating profile from profile URL '$profileUrl': " . $e->getMessage()); common_log(LOG_WARNING, "Failed creating profile from profile URL '$profileUrl': " . $e->getMessage());

View File

@@ -49,11 +49,17 @@ class ProfileObjectTest extends TestCase
/* Test get_inbox() */ /* Test get_inbox() */
$this->assertTrue($aprofile->sharedInboxuri == $aprofile->get_inbox()); $this->assertTrue($aprofile->sharedInboxuri == $aprofile->get_inbox());
/* Test get_uri() */ /* Test getUri() */
$this->assertTrue($aprofile->uri == $aprofile->get_uri()); $this->assertTrue($aprofile->uri == $aprofile->getUri());
/* Test get_from_uri() */ /* Test getUrl() */
$this->assertTrue($this->compare_aprofiles($aprofile, \Activitypub_profile::get_from_uri($aprofile->uri))); $this->assertTrue($profile->getUrl() == $aprofile->getUrl());
/* Test getID() */
$this->assertTrue($profile->getID() == $aprofile->getID());
/* Test fromUri() */
$this->assertTrue($this->compare_aprofiles($aprofile, \Activitypub_profile::fromUri($aprofile->uri)));
/* Remove Remote User Test 1 */ /* Remove Remote User Test 1 */
$old_id = $profile->getID(); $old_id = $profile->getID();
@@ -124,20 +130,28 @@ class ProfileObjectTest extends TestCase
private function compare_aprofiles(\Activitypub_profile $a, \Activitypub_profile $b) private function compare_aprofiles(\Activitypub_profile $a, \Activitypub_profile $b)
{ {
if (($av = $a->get_uri()) != ($bv = $b->get_uri())) { if (($av = $a->getUri()) != ($bv = $b->getUri())) {
throw new Exception('Compare AProfiles 1 Fail: $a: '.$av.' is different from $b: '.$bv); throw new Exception('Compare AProfiles 1 Fail: $a: '.$av.' is different from $b: '.$bv);
} }
if (($av = $a->profile_id) != ($bv = $b->profile_id)) { if (($av = $a->getUrl()) != ($bv = $b->getUrl())) {
throw new Exception('Compare AProfiles 2 Fail: $a: '.$av.' is different from $b: '.$bv); throw new Exception('Compare AProfiles 2 Fail: $a: '.$av.' is different from $b: '.$bv);
} }
if (($av = $a->inboxuri) != ($bv = $b->inboxuri)) { if (($av = $a->getID()) != ($bv = $b->getID())) {
throw new Exception('Compare AProfiles 3 Fail: $a: '.$av.' is different from $b: '.$bv); throw new Exception('Compare AProfiles 3 Fail: $a: '.$av.' is different from $b: '.$bv);
} }
if (($av = $a->profile_id) != ($bv = $b->profile_id)) {
throw new Exception('Compare AProfiles 4 Fail: $a: '.$av.' is different from $b: '.$bv);
}
if (($av = $a->inboxuri) != ($bv = $b->inboxuri)) {
throw new Exception('Compare AProfiles 5 Fail: $a: '.$av.' is different from $b: '.$bv);
}
if (($av = $a->sharedInboxuri) != ($bv = $b->sharedInboxuri)) { if (($av = $a->sharedInboxuri) != ($bv = $b->sharedInboxuri)) {
throw new Exception('Compare AProfiles 1 Fail: $a: '.$av.' is different from $b: '.$bv); throw new Exception('Compare AProfiles 6 Fail: $a: '.$av.' is different from $b: '.$bv);
} }
return true; return true;

View File

@@ -32,7 +32,7 @@ if (!defined('GNUSOCIAL')) {
/** /**
* ActivityPub's own Explorer * ActivityPub's own Explorer
* *
* Allows to discovery new (or the same) ActivityPub profiles * Allows to discovery new (or the same) Profiles (both local or remote)
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
@@ -55,6 +55,7 @@ class Activitypub_explorer
*/ */
public function lookup($url) public function lookup($url)
{ {
common_debug("Explorer started now looking for ".$url);
$this->discovered_actor_profiles = array(); $this->discovered_actor_profiles = array();
return $this->_lookup($url); return $this->_lookup($url);
@@ -95,9 +96,6 @@ class Activitypub_explorer
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social'; $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get($url, $headers); $response = $client->get($url, $headers);
if (!$response->isOk()) {
throw new Exception("Invalid Actor URL.");
}
$res = json_decode($response->getBody(), JSON_UNESCAPED_SLASHES); $res = json_decode($response->getBody(), JSON_UNESCAPED_SLASHES);
if (self::validate_remote_response($res)) { if (self::validate_remote_response($res)) {
$this->temp_res = $res; $this->temp_res = $res;
@@ -108,7 +106,7 @@ class Activitypub_explorer
} }
/** /**
* Get a local user profiles from its URL and joins it on * Get a local user profile from its URL and joins it on
* $this->discovered_actor_profiles * $this->discovered_actor_profiles
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
@@ -117,6 +115,11 @@ class Activitypub_explorer
*/ */
private function grab_local_user($uri, $online = false) private function grab_local_user($uri, $online = false)
{ {
if ($online) {
common_debug("Explorer is searching locally for ".$uri. " online.");
} else {
common_debug("Explorer is searching locally for ".$uri. " offline.");
}
// Ensure proper remote URI // Ensure proper remote URI
// If an exception occurs here it's better to just leave everything // If an exception occurs here it's better to just leave everything
// break than to continue processing // break than to continue processing
@@ -126,14 +129,31 @@ class Activitypub_explorer
// Try standard ActivityPub route // Try standard ActivityPub route
// Is this a known filthy little mudblood? // Is this a known filthy little mudblood?
$aprofile = Activitypub_profile::getKV("uri", $uri); $aprofile = self::get_aprofile_by_url($uri);
if ($aprofile instanceof Activitypub_profile) { if ($aprofile instanceof Activitypub_profile) {
$profile = $aprofile->local_profile(); $profile = $aprofile->local_profile();
common_debug("Explorer found a local Aprofile for ".$uri);
// We found something!
$this->discovered_actor_profiles[]= $profile;
unset($this->temp_res); // IMPORTANT to avoid _dangerous_ noise in the Explorer system
return true;
} else {
common_debug("Explorer didn't find a local Aprofile for ".$uri);
// Well, maybe it is a pure blood?
// Iff, we are in the same instance:
$ACTIVITYPUB_BASE_INSTANCE_URI_length = strlen(ACTIVITYPUB_BASE_INSTANCE_URI);
if (substr($uri, 0, $ACTIVITYPUB_BASE_INSTANCE_URI_length) == ACTIVITYPUB_BASE_INSTANCE_URI) {
try {
$profile = Profile::getByID(intval(substr($uri, $ACTIVITYPUB_BASE_INSTANCE_URI_length)));
// We found something! // We found something!
$this->discovered_actor_profiles[]= $profile; $this->discovered_actor_profiles[]= $profile;
unset($this->temp_res); // IMPORTANT to avoid _dangerous_ noise in the Explorer system unset($this->temp_res); // IMPORTANT to avoid _dangerous_ noise in the Explorer system
return true; return true;
} catch (Exception $e) {
// Let the exception go on its merry way.
}
}
} }
// If offline grabbing failed, attempt again with online resources // If offline grabbing failed, attempt again with online resources
@@ -154,15 +174,13 @@ class Activitypub_explorer
*/ */
private function grab_remote_user($url) private function grab_remote_user($url)
{ {
common_debug("Explorer is grabbing a remote profile for ".$url);
if (!isset($this->temp_res)) { if (!isset($this->temp_res)) {
$client = new HTTPClient(); $client = new HTTPClient();
$headers = array(); $headers = array();
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social'; $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get($url, $headers); $response = $client->get($url, $headers);
if (!$response->isOk()) {
throw new Exception("Invalid Actor URL.");
}
$res = json_decode($response->getBody(), JSON_UNESCAPED_SLASHES); $res = json_decode($response->getBody(), JSON_UNESCAPED_SLASHES);
} else { } else {
$res = $this->temp_res; $res = $this->temp_res;
@@ -234,6 +252,32 @@ class Activitypub_explorer
return true; return true;
} }
/**
* Get a ActivityPub Profile from it's uri
* Unfortunately GNU Social cache is not truly reliable when handling
* potential ActivityPub remote profiles, as so it is important to use
* this hacky workaround (at least for now)
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $v URL
* @return boolean|Activitypub_profile false if fails | Aprofile object if successful
*/
public static function get_aprofile_by_url($v)
{
$i = Managed_DataObject::getcached("Activitypub_profile", "uri", $v);
if (empty($i)) { // false = cache miss
$i = new Activitypub_profile;
$result = $i->get("uri", $v);
if ($result) {
// Hit!
$i->encache();
} else {
return false;
}
}
return $i;
}
/** /**
* Given a valid actor profile url returns its inboxes * Given a valid actor profile url returns its inboxes
* *

View File

@@ -168,12 +168,12 @@ class Activitypub_postman
public function create($notice) public function create($notice)
{ {
$data = Activitypub_create::create_to_array( $data = Activitypub_create::create_to_array(
$notice->getUri(), $notice->getUrl(),
ActivityPubPlugin::actor_uri($this->actor), ActivityPubPlugin::actor_uri($this->actor),
Activitypub_notice::notice_to_array($notice) array_merge(Activitypub_notice::notice_to_array($notice), ['cc' => common_local_url('apActorFollowers', ['id' => $this->actor->getID()]),])
); );
if (isset($notice->reply_to)) { if (isset($notice->reply_to)) {
$data["object"]["reply_to"] = $notice->getParent()->getUri(); $data["object"]["reply_to"] = $notice->getParent()->getUrl();
} }
$this->client->setBody(json_encode($data)); $this->client->setBody(json_encode($data));
foreach ($this->to_inbox() as $inbox) { foreach ($this->to_inbox() as $inbox) {