Merge branch 'dev' into 'actor_outbox'

Dev

See merge request dansup/ActivityPub!25
This commit is contained in:
Diogo Cordeiro 2018-08-07 03:36:23 +00:00
commit 5911b077ca
30 changed files with 398 additions and 217 deletions

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -30,7 +29,7 @@ if (!defined('GNUSOCIAL')) {
}
// Ensure proper timezone
date_default_timezone_set('UTC');
date_default_timezone_set('GMT');
// Import required files by the plugin
require_once __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
@ -106,8 +105,8 @@ class ActivityPubPlugin extends Plugin
// Look for a local notice (unfortunately GNU Social doesn't
// provide this functionality natively)
try {
$candidate = Notice::getByID(intval(substr($url, strlen(common_local_url('shownotice', ['notice' => ''])))));
if ($candidate->getUrl() == $url) { // Sanity check
$candidate = Notice::getByID(intval(substr($url, (strlen(common_local_url('apNotice', ['id' => 0]))-1))));
if (common_local_url('apNotice', ['id' => $candidate->getID()]) === $url) { // Sanity check
return $candidate;
} else {
common_debug('ActivityPubPlugin Notice Grabber: '.$candidate->getUrl(). ' is different of '.$url);
@ -151,49 +150,49 @@ class ActivityPubPlugin extends Plugin
['nickname' => Nickname::DISPLAY_FMT],
'apActorProfile'
);
ActivityPubURLMapperOverwrite::variable(
$m,
'notice/:id',
['id' => '[0-9]+'],
'apNotice'
);
}
// No .json here for convenience purposes on Notice grabber
$m->connect(
'note/:id',
['action' => 'apNotice'],
['id' => '[0-9]+']
);
$m->connect(
'user/:id/liked.json',
['action' => 'apActorLiked'],
['id' => '[0-9]+']
);
['action' => 'apActorLiked'],
['id' => '[0-9]+']
);
$m->connect(
'user/:id/followers.json',
['action' => 'apActorFollowers'],
['id' => '[0-9]+']
);
['action' => 'apActorFollowers'],
['id' => '[0-9]+']
);
$m->connect(
'user/:id/following.json',
['action' => 'apActorFollowing'],
['id' => '[0-9]+']
);
['action' => 'apActorFollowing'],
['id' => '[0-9]+']
);
$m->connect(
'user/:id/inbox.json',
['action' => 'apInbox'],
['id' => '[0-9]+']
);
['action' => 'apInbox'],
['id' => '[0-9]+']
);
$m->connect(
'user/:id/outbox.json',
['action' => 'apActorOutbox'],
['id' => '[0-9]+']
);
['action' => 'apActorOutbox'],
['id' => '[0-9]+']
);
$m->connect(
'inbox.json',
['action' => 'apInbox']
);
['action' => 'apInbox']
);
}
/**
@ -206,7 +205,7 @@ class ActivityPubPlugin extends Plugin
{
$versions[] = [ 'name' => 'ActivityPub',
'version' => GNUSOCIAL_VERSION,
'author' => 'Diogo Cordeiro, Daniel Supernault',
'author' => 'Diogo Cordeiro',
'homepage' => 'https://www.gnu.org/software/social/',
'rawdescription' => 'Adds ActivityPub Support'];
@ -214,8 +213,7 @@ class ActivityPubPlugin extends Plugin
}
/**
* Dummy string on AccountProfileBlock stating that ActivityPub is active
* this is more of a placeholder for eventual useful stuff ._.
* Adds an indicator on Remote ActivityPub profiles.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @return boolean hook return value
@ -226,8 +224,8 @@ class ActivityPubPlugin extends Plugin
return true;
}
try {
$aprofile = Activitypub_profile::getKV('profile_id', $profile->id);
} catch (NoResultException $e) {
$aprofile = Activitypub_profile::from_profile($profile);
} catch (Exception $e) {
// Not a remote ActivityPub_profile! Maybe some other network
// that has imported a non-local user (e.g.: OStatus)?
return true;
@ -278,7 +276,7 @@ class ActivityPubPlugin extends Plugin
}
// Look for profile URLs, with or without scheme:
$urls = array();
$urls = [];
if (preg_match('!^https?://((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!', $arg)) {
$urls[] = $arg;
}
@ -336,7 +334,7 @@ class ActivityPubPlugin extends Plugin
*/
public static function extractUrlMentions($text, $preMention='@')
{
$wmatches = array();
$wmatches = [];
// In the regexp below we need to match / _before_ URL_REGEX_VALID_PATH_CHARS because it otherwise gets merged
// with the TLD before (but / is in URL_REGEX_VALID_PATH_CHARS anyway, it's just its positioning that is important)
$result = preg_match_all(
@ -386,7 +384,7 @@ class ActivityPubPlugin extends Plugin
*/
public function onEndFindMentions(Profile $sender, $text, &$mentions)
{
$matches = array();
$matches = [];
foreach (self::extractWebfingerIds($text, '@') as $wmatch) {
list($target, $pos) = $wmatch;
@ -547,7 +545,7 @@ class ActivityPubPlugin extends Plugin
try {
$other = Activitypub_profile::from_profile($other);
} catch (Exception $e) {
return true;
return true; // Let other plugin handle this instead
}
$postman = new Activitypub_postman($profile, array($other));
@ -574,7 +572,7 @@ class ActivityPubPlugin extends Plugin
try {
$other = Activitypub_profile::from_profile($other);
} catch (Exception $e) {
return true;
return true; // Let other plugin handle this instead
}
$postman = new Activitypub_postman($profile, array($other));
@ -585,7 +583,7 @@ class ActivityPubPlugin extends Plugin
}
/**
* Notify remote users when their notices get favorited.
* Notify remote users when their notices get favourited.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile of local user doing the faving
@ -600,7 +598,7 @@ class ActivityPubPlugin extends Plugin
return true;
}
$other = array();
$other = [];
try {
$other[] = Activitypub_profile::from_profile($notice->getProfile());
} catch (Exception $e) {
@ -644,7 +642,7 @@ class ActivityPubPlugin extends Plugin
}
/**
* Notify remote users when their notices get de-favorited.
* Notify remote users when their notices get de-favourited.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile of local user doing the de-faving
@ -659,7 +657,7 @@ class ActivityPubPlugin extends Plugin
return true;
}
$other = array();
$other = [];
try {
$other[] = Activitypub_profile::from_profile($notice->getProfile());
} catch (Exception $e) {
@ -718,7 +716,7 @@ class ActivityPubPlugin extends Plugin
return true;
}
$other = array();
$other = [];
foreach ($notice->getAttentionProfiles() as $to_profile) {
try {
@ -765,44 +763,12 @@ class ActivityPubPlugin extends Plugin
{
assert($notice->id > 0); // Ignore if not a valid notice
$profile = Profile::getKV($notice->profile_id);
$profile = $notice->getProfile();
if (!$profile->isLocal()) {
return true;
}
$other = array();
try {
$other[] = Activitypub_profile::from_profile($notice->getProfile());
} catch (Exception $e) {
// Local user can be ignored
}
foreach ($notice->getAttentionProfiles() as $to_profile) {
try {
$other[] = Activitypub_profile::from_profile($to_profile);
} catch (Exception $e) {
// Local user can be ignored
}
}
// Is Announce
if ($notice->isRepeat()) {
$repeated_notice = Notice::getKV('id', $notice->repeat_of);
if ($repeated_notice instanceof Notice) {
try {
$other[] = Activitypub_profile::from_profile($repeated_notice->getProfile());
} catch (Exception $e) {
// Local user can be ignored
}
$postman = new Activitypub_postman($profile, $other);
// That was it
$postman->announce($repeated_notice);
return true;
}
}
// Ignore for activity/non-post-verb notices
if (method_exists('ActivityUtils', 'compareVerbs')) {
$is_post_verb = ActivityUtils::compareVerbs(
@ -816,7 +782,16 @@ class ActivityPubPlugin extends Plugin
return true;
}
// Create
$other = [];
foreach ($notice->getAttentionProfiles() as $mention) {
try {
$other[] = Activitypub_profile::from_profile($mention);
} catch (Exception $e) {
// Local user can be ignored
}
}
// Is a reply?
if ($notice->reply_to) {
try {
$other[] = Activitypub_profile::from_profile($notice->getParent()->getProfile());
@ -824,10 +799,9 @@ class ActivityPubPlugin extends Plugin
// Local user can be ignored
}
try {
$mentions = $notice->getParent()->getAttentionProfiles();
foreach ($mentions as $to_profile) {
foreach ($notice->getParent()->getAttentionProfiles() as $mention) {
try {
$other[] = Activitypub_profile::from_profile($to_profile);
$other[] = Activitypub_profile::from_profile($mention);
} catch (Exception $e) {
// Local user can be ignored
}
@ -839,10 +813,27 @@ class ActivityPubPlugin extends Plugin
common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
}
}
$postman = new Activitypub_postman($profile, $other);
// Is an Announce?
if ($notice->isRepeat()) {
$repeated_notice = Notice::getKV('id', $notice->repeat_of);
if ($repeated_notice instanceof Notice) {
try {
$other[] = Activitypub_profile::from_profile($repeated_notice->getProfile());
} catch (Exception $e) {
// Local user can be ignored
}
// That was it
$postman = new Activitypub_postman($profile, $other);
$postman->announce($repeated_notice);
return true;
}
}
// That was it
$postman->create($notice);
$postman = new Activitypub_postman($profile, $other);
$postman->create_note($notice);
return true;
}

View File

@ -40,10 +40,14 @@ We use [SemVer](http://semver.org/) for versioning. For the versions available,
## Credits
* **[Diogo Cordeiro](https://www.diogo.site/)**
* **[Daniel Supernault](https://github.com/dansup)**
See also the list of [contributors](https://git.gnu.io/gnu/GS-ActivityPub-Plugin/contributors) who participated in this project.
## Extra special thanks
* **[Daniel Supernault](https://github.com/dansup)**
* **[Mikael Nordfeldth](https://mmn-o.se/)**
## License
This program is free software: you can redistribute it and/or modify

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -108,7 +107,7 @@ class apActorFollowersAction extends ManagedAction
}
/**
* Generates a list of followers for a given profile.
* Generates a list of stalkers for a given profile.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile
@ -116,7 +115,7 @@ class apActorFollowersAction extends ManagedAction
* @param int32 $limit
* @return Array of URIs
*/
public function generate_followers($profile, $since, $limit)
public static function generate_followers($profile, $since, $limit)
{
/* Fetch Followers */
try {
@ -130,6 +129,7 @@ class apActorFollowersAction extends ManagedAction
while ($sub->fetch()) {
$subs[] = ActivityPubPlugin::actor_uri($sub);
}
return $subs;
}
}

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -108,7 +107,7 @@ class apActorFollowingAction extends ManagedAction
}
/**
* Generates a list of people following given profile.
* Generates the list of those a given profile is stalking.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -124,7 +123,7 @@ class apActorOutboxAction extends ManagedAction
// TODO: Handle other types
if ($note->object_type == 'http://activitystrea.ms/schema/1.0/note') {
$notices[] = Activitypub_create::create_to_array(
ActivityPubPlugin::actor_uri($note->getProfile()),
ActivityPubPlugin::actor_uri($note->getProfile()),
Activitypub_notice::notice_to_array($note)
);
}

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -55,16 +54,50 @@ class apInboxAction extends ManagedAction
ActivityPubReturn::error('Only POST requests allowed.');
}
common_debug('ActivityPub Shared Inbox: Received a POST request.');
common_debug('ActivityPub Inbox: Received a POST request.');
$data = file_get_contents('php://input');
common_debug('ActivityPub Shared Inbox: Request contents: '.$data);
common_debug('ActivityPub Inbox: Request contents: '.$data);
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['actor'])) {
ActivityPubReturn::error('Actor not found in the request.');
}
$actor = ActivityPub_explorer::get_profile_from_url($data['actor']);
$actor_public_key = new Activitypub_rsa();
$actor_public_key = $actor_public_key->ensure_public_key($actor);
common_debug('ActivityPub Inbox: HTTP Signature: Validation will now start!');
$headers = $this->get_all_headers();
common_debug('ActivityPub Inbox: Request Headers: '.print_r($headers, true));
// TODO: Validate HTTP Signature, if it fails, attempt once with profile update
common_debug('ActivityPub Inbox: HTTP Signature: Authorized request. Will now start the inbox handler.');
try {
new Activitypub_inbox_handler($data);
new Activitypub_inbox_handler($data, $actor);
ActivityPubReturn::answer();
} catch (Exception $e) {
ActivityPubReturn::error($e->getMessage());
}
}
/**
* Get all HTTP header key/values as an associative array for the current request.
*
* @author PHP Manual Contributed Notes <joyview@gmail.com>
* @return string[string] The HTTP header key/value pairs.
*/
private function get_all_headers()
{
$headers = [];
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[strtolower(str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))))] = $value;
}
}
return $headers;
}
}

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -50,10 +49,7 @@ class Activitypub_accept extends Managed_DataObject
public static function accept_to_array($object)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => common_root_url().'accept_follow_from_'.urlencode($object['actor']).'_to_'.urlencode($object['object']),
'type' => 'Accept',
'actor' => $object['object'],

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -50,10 +49,7 @@ class Activitypub_announce extends Managed_DataObject
public static function announce_to_array($actor, $object)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
"type" => "Announce",
"actor" => $actor,
"object" => $object

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -50,10 +49,7 @@ class Activitypub_attachment extends Managed_DataObject
public static function attachment_to_array($attachment)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Document',
'mediaType' => $attachment->mimetype,
'url' => $attachment->getUrl(),

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -51,10 +50,7 @@ class Activitypub_create extends Managed_DataObject
public static function create_to_array($actor, $object)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $object['id'].'/create',
'type' => 'Create',
'to' => $object['to'],

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -50,10 +49,7 @@ class Activitypub_delete extends Managed_DataObject
public static function delete_to_array($actor, $object)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $object.'/delete',
'type' => 'Delete',
'actor' => $actor,

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -51,10 +50,7 @@ class Activitypub_follow extends Managed_DataObject
public static function follow_to_array($actor, $object)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => common_root_url().'follow_from_'.urlencode($actor).'_to_'.urlencode($object),
'type' => 'Follow',
'actor' => $actor,

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -51,10 +50,7 @@ class Activitypub_like extends Managed_DataObject
public static function like_to_array($actor, $object)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => common_root_url().'like_from_'.urlencode($actor).'_to_'.urlencode($object),
"type" => "Like",
"actor" => $actor,

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -51,10 +50,7 @@ class Activitypub_mention_tag extends Managed_DataObject
public static function mention_tag_to_array_from_values($href, $name)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
"type" => "Mention",
"href" => $href,
"name" => $name

View File

@ -19,7 +19,6 @@
*
* @category Plugin
* @package GNUsocial
* @author Daniel Supernault <danielsupernault@gmail.com>
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
@ -72,11 +71,8 @@ class Activitypub_notice extends Managed_DataObject
$to[]= 'https://www.w3.org/ns/activitystreams#Public';
$item = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'id' => $notice->getUrl(),
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => common_local_url('apNotice', ['id' => $notice->getID()]),
'type' => 'Note',
'published' => str_replace(' ', 'T', $notice->getCreated()).'Z',
'url' => $notice->getUrl(),
@ -93,7 +89,7 @@ class Activitypub_notice extends Managed_DataObject
// Is this a reply?
if (!empty($notice->reply_to)) {
$item['inReplyTo'] = Notice::getById($notice->reply_to)->getUrl();
$item['inReplyTo'] = common_local_url('apNotice', ['id' => $notice->getID()]);
$item['inReplyToAtomUri'] = Notice::getById($notice->reply_to)->getUrl();
}
@ -124,7 +120,6 @@ class Activitypub_notice extends Managed_DataObject
$id = $object['id']; // int32
$url = $object['url']; // string
$content = $object['content']; // string
$cc = $object['cc']; // array|string
// possible keys: ['inReplyTo', 'latitude', 'longitude', 'attachment']
$settings = [];
@ -192,43 +187,41 @@ class Activitypub_notice extends Managed_DataObject
if (isset($settings['inReplyTo'])) {
try {
$inReplyTo = ActivityPubPlugin::grab_notice_from_url($settings['inReplyTo']);
$act->context->replyToID = $inReplyTo->getUri();
$act->context->replyToUrl = $inReplyTo->getUrl();
} catch (Exception $e) {
throw new Exception('Invalid Object inReplyTo value: '.$e->getMessage());
// It failed to grab, maybe we got this note from another source
// (e.g.: OStatus) that handles this differently or we really
// failed to get it...
// Welp, nothing that we can do about, let's
// just fake we don't have such notice.
}
$act->context->replyToID = $inReplyTo->getUri();
$act->context->replyToUrl = $inReplyTo->getUrl();
} else {
$inReplyTo = null;
}
$discovery = new Activitypub_explorer;
// Generate Cc objects
$cc_profiles = [];
if (is_array($cc)) {
// Remove duplicates from Cc actors set
array_unique($cc);
foreach ($cc as $cc_url) {
try {
$cc_profiles = array_merge($cc_profiles, $discovery->lookup($cc_url));
} catch (Exception $e) {
// Invalid actor found, just let it go. // TODO: Fallback to OStatus
// Mentions
$mentions = [];
if (isset($object['tag']) && is_array($object['tag'])) {
foreach ($object['tag'] as $tag) {
if ($tag['type'] == 'Mention') {
$mentions[] = $tag['href'];
}
}
} elseif (empty($cc) || in_array($cc, ACTIVITYPUB_PUBLIC_TO)) {
// No need to do anything else at this point, let's just break out the if
} else {
}
$mentions_profiles = [];
$discovery = new Activitypub_explorer;
foreach ($mentions as $mention) {
try {
$cc_profiles = $discovery->lookup($cc);
$mentions_profiles[] = $discovery->lookup($mention)[0];
} catch (Exception $e) {
// Invalid actor found, just let it go. // TODO: Fallback to OStatus
}
}
unset($discovery);
foreach ($cc_profiles as $cp) {
$act->context->attention[ActivityPubPlugin::actor_uri($cp)] = 'http://activitystrea.ms/schema/1.0/person';
foreach ($mentions_profiles as $mp) {
$act->context->attention[ActivityPubPlugin::actor_uri($mp)] = 'http://activitystrea.ms/schema/1.0/person';
}
// Add location if that is set

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -96,10 +95,7 @@ class Activitypub_profile extends Managed_DataObject
$public_key = $rsa->ensure_public_key($profile);
unset($rsa);
$res = [
'@context' => [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $uri,
'type' => 'Person',
'following' => common_local_url('apActorFollowing', ['id' => $id]),
@ -242,9 +238,13 @@ class Activitypub_profile extends Managed_DataObject
*/
private static function create_from_local_profile(Profile $profile)
{
$url = $profile->getURL();
$url = $profile->getUri();
$inboxes = Activitypub_explorer::get_actor_inboxes_uri($url);
if ($inboxes == null) {
throw new Exception('This is not an ActivityPub user thus AProfile is politely refusing to proceed.');
}
$aprofile->created = $aprofile->modified = common_sql_now();
$aprofile = new Activitypub_profile;
@ -413,4 +413,57 @@ class Activitypub_profile extends Managed_DataObject
// TRANS: Exception. %s is a webfinger address.
throw new Exception(sprintf(_m('Could not find a valid profile for "%s".'), $addr));
}
/**
* Update remote user profile in local instance
* Depends on do_update
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array $res remote response
* @return Profile remote Profile object
*/
public static function update_profile($aprofile, $res)
{
// ActivityPub Profile
$aprofile->uri = $res['id'];
$aprofile->nickname = $res['preferredUsername'];
$aprofile->fullname = isset($res['name']) ? $res['name'] : null;
$aprofile->bio = isset($res['summary']) ? substr(strip_tags($res['summary']), 0, 1000) : null;
$aprofile->inboxuri = $res['inbox'];
$aprofile->sharedInboxuri = isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox'];
$profile = $aprofile->local_profile();
$profile->modified = $aprofile->modified = common_sql_now();
$fields = [
'uri' => 'profileurl',
'nickname' => 'nickname',
'fullname' => 'fullname',
'bio' => 'bio'
];
foreach ($fields as $af => $pf) {
$profile->$pf = $aprofile->$af;
}
// Profile
$profile->update();
$aprofile->update();
// Public Key
Activitypub_rsa::update_public_key($profile, $res['publicKey']['publicKeyPem']);
// Avatar
if (isset($res['icon']['url'])) {
try {
Activitypub_explorer::update_avatar($profile, $res['icon']['url']);
} catch (Exception $e) {
// Let the exception go, it isn't a serious issue
common_debug('An error ocurred while grabbing remote avatar'.$e->getMessage());
}
}
return $profile;
}
}

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -50,10 +49,7 @@ class Activitypub_reject extends Managed_DataObject
public static function reject_to_array($object)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
"type" => "Reject",
"object" => $object
];

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -84,16 +83,15 @@ class Activitypub_rsa extends Managed_DataObject
return $apRSA->private_key;
}
/**
* Guarantees a Public Key for a given profile.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile
* @return string The public key
* @throws Exception It should never occur
* @throws ServerException It should never occur, but if so, we break everything!
*/
public function ensure_public_key($profile)
public function ensure_public_key($profile, $fetch = true)
{
$this->profile_id = $profile->getID();
$apRSA = self::getKV('profile_id', $this->profile_id);
@ -103,7 +101,14 @@ class Activitypub_rsa extends Managed_DataObject
self::generate_keys($this->private_key, $this->public_key);
$this->store_keys();
} else {
throw new Exception('No Keys for this Profile. That\'s odd.');
// This should never happen, but try to recover!
if ($fetch) {
$res = Activitypub_explorer::get_remote_user_activity(ActivityPubPlugin::actor_uri($profile));
Activitypub_rsa::update_public_key($profile, $res['publicKey']['publicKeyPem']);
return ensure_public_key($profile, false);
} else {
throw new ServerException('Activitypub_rsa: Failed to find keys for given profile. That should have not happened!');
}
}
}
return $apRSA->public_key;
@ -121,7 +126,6 @@ class Activitypub_rsa extends Managed_DataObject
$this->created = $this->modified = common_sql_now();
$ok = $this->insert();
if ($ok === false) {
$profile->query('ROLLBACK');
throw new ServerException('Cannot save ActivityPub RSA.');
}
}
@ -152,4 +156,23 @@ class Activitypub_rsa extends Managed_DataObject
$public_key = $pubKey["key"];
unset($pubKey);
}
/**
* Update public key.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Profile $profile
* @param string $public_key
*/
public static function update_public_key($profile, $public_key)
{
// Public Key
$apRSA = new Activitypub_rsa();
$apRSA->profile_id = $profile->getID();
$apRSA->public_key = $public_key;
$apRSA->modified = common_sql_now();
if(!$apRSA->update()) {
$apRSA->insert();
}
}
}

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -50,10 +49,7 @@ class Activitypub_tag extends Managed_DataObject
public static function tag_to_array($tag)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
'name' => $tag,
'url' => common_local_url('tag', ['tag' => $tag])
];

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -50,10 +49,7 @@ class Activitypub_undo extends Managed_DataObject
public static function undo_to_array($object)
{
$res = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $object['id'].'/undo',
'type' => 'Undo',
'actor' => $object['actor'],

View File

@ -0,0 +1,104 @@
#!/usr/bin/env php
<?php
/**
* GNU social - a federating social network
*
* ActivityPubPlugin implementation for GNU Social
*
* 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 Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
define('INSTALLDIR', realpath(__DIR__ . '/../../..'));
$shortoptions = 'u:af';
$longoptions = ['uri=', 'all', 'force'];
$helptext = <<<END_OF_HELP
update_activitypub_profiles.php [options]
Refetch / update ActivityPub RSA keys, profile info and avatars. Useful if you
do something like accidentally delete your avatars directory when
you have no backup.
-u --uri ActivityPub profile URI to update
-a --all update all
END_OF_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
$quiet = have_option('q', 'quiet');
if (!$quiet) {
echo "ActivityPub Profiles updater will now start!\n";
echo "Summoning Diogo Cordeiro, Richard Stallman and Chuck Norris to help us with this task!\n";
}
if (have_option('u', 'uri')) {
$uri = get_option_value('u', 'uri');
$discovery = new Activitypub_explorer();
$discovery = $discovery->lookup($uri);
if (empty($discovery)) {
echo "Bad URI\n";
exit(1);
}
$user = $discovery->lookup($uri)[0];
try {
$res = Activitypub_explorer::get_remote_user_activity($uri);
} catch (Exception $e) {
echo $e->getMessage()."\n";
exit(1);
}
if (!$quiet) {
echo "Updated ".Activitypub_profile::update_profile($user, $res)->getBestName()."\n";
}
} else if (!have_option('a', 'all')) {
show_help();
exit(1);
}
$user = new Activitypub_profile();
$cnt = $user->find();
if (!empty($cnt)) {
if (!$quiet) {
echo "Found {$cnt} ActivityPub profiles:\n";
}
} else {
if (have_option('u', 'uri')) {
if (!$quiet) {
echo "Couldn't find an existing ActivityPub profile with that URI.\n";
}
} else {
if (!$quiet) {
echo "Couldn't find any existing ActivityPub profiles.\n";
}
}
exit(0);
}
while ($user->fetch()) {
try {
$res = Activitypub_explorer::get_remote_user_activity($user->uri);
if (!$quiet) {
echo "Updated ".Activitypub_profile::update_profile($user, $res)->getBestName()."\n";
}
} catch (Exception $e) {
// let it go
}
}

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -204,7 +203,7 @@ class Activitypub_explorer
common_debug('ActivityPub Explorer: Trying to grab a remote actor for '.$url);
if (!isset($this->temp_res)) {
$client = new HTTPClient();
$headers = array();
$headers = [];
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get($url, $headers);
@ -259,10 +258,10 @@ class Activitypub_explorer
// Avatar
if (isset($res['icon']['url'])) {
try {
$this->_store_avatar($profile, $res['icon']['url']);
$this->update_avatar($profile, $res['icon']['url']);
} catch (Exception $e) {
// Let the exception go, it isn't a serious issue
common_debug('An error ocurred while grabbing remote avatar'.$e->getMessage());
common_debug('ActivityPub Explorer: An error ocurred while grabbing remote avatar: '.$e->getMessage());
}
}
@ -278,7 +277,7 @@ class Activitypub_explorer
* @return Avatar The Avatar we have on disk.
* @throws Exception in various failure cases
*/
private function _store_avatar(Profile $profile, $url)
public static function update_avatar(Profile $profile, $url)
{
common_debug('ActivityPub Explorer: Started grabbing remote avatar from: '.$url);
if (!filter_var($url, FILTER_VALIDATE_URL)) {
@ -388,7 +387,7 @@ class Activitypub_explorer
public static function get_actor_inboxes_uri($url)
{
$client = new HTTPClient();
$headers = array();
$headers = [];
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get($url, $headers);
@ -416,7 +415,7 @@ class Activitypub_explorer
private function travel_collection($url)
{
$client = new HTTPClient();
$headers = array();
$headers = [];
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get($url, $headers);
@ -439,4 +438,27 @@ class Activitypub_explorer
return true;
}
/**
* Get a remote user array from its URL (this function is only used for
* profile updating and shall not be used for anything else)
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url User's url
* @throws Exception
*/
public static function get_remote_user_activity($url)
{
$client = new HTTPClient();
$headers = [];
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get($url, $headers);
$res = json_decode($response->getBody(), true);
if (Activitypub_explorer::validate_remote_response($res)) {
common_debug('ActivityPub Explorer: Found a valid remote actor for '.$url);
return $res;
}
throw new Exception('ActivityPub Explorer: Failed to get activity.');
}
}

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -49,8 +48,9 @@ class Activitypub_inbox_handler
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Array $activity Activity we are receiving
* @param Profile $actor_profile Actor originating the activity
*/
public function __construct($activity)
public function __construct($activity, $actor_profile = null)
{
$this->activity = $activity;
$this->object = $activity['object'];
@ -59,7 +59,11 @@ class Activitypub_inbox_handler
$this->validate_activity();
// Get Actor's Profile
$this->actor = ActivityPub_explorer::get_profile_from_url($this->activity['actor']);
if (!is_null($actor_profile)) {
$this->actor = $actor_profile;
} else {
$this->actor = ActivityPub_explorer::get_profile_from_url($this->activity['actor']);
}
// Handle the Activity
$this->process();
@ -98,7 +102,7 @@ class Activitypub_inbox_handler
case 'Like':
case 'Announce':
if (!filter_var($this->object, FILTER_VALIDATE_URL)) {
throw new Exception("Object is not a valid Object URI for Activity.");
throw new Exception('Object is not a valid Object URI for Activity.');
}
break;
case 'Undo':

View File

@ -20,7 +20,6 @@
* @category Plugin
* @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @author Daniel Supernault <danielsupernault@gmail.com>
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
@ -60,27 +59,37 @@ class Activitypub_postman
* @param Profile $from Profile of sender
* @param Array of Activitypub_profile $to destinataries
*/
public function __construct($from, $to = [])
public function __construct($from, $to)
{
$this->actor = $from;
$discovery = new Activitypub_explorer();
$this->to = $to;
$followers = apActorFollowersAction::generate_followers($this->actor, 0, null);
foreach ($followers as $sub) {
try {
$to[]= Activitypub_profile::from_profile($discovery->lookup($sub)[0]);
} catch (Exception $e) {
// Not an ActivityPub Remote Follower, let it go
}
}
unset($discovery);
$this->actor_uri = ActivityPubPlugin::actor_uri($this->actor);
$actor_private_key = new Activitypub_rsa();
$actor_private_key = $actor_private_key->get_private_key($this->actor);
$context = new Context([
'keys' => [$this->actor_uri."#public-key" => $actor_private_key],
'keys' => [$this->actor_uri.'#public-key' => $actor_private_key],
'algorithm' => 'rsa-sha256',
'headers' => ['(request-target)', 'date', 'content-type', 'accept', 'user-agent'],
]);
$this->to = $to;
$this->headers = [
'content-type' => 'application/activity+json',
'accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'user-agent' => 'GNUSocialBot v0.1 - https://gnu.io/social',
'date' => date('D, d M Y h:i:s') . ' GMT'
'date' => gmdate('D, d M Y H:i:s \G\M\T', time())
];
$handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context);
@ -228,15 +237,12 @@ class Activitypub_postman
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Notice $notice
*/
public function create($notice)
public function create_note($notice)
{
$data = Activitypub_create::create_to_array(
$this->actor_uri,
Activitypub_notice::notice_to_array($notice)
);
if (isset($notice->reply_to)) {
$data["object"]["reply_to"] = $notice->getParent()->getUrl();
}
$data = json_encode($data, JSON_UNESCAPED_SLASHES);
foreach ($this->to_inbox() as $inbox) {