From 43ebdfa7275466eb2d2b6ba3227231edefbfd556 Mon Sep 17 00:00:00 2001 From: Diogo Cordeiro Date: Sat, 4 Aug 2018 04:11:17 +0100 Subject: [PATCH] Inbox Reinvented --- ActivityPubPlugin.php | 44 +--- actions/apactorinbox.php | 130 ---------- actions/{inbox/Accept.php => apinbox.php} | 54 ++-- actions/apsharedinbox.php | 121 --------- actions/inbox/Announce.php | 42 --- actions/inbox/Create.php | 73 ------ actions/inbox/Delete.php | 38 --- actions/inbox/Follow.php | 65 ----- actions/inbox/Like.php | 42 --- actions/inbox/Reject.php | 32 --- actions/inbox/Undo.php | 80 ------ classes/Activitypub_accept.php | 28 ++ classes/Activitypub_announce.php | 14 +- classes/Activitypub_attachment.php | 18 +- classes/Activitypub_create.php | 30 ++- classes/Activitypub_delete.php | 5 +- classes/Activitypub_error.php | 4 +- classes/Activitypub_follow.php | 30 +++ classes/Activitypub_mention_tag.php | 4 + classes/Activitypub_notice.php | 95 ++++--- classes/Activitypub_profile.php | 4 +- classes/Activitypub_reject.php | 12 +- classes/Activitypub_tag.php | 17 +- classes/Activitypub_undo.php | 29 +++ tests/Unit/HTTPSignatureTest.php | 2 +- utils/Activitypub_activityverb2.php | 102 ++++++++ utils/discoveryhints.php | 12 +- utils/inbox_handler.php | 296 ++++++++++++++++++++++ utils/postman.php | 42 ++- 29 files changed, 700 insertions(+), 765 deletions(-) delete mode 100755 actions/apactorinbox.php rename actions/{inbox/Accept.php => apinbox.php} (53%) delete mode 100755 actions/apsharedinbox.php delete mode 100755 actions/inbox/Announce.php delete mode 100755 actions/inbox/Create.php delete mode 100755 actions/inbox/Delete.php delete mode 100755 actions/inbox/Follow.php delete mode 100755 actions/inbox/Like.php delete mode 100755 actions/inbox/Reject.php delete mode 100755 actions/inbox/Undo.php create mode 100644 utils/Activitypub_activityverb2.php create mode 100644 utils/inbox_handler.php diff --git a/ActivityPubPlugin.php b/ActivityPubPlugin.php index 7a94aeb..0c96be0 100755 --- a/ActivityPubPlugin.php +++ b/ActivityPubPlugin.php @@ -38,6 +38,7 @@ require_once __DIR__ . DIRECTORY_SEPARATOR . 'utils' . DIRECTORY_SEPARATOR . 'd require_once __DIR__ . DIRECTORY_SEPARATOR . 'utils' . DIRECTORY_SEPARATOR . 'AcceptHeader.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 . 'inbox_handler.php'; // So that this isn't hardcoded everywhere define('ACTIVITYPUB_BASE_ACTOR_URI', common_root_url().'index.php/user/'); @@ -122,40 +123,9 @@ class ActivityPubPlugin extends Plugin $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); - $settings = []; - try { - Activitypub_notice::validate_remote_notice($res); - } catch (Exception $e) { - common_debug('ActivityPubPlugin Notice Grabber: Invalid potential remote notice while processing id: '.$url. '. He returned the following: '.json_encode($res, JSON_UNESCAPED_SLASHES)); - throw $e; - } - - if (isset($res->inReplyTo)) { - $settings['inReplyTo'] = $res->inReplyTo; - } - if (isset($res->latitude)) { - $settings['latitude'] = $res->latitude; - } - if (isset($res->longitude)) { - $settings['longitude'] = $res->longitude; - } - try { - return Activitypub_notice::create_notice( - ActivityPub_explorer::get_profile_from_url($res['attributedTo']), - $res['id'], - $res['url'], - $res['content'], - $res['cc'], - $settings - ); - } catch (Exception $e) { - common_debug('ActivityPubPlugin Notice Grabber: failed to find: '.$url.' online.'); - throw $e; - } - - // When all the above failed in its quest of grabbing the Notice - throw new Exception('Notice not found.'); + $object = json_decode($response->getBody(), true); + Activitypub_notice::validate_note($object); + return Activitypub_notice::create_notice($object); } /** @@ -210,13 +180,13 @@ class ActivityPubPlugin extends Plugin $m->connect( 'user/:id/inbox.json', - ['action' => 'apActorInbox'], + ['action' => 'apInbox'], ['id' => '[0-9]+'] ); $m->connect( 'inbox.json', - ['action' => 'apSharedInbox'] + ['action' => 'apInbox'] ); } @@ -941,7 +911,7 @@ class ActivityPubReturn * @param int32 $code Status Code * @return void */ - public static function error($m, $code = 500) + public static function error($m, $code = 400) { http_response_code($code); header('Content-Type: application/activity+json'); diff --git a/actions/apactorinbox.php b/actions/apactorinbox.php deleted file mode 100755 index 343cc3c..0000000 --- a/actions/apactorinbox.php +++ /dev/null @@ -1,130 +0,0 @@ -. - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @author Daniel Supernault - * @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/ - */ -if (!defined('GNUSOCIAL')) { - exit(1); -} - -/** - * Actor's Inbox - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @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/ - */ -class apActorInboxAction extends ManagedAction -{ - protected $needLogin = false; - protected $canPost = true; - - /** - * Handle the Actor Inbox request - * - * @author Diogo Cordeiro - * @return void - */ - protected function handle() - { - try { - $profile = Profile::getByID($this->trimmed('id')); - } catch (Exception $e) { - ActivityPubReturn::error('Invalid Actor URI.', 404); - } - - if (!$profile->isLocal()) { - ActivityPubReturn::error('This is not a local user.'); - } - - if ($_SERVER['REQUEST_METHOD'] !== 'POST') { - ActivityPubReturn::error('C2S not implemented just yet.'); - } - - common_debug('ActivityPub Inbox: Received a POST request.'); - $data = file_get_contents('php://input'); - common_debug('ActivityPub Inbox: Request contents: '.$data); - $data = json_decode(file_get_contents('php://input'), true); - - // Validate data - if (!(isset($data['type']))) { - ActivityPubReturn::error('Type was not specified.'); - } - if (!isset($data['actor'])) { - ActivityPubReturn::error('Actor was not specified.'); - } - if (!isset($data['object'])) { - ActivityPubReturn::error('Object was not specified.'); - } - - // Get valid Actor object - try { - $actor_profile = ActivityPub_explorer::get_profile_from_url($data['actor']); - } catch (Exception $e) { - ActivityPubReturn::error($e->getMessage(), 404); - } - - $cc = [$profile]; - - // Process request - define('INBOX_HANDLERS', __DIR__ . DIRECTORY_SEPARATOR . 'inbox' . DIRECTORY_SEPARATOR); - switch ($data['type']) { - // Data available: - // Profile $actor_profile Actor performing the action - // string|object $data->object Object to be handled - // Array|String $cc Destinataries - // Profile $profile Local user to whom this action is directed - case 'Create': - $cc = array_merge ([$profile], $data['object']['cc']); - require_once INBOX_HANDLERS . 'Create.php'; - break; - case 'Follow': - require_once INBOX_HANDLERS . 'Follow.php'; - break; - case 'Like': - require_once INBOX_HANDLERS . 'Like.php'; - break; - case 'Announce': - require_once INBOX_HANDLERS . 'Announce.php'; - break; - case 'Undo': - require_once INBOX_HANDLERS . 'Undo.php'; - break; - case 'Delete': - require_once INBOX_HANDLERS . 'Delete.php'; - break; - case 'Accept': - require_once INBOX_HANDLERS . 'Accept.php'; - break; - case 'Reject': - require_once INBOX_HANDLERS . 'Reject.php'; - break; - default: - ActivityPubReturn::error('Invalid type value.'); - } - } -} diff --git a/actions/inbox/Accept.php b/actions/apinbox.php similarity index 53% rename from actions/inbox/Accept.php rename to actions/apinbox.php index a7302bb..f8d818d 100755 --- a/actions/inbox/Accept.php +++ b/actions/apinbox.php @@ -29,30 +29,42 @@ if (!defined('GNUSOCIAL')) { exit(1); } -// Validate data -if (!isset($data['object']['type'])) { - ActivityPubReturn::error("Type was not specified."); -} +/** + * Inbox Request Handler + * + * @category Plugin + * @package GNUsocial + * @author Diogo Cordeiro + * @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/ + */ +class apInboxAction extends ManagedAction +{ + protected $needLogin = false; + protected $canPost = true; -switch ($data['object']['type']) { - case "Follow": - // Validate data - if (!isset($data['object']['object'])) { - ActivityPubReturn::error("Object Actor URL was not specified."); + /** + * Handle the Inbox request + * + * @author Diogo Cordeiro + * @return void + */ + protected function handle() + { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + ActivityPubReturn::error('Only POST requests allowed.'); } - // Get valid Object profile + + common_debug('ActivityPub Shared Inbox: Received a POST request.'); + $data = file_get_contents('php://input'); + common_debug('ActivityPub Shared Inbox: Request contents: '.$data); + $data = json_decode(file_get_contents('php://input'), true); + try { - $object_profile = new Activitypub_explorer; - $object_profile = $object_profile->lookup($data['object']['object'])[0]; + new Activitypub_inbox_handler($data); + ActivityPubReturn::answer(); } catch (Exception $e) { - ActivityPubReturn::error("Invalid Object Actor URL.", 404); + ActivityPubReturn::error($e->getMessage()); } - - $pending_list = new Activitypub_pending_follow_requests($actor_profile->getID(), $object_profile->getID()); - $pending_list->remove(); - ActivityPubReturn::answer(); // You are now being followed by this person. - break; - default: - ActivityPubReturn::error("Invalid object type."); - break; + } } diff --git a/actions/apsharedinbox.php b/actions/apsharedinbox.php deleted file mode 100755 index e59b771..0000000 --- a/actions/apsharedinbox.php +++ /dev/null @@ -1,121 +0,0 @@ -. - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @author Daniel Supernault - * @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/ - */ -if (!defined('GNUSOCIAL')) { - exit(1); -} - -/** - * Shared Inbox Handler - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @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/ - */ -class apSharedInboxAction extends ManagedAction -{ - protected $needLogin = false; - protected $canPost = true; - - /** - * Handle the Shared Inbox request - * - * @author Diogo Cordeiro - * @return void - */ - protected function handle() - { - if ($_SERVER['REQUEST_METHOD'] !== 'POST') { - ActivityPubReturn::error('Only POST requests allowed.'); - } - - common_debug('ActivityPub Shared Inbox: Received a POST request.'); - $data = file_get_contents('php://input'); - common_debug('ActivityPub Shared Inbox: Request contents: '.$data); - $data = json_decode(file_get_contents('php://input'), true); - - // Validate data - if (!isset($data['type'])) { - ActivityPubReturn::error('Type was not specified.'); - } - if (!isset($data['actor'])) { - ActivityPubReturn::error('Actor was not specified.'); - } - if (!isset($data['object'])) { - ActivityPubReturn::error('Object was not specified.'); - } - - // Get valid Actor object - try { - $actor_profile = ActivityPub_explorer::get_profile_from_url($data['actor']); - } catch (Exception $e) { - ActivityPubReturn::error($e->getMessage(), 404); - } - - $cc = []; - - // Process request - define('INBOX_HANDLERS', __DIR__ . DIRECTORY_SEPARATOR . 'inbox' . DIRECTORY_SEPARATOR); - switch ($data['type']) { - // Data available: - // Profile $actor_profile Actor performing the action - // string|object $data->object Object to be handled - // Array|String $cc Destinataries - // string|object $data->object - case 'Create': - $cc = $data['object']['cc']; - $res = $data['object']; - require_once INBOX_HANDLERS . 'Create.php'; - break; - case 'Follow': - require_once INBOX_HANDLERS . 'Follow.php'; - break; - case 'Like': - require_once INBOX_HANDLERS . 'Like.php'; - break; - case 'Announce': - require_once INBOX_HANDLERS . 'Announce.php'; - break; - case 'Undo': - require_once INBOX_HANDLERS . 'Undo.php'; - break; - case 'Delete': - require_once INBOX_HANDLERS . 'Delete.php'; - break; - case 'Accept': - require_once INBOX_HANDLERS . 'Accept.php'; - break; - case 'Reject': - require_once INBOX_HANDLERS . 'Reject.php'; - break; - default: - ActivityPubReturn::error('Invalid type value.'); - } - } -} diff --git a/actions/inbox/Announce.php b/actions/inbox/Announce.php deleted file mode 100755 index 5596b9a..0000000 --- a/actions/inbox/Announce.php +++ /dev/null @@ -1,42 +0,0 @@ -. - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @author Daniel Supernault - * @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/ - */ -if (!defined('GNUSOCIAL')) { - exit(1); -} - -try { - try { - $object_notice = ActivityPubPlugin::grab_notice_from_url($data['object']); - } catch (Exception $e) { - ActivityPubReturn::error('Invalid Object specified.'); - } - $object_notice->repeat($actor_profile, 'ActivityPub'); - ActivityPubReturn::answer(); -} catch (Exception $e) { - ActivityPubReturn::error($e->getMessage(), 403); -} diff --git a/actions/inbox/Create.php b/actions/inbox/Create.php deleted file mode 100755 index 80a0e95..0000000 --- a/actions/inbox/Create.php +++ /dev/null @@ -1,73 +0,0 @@ -. - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @author Daniel Supernault - * @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/ - */ -if (!defined('GNUSOCIAL')) { - exit(1); -} - -$valid_object_types = ['Note']; - -try { - Activitypub_notice::validate_remote_notice($res); -} catch (Exception $e) { - common_debug('ActivityPub Inbox Create Note: Invalid note: '.$e->getMessage()); - ActivityPubReturn::error($e->getMessage()); -} - -$settings = []; - -if (isset($res['inReplyTo'])) { - $settings['inReplyTo'] = $res['inReplyTo']; -} -if (isset($res['latitude'])) { - $settings['latitude'] = $res['latitude']; -} -if (isset($res['longitude'])) { - $settings['longitude'] = $res['longitude']; -} -if (isset($res['attachment'])) { - $settings['attachment'] = $res['attachment']; -} - -try { - Activitypub_notice::create_notice( - $actor_profile, - $res['id'], - $res['url'], - $res['content'], - $cc, - $settings - ); - ActivityPubReturn::answer(); -} catch (AlreadyFulfilledException $e) { - // Notice URI already exists - common_debug('ActivityPub Inbox Create Note: Note already exists: '.$e->getMessage()); - ActivityPubReturn::error('Note already exists.', 202); -} catch (Exception $e) { - common_debug('ActivityPub Inbox Create Note: Failed Create Note: '.$e->getMessage()); - ActivityPubReturn::error($e->getMessage()); -} diff --git a/actions/inbox/Delete.php b/actions/inbox/Delete.php deleted file mode 100755 index db02a3f..0000000 --- a/actions/inbox/Delete.php +++ /dev/null @@ -1,38 +0,0 @@ -. - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @author Daniel Supernault - * @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/ - */ -if (!defined('GNUSOCIAL')) { - exit(1); -} - -try { - $notice = ActivityPubPlugin::grab_notice_from_url($data['object']); - $notice->deleteAs($actor_profile); - ActivityPubReturn::answer(); -} catch (Exception $e) { - ActivityPubReturn::error($e->getMessage(), 403); -} diff --git a/actions/inbox/Follow.php b/actions/inbox/Follow.php deleted file mode 100755 index 992d00d..0000000 --- a/actions/inbox/Follow.php +++ /dev/null @@ -1,65 +0,0 @@ -. - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @author Daniel Supernault - * @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/ - */ -if (!defined('GNUSOCIAL')) { - exit(1); -} - -// Validate Object -if (!filter_var($data['object'], FILTER_VALIDATE_URL)) { - ActivityPubReturn::error('Invalid Object Actor URL.'); -} - -// Ensure valid Object profile -try { - if (!isset($profile)) { - $object_profile = new Activitypub_explorer; - $object_profile = $object_profile->lookup($data['object'])[0]; - } else { - $object_profile = $profile; - unset($profile); - } -} catch (Exception $e) { - ActivityPubReturn::error('Invalid Object Actor URL.', 404); -} - -// Get Actor's Aprofile -$actor_aprofile = Activitypub_profile::from_profile($actor_profile); - -if (!Subscription::exists($actor_profile, $object_profile)) { - Subscription::start($actor_profile, $object_profile); - common_debug('ActivityPubPlugin: Accepted Follow request from '.$data['actor'].' to '.$data['object']); - - // Notify remote instance that we have accepted their request - common_debug('ActivityPubPlugin: Notifying remote instance that we have accepted their Follow request request from '.$data['actor'].' to '.$data['object']); - $postman = new Activitypub_postman($actor_profile, [$actor_aprofile]); - $postman->follow(); - ActivityPubReturn::answer(); -} else { - common_debug('ActivityPubPlugin: Received a repeated Follow request from '.$data['actor'].' to '.$data['object']); - ActivityPubReturn::error('Already following.', 202); -} diff --git a/actions/inbox/Like.php b/actions/inbox/Like.php deleted file mode 100755 index 551f1d3..0000000 --- a/actions/inbox/Like.php +++ /dev/null @@ -1,42 +0,0 @@ -. - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @author Daniel Supernault - * @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/ - */ -if (!defined('GNUSOCIAL')) { - exit(1); -} - -try { - try { - $object_notice = ActivityPubPlugin::grab_notice_from_url($data['object']); - } catch (Exception $e) { - ActivityPubReturn::error('Invalid Object specified.'); - } - Fave::addNew($actor_profile, $object_notice); - ActivityPubReturn::answer(); -} catch (Exception $e) { - ActivityPubReturn::error($e->getMessage(), 403); -} diff --git a/actions/inbox/Reject.php b/actions/inbox/Reject.php deleted file mode 100755 index e061acc..0000000 --- a/actions/inbox/Reject.php +++ /dev/null @@ -1,32 +0,0 @@ -. - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @author Daniel Supernault - * @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/ - */ -if (!defined('GNUSOCIAL')) { - exit(1); -} - -// This is a dummy file as there is nothing to do if we fall in this case diff --git a/actions/inbox/Undo.php b/actions/inbox/Undo.php deleted file mode 100755 index 8b942d9..0000000 --- a/actions/inbox/Undo.php +++ /dev/null @@ -1,80 +0,0 @@ -. - * - * @category Plugin - * @package GNUsocial - * @author Diogo Cordeiro - * @author Daniel Supernault - * @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/ - */ -if (!defined('GNUSOCIAL')) { - exit(1); -} - -// Validate data -if (!isset($data['type'])) { - ActivityPubReturn::error('Type was not specified.'); -} - -switch ($data['object']['type']) { - case 'Like': - try { - // Validate data - if (!isset($data['object']['object'])) { - ActivityPubReturn::error('Notice URI was not specified.'); - } - Fave::removeEntry($actor_profile, ActivityPubPlugin::grab_notice_from_url($data['object']['object'])); - // Notice disfavorited successfully. - ActivityPubReturn::answer(); - } catch (Exception $e) { - ActivityPubReturn::error($e->getMessage(), 403); - } - break; - case 'Follow': - // Validate data - if (!isset($data['object']['object'])) { - ActivityPubReturn::error('Object Actor URL was not specified.'); - } - // Get valid Object profile - try { - $object_profile = new Activitypub_explorer; - $object_profile = $object_profile->lookup($data['object']['object'])[0]; - } catch (Exception $e) { - ActivityPubReturn::error('Invalid Object Actor URL.', 404); - } - - if (Subscription::exists($actor_profile, $object_profile)) { - Subscription::cancel($actor_profile, $object_profile); - // You are no longer following this person. - ActivityPubReturn::answer(); - } else { - // 409: You are not following this person already. - ActivityPubReturn::answer(); - } - break; - case 'Announce': - // This is a dummy entry point as GNU Social doesn't allow Undo Announce - ActivityPubReturn::answer(); - // no break - default: - ActivityPubReturn::error('Invalid object type.'); - break; -} diff --git a/classes/Activitypub_accept.php b/classes/Activitypub_accept.php index e28f1c5..4af6ad5 100755 --- a/classes/Activitypub_accept.php +++ b/classes/Activitypub_accept.php @@ -61,4 +61,32 @@ class Activitypub_accept extends Managed_DataObject ]; return $res; } + + /** + * Verifies if a given object is acceptable for an Accept Activity. + * + * @author Diogo Cordeiro + * @param Array $object + * @throws Exception + */ + public static function validate_object($object) + { + if (!is_array($object)) { + throw new Exception('Invalid Object Format for Accept Activity.'); + } + if (!isset($object['type'])) { + throw new Exception('Object type was not specified for Accept Activity.'); + } + switch ($object['type']) { + case 'Follow': + // Validate data + if (!filter_var($object['object'], FILTER_VALIDATE_URL)) { + throw new Exception("Object is not a valid Object URI for Activity."); + } + break; + default: + throw new Exception('This is not a supported Object Type for Accept Activity.'); + } + return true; + } } diff --git a/classes/Activitypub_announce.php b/classes/Activitypub_announce.php index 320ec91..8a04152 100755 --- a/classes/Activitypub_announce.php +++ b/classes/Activitypub_announce.php @@ -49,11 +49,15 @@ class Activitypub_announce extends Managed_DataObject */ public static function announce_to_array($actor, $object) { - $res = array("@context" => "https://www.w3.org/ns/activitystreams", - "type" => "Announce", - "actor" => $actor, - "object" => $object - ); + $res = [ + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], + "type" => "Announce", + "actor" => $actor, + "object" => $object + ]; return $res; } } diff --git a/classes/Activitypub_attachment.php b/classes/Activitypub_attachment.php index f053f0e..06e48a5 100755 --- a/classes/Activitypub_attachment.php +++ b/classes/Activitypub_attachment.php @@ -50,18 +50,22 @@ class Activitypub_attachment extends Managed_DataObject public static function attachment_to_array($attachment) { $res = [ - 'type' => 'Document', - 'mediaType' => $attachment->mimetype, - 'url' => $attachment->getUrl(), - 'size' => intval($attachment->size), // $attachment->getSize () - 'name' => $attachment->getTitle(), + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], + 'type' => 'Document', + 'mediaType' => $attachment->mimetype, + 'url' => $attachment->getUrl(), + 'size' => intval($attachment->size), // $attachment->getSize () + 'name' => $attachment->getTitle(), ]; // Image if (substr($res["mediaType"], 0, 5) == "image") { $res["meta"]= [ - 'width' => $attachment->width, - 'height' => $attachment->height + 'width' => $attachment->width, + 'height' => $attachment->height ]; } diff --git a/classes/Activitypub_create.php b/classes/Activitypub_create.php index f516456..8b1229d 100755 --- a/classes/Activitypub_create.php +++ b/classes/Activitypub_create.php @@ -51,7 +51,10 @@ class Activitypub_create extends Managed_DataObject public static function create_to_array($id, $actor, $object) { $res = [ - '@context' => 'https://www.w3.org/ns/activitystreams', + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], 'id' => $id.'/create', 'type' => 'Create', 'to' => $object['to'], @@ -61,4 +64,29 @@ class Activitypub_create extends Managed_DataObject ]; return $res; } + + /** + * Verifies if a given object is acceptable for a Create Activity. + * + * @author Diogo Cordeiro + * @param Array $object + * @throws Exception + */ + public static function validate_object($object) + { + if (!is_array($object)) { + throw new Exception('Invalid Object Format for Create Activity.'); + } + if (!isset($object['type'])) { + throw new Exception('Object type was not specified for Create Activity.'); + } + switch ($object['type']) { + case 'Note': + // Validate data + Activitypub_notice::validate_note($object); + break; + default: + throw new Exception('This is not a supported Object Type for Create Activity.'); + } + } } diff --git a/classes/Activitypub_delete.php b/classes/Activitypub_delete.php index 8b7c323..64f56a4 100755 --- a/classes/Activitypub_delete.php +++ b/classes/Activitypub_delete.php @@ -50,7 +50,10 @@ class Activitypub_delete extends Managed_DataObject public static function delete_to_array($actor, $object) { $res = [ - '@context' => 'https://www.w3.org/ns/activitystreams', + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], 'id' => $object.'/delete', 'type' => 'Delete', 'actor' => $actor, diff --git a/classes/Activitypub_error.php b/classes/Activitypub_error.php index 7941b6c..c7fabb8 100755 --- a/classes/Activitypub_error.php +++ b/classes/Activitypub_error.php @@ -50,8 +50,8 @@ class Activitypub_error extends Managed_DataObject public static function error_message_to_array($m) { $res = [ - 'error'=> $m - ]; + 'error'=> $m + ]; return $res; } } diff --git a/classes/Activitypub_follow.php b/classes/Activitypub_follow.php index 289456d..f8f42f0 100755 --- a/classes/Activitypub_follow.php +++ b/classes/Activitypub_follow.php @@ -62,4 +62,34 @@ class Activitypub_follow extends Managed_DataObject ]; return $res; } + + /** + * Handles a Follow Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor_profile Remote Actor + * @param string $object Local Actor + * @throws Exception + */ + public static function follow($actor_profile, $object) + { + // Get Actor's Aprofile + $actor_aprofile = Activitypub_profile::from_profile($actor_profile); + + // Get Object profile + $object_profile = new Activitypub_explorer; + $object_profile = $object_profile->lookup($object)[0]; + + if (!Subscription::exists($actor_profile, $object_profile)) { + Subscription::start($actor_profile, $object_profile); + common_debug('ActivityPubPlugin: Accepted Follow request from '.ActivityPubPlugin::actor_uri($actor_profile).' to '.$object); + } else { + common_debug('ActivityPubPlugin: Received a repeated Follow request from '.ActivityPubPlugin::actor_uri($actor_profile).' to '.$object); + } + + // Notify remote instance that we have accepted their request + common_debug('ActivityPubPlugin: Notifying remote instance that we have accepted their Follow request request from '.ActivityPubPlugin::actor_uri($actor_profile).' to '.$object); + $postman = new Activitypub_postman($actor_profile, [$actor_aprofile]); + $postman->accept_follow(); + } } diff --git a/classes/Activitypub_mention_tag.php b/classes/Activitypub_mention_tag.php index a143352..717fb0c 100755 --- a/classes/Activitypub_mention_tag.php +++ b/classes/Activitypub_mention_tag.php @@ -51,6 +51,10 @@ 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' + ], "type" => "Mention", "href" => $href, "name" => $name diff --git a/classes/Activitypub_notice.php b/classes/Activitypub_notice.php index 23906f5..68e6440 100755 --- a/classes/Activitypub_notice.php +++ b/classes/Activitypub_notice.php @@ -72,12 +72,15 @@ class Activitypub_notice extends Managed_DataObject $to[]= 'https://www.w3.org/ns/activitystreams#Public'; $item = [ - '@context' => 'https://www.w3.org/ns/activitystreams', + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], 'id' => $notice->getUrl(), 'type' => 'Note', 'published' => str_replace(' ', 'T', $notice->getCreated()).'Z', 'url' => $notice->getUrl(), - 'atributtedTo' => ActivityPubPlugin::actor_uri($profile), + 'attributedTo' => ActivityPubPlugin::actor_uri($profile), 'to' => ['https://www.w3.org/ns/activitystreams#Public'], 'cc' => $cc, 'atomUri' => $notice->getUrl(), @@ -107,21 +110,42 @@ class Activitypub_notice extends Managed_DataObject } /** - * Create a Notice via ActivityPub data. + * Create a Notice via ActivityPub Note Object. * Returns created Notice. * * @author Diogo Cordeiro - * @param Profile $actor_profile - * @param int32 $id - * @param string $url - * @param string $content - * @param array|string $cc - * @param array $settings possible keys: ['inReplyTo', 'latitude', 'longitude', 'attachment'] + * @param Array $object + * @param Profile|null $actor_profile * @return Notice * @throws Exception */ - public static function create_notice($actor_profile, $id, $url, $content, $cc, $settings) + public static function create_notice($object, $actor_profile = null) { + $id = $object['id']; // int32 + $url = $object['url']; // string + $content = $object['content']; // string + $cc = $object['cc']; // array|string + + // possible keys: ['inReplyTo', 'latitude', 'longitude', 'attachment'] + $settings = []; + if (isset($object['inReplyTo'])) { + $settings['inReplyTo'] = $object['inReplyTo']; + } + if (isset($object['latitude'])) { + $settings['latitude'] = $object['latitude']; + } + if (isset($object['longitude'])) { + $settings['longitude'] = $object['longitude']; + } + if (isset($object['attachment'])) { + $settings['attachment'] = $object['attachment']; + } + + // Ensure Actor Profile + if (is_null($actor_profile)) { + $actor_profile = ActivityPub_explorer::get_profile_from_url($object['actor']); + } + $act = new Activity(); $act->verb = ActivityVerb::POST; $act->time = time(); @@ -150,6 +174,7 @@ class Activitypub_notice extends Managed_DataObject $imagefile = new ImageFile(null, $temp_filename); $filename = hash(File::FILEHASH_ALG, $imgData).image_type_to_extension($imagefile->type); + unset($imgData); // No need to carry this in memory. rename($temp_filename, File::path($filename)); common_debug('ActivityPub Create Notice: Moved image from: '.$temp_filename.' to '.$filename); @@ -158,11 +183,9 @@ class Activitypub_notice extends Managed_DataObject } catch (Exception $e) { common_debug('ActivityPub Create Notice: Something went wrong while processing the image from: '.$attach_url.' details: '.$e->getMessage()); unlink($temp_filename); - $content .= ($content==='' ? '' : ' ') . $attach_url; } - } else { - $content .= ($content==='' ? '' : ' ') . $attach_url; } + $content .= ($content==='' ? '' : ' ') . '
Remote Attachment Source'; } // Is this a reply? @@ -181,12 +204,13 @@ class Activitypub_notice extends Managed_DataObject $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 = array_merge($cc, $discovery->lookup($cc_url)); + $cc_profiles = array_merge($cc_profiles, $discovery->lookup($cc_url)); } catch (Exception $e) { // Invalid actor found, just let it go. // TODO: Fallback to OStatus } @@ -195,7 +219,7 @@ class Activitypub_notice extends Managed_DataObject // No need to do anything else at this point, let's just break out the if } else { try { - $cc = array_merge($cc, $discovery->lookup($cc)); + $cc_profiles = $discovery->lookup($cc); } catch (Exception $e) { // Invalid actor found, just let it go. // TODO: Fallback to OStatus } @@ -203,8 +227,8 @@ class Activitypub_notice extends Managed_DataObject unset($discovery); - foreach ($cc as $tp) { - $act->context->attention[ActivityPubPlugin::actor_uri($tp)] = 'http://activitystrea.ms/schema/1.0/person'; + foreach ($cc_profiles as $cp) { + $act->context->attention[ActivityPubPlugin::actor_uri($cp)] = 'http://activitystrea.ms/schema/1.0/person'; } // Add location if that is set @@ -224,56 +248,51 @@ class Activitypub_notice extends Managed_DataObject // Finally add the activity object to our activity $act->objects[] = $actobj; - try { - $note = Notice::saveActivity($act, $actor_profile, $options); - if (ActivityPubPlugin::$store_images_from_remote_notes_attachments && isset($mediaFile)) { - $mediaFile->attachToNotice($note); - } - return $note; - } catch (Exception $e) { - throw $e; + $note = Notice::saveActivity($act, $actor_profile, $options); + if (ActivityPubPlugin::$store_images_from_remote_notes_attachments && isset($mediaFile)) { + $mediaFile->attachToNotice($note); } + return $note; } /** - * Validates a remote notice. + * Validates a note. * * @author Diogo Cordeiro - * @param Array $data - * @return boolean true in case of success + * @param Array $object * @throws Exception */ - public static function validate_remote_notice($data) + public static function validate_note($object) { - /*if (!isset($data['attributedTo'])) { + if (!isset($object['attributedTo'])) { common_debug('ActivityPub Notice Validator: Rejected because attributedTo was not specified.'); throw new Exception('No attributedTo specified.'); } - if (!isset($data['id'])) { + if (!isset($object['id'])) { common_debug('ActivityPub Notice Validator: Rejected because Object ID was not specified.'); throw new Exception('Object ID not specified.'); - } elseif (!filter_var($data['id'], FILTER_VALIDATE_URL)) { + } elseif (!filter_var($object['id'], FILTER_VALIDATE_URL)) { common_debug('ActivityPub Notice Validator: Rejected because Object ID is invalid.'); throw new Exception('Invalid Object ID.'); } - if (!isset($data['type']) || $data['type'] !== 'Note') { + if (!isset($object['type']) || $object['type'] !== 'Note') { common_debug('ActivityPub Notice Validator: Rejected because of Type.'); throw new Exception('Invalid Object type.'); } - if (!isset($data['content'])) { + if (!isset($object['content'])) { common_debug('ActivityPub Notice Validator: Rejected because Content was not specified.'); throw new Exception('Object content was not specified.'); } - if (!isset($data['url'])) { + if (!isset($object['url'])) { throw new Exception('Object URL was not specified.'); - } elseif (!filter_var($data['url'], FILTER_VALIDATE_URL)) { + } elseif (!filter_var($object['url'], FILTER_VALIDATE_URL)) { common_debug('ActivityPub Notice Validator: Rejected because Object URL is invalid.'); throw new Exception('Invalid Object URL.'); } - if (!isset($data['cc'])) { + if (!isset($object['cc'])) { common_debug('ActivityPub Notice Validator: Rejected because Object CC was not specified.'); throw new Exception('Object CC was not specified.'); - }*/ + } return true; } } diff --git a/classes/Activitypub_profile.php b/classes/Activitypub_profile.php index 6a5094e..6e7f491 100755 --- a/classes/Activitypub_profile.php +++ b/classes/Activitypub_profile.php @@ -105,7 +105,7 @@ class Activitypub_profile extends Managed_DataObject 'following' => common_local_url("apActorFollowing", array("id" => $id)), 'followers' => common_local_url("apActorFollowers", array("id" => $id)), 'liked' => common_local_url("apActorLiked", array("id" => $id)), - 'inbox' => common_local_url("apActorInbox", array("id" => $id)), + 'inbox' => common_local_url("apInbox", array("id" => $id)), 'preferredUsername' => $profile->getNickname(), 'name' => $profile->getBestName(), 'summary' => ($desc = $profile->getDescription()) == null ? "" : $desc, @@ -128,7 +128,7 @@ class Activitypub_profile extends Managed_DataObject ]; if ($profile->isLocal()) { - $res['endpoints']['sharedInbox'] = common_local_url('apSharedInbox'); + $res['endpoints']['sharedInbox'] = common_local_url('apInbox'); } else { $aprofile = new Activitypub_profile(); $aprofile = $aprofile->from_profile($profile); diff --git a/classes/Activitypub_reject.php b/classes/Activitypub_reject.php index abf3907..d0a0095 100755 --- a/classes/Activitypub_reject.php +++ b/classes/Activitypub_reject.php @@ -49,10 +49,14 @@ class Activitypub_reject extends Managed_DataObject */ public static function reject_to_array($object) { - $res = array("@context" => "https://www.w3.org/ns/activitystreams", - "type" => "Reject", - "object" => $object - ); + $res = [ + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], + "type" => "Reject", + "object" => $object + ]; return $res; } } diff --git a/classes/Activitypub_tag.php b/classes/Activitypub_tag.php index 43f1ecd..1ca5823 100755 --- a/classes/Activitypub_tag.php +++ b/classes/Activitypub_tag.php @@ -50,16 +50,13 @@ class Activitypub_tag extends Managed_DataObject public static function tag_to_array($tag) { $res = [ - '@context' => [ - "https://www.w3.org/ns/activitystreams", - [ - "@language" => "en" - ] - ], - 'name' => $tag, - 'url' => common_local_url('tag', array('tag' => $tag)) - ]; - + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], + 'name' => $tag, + 'url' => common_local_url('tag', ['tag' => $tag]) + ]; return $res; } } diff --git a/classes/Activitypub_undo.php b/classes/Activitypub_undo.php index ba54c79..685dad1 100755 --- a/classes/Activitypub_undo.php +++ b/classes/Activitypub_undo.php @@ -61,4 +61,33 @@ class Activitypub_undo extends Managed_DataObject ]; return $res; } + + /** + * Verifies if a given object is acceptable for a Undo Activity. + * + * @author Diogo Cordeiro + * @param Array $object + * @throws Exception + */ + public static function validate_object($object) + { + if (!is_array($object)) { + throw new Exception('Invalid Object Format for Undo Activity.'); + } + if (!isset($object['type'])) { + throw new Exception('Object type was not specified for Undo Activity.'); + } + switch ($object['type']) { + case 'Follow': + case 'Like': + // Validate data + if (!filter_var($object['object'], FILTER_VALIDATE_URL)) { + throw new Exception('Object is not a valid Object URI for Activity.'); + } + break; + default: + throw new Exception('This is not a supported Object Type for Undo Activity.'); + } + return true; + } } diff --git a/tests/Unit/HTTPSignatureTest.php b/tests/Unit/HTTPSignatureTest.php index fa53e67..c15b03a 100755 --- a/tests/Unit/HTTPSignatureTest.php +++ b/tests/Unit/HTTPSignatureTest.php @@ -34,7 +34,7 @@ class HTTPSignatureTest extends TestCase $this->assertTrue(class_exists('\HttpSignatures\Context')); $this->assertTrue(class_exists('\HttpSignatures\GuzzleHttpSignatures')); } - + public function setUp() { $this->context = new Context([ diff --git a/utils/Activitypub_activityverb2.php b/utils/Activitypub_activityverb2.php new file mode 100644 index 0000000..9b16789 --- /dev/null +++ b/utils/Activitypub_activityverb2.php @@ -0,0 +1,102 @@ +. + * + * @category Plugin + * @package GNUsocial + * @author Diogo Cordeiro + * @author Daniel Supernault + * @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/ + */ +if (!defined('GNUSOCIAL')) { + exit(1); +} + +/** + * Utility class to hold a bunch of constant defining default verb types + * + * @category Plugin + * @package GNUsocial + * @author Diogo Cordeiro + * @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/ + */ +class Activitypub_activityverb2 extends Managed_DataObject +{ + const FULL_LIST = + [ + 'Accept' => 'https://www.w3.org/ns/activitystreams#Accept', + 'TentativeAccept' => 'https://www.w3.org/ns/activitystreams#TentativeAccept', + 'Add' => 'https://www.w3.org/ns/activitystreams#Add', + 'Arrive' => 'https://www.w3.org/ns/activitystreams#Arrive', + 'Create' => 'https://www.w3.org/ns/activitystreams#Create', + 'Delete' => 'https://www.w3.org/ns/activitystreams#Delete', + 'Follow' => 'https://www.w3.org/ns/activitystreams#Follow', + 'Ignore' => 'https://www.w3.org/ns/activitystreams#Ignore', + 'Join' => 'https://www.w3.org/ns/activitystreams#Join', + 'Leave' => 'https://www.w3.org/ns/activitystreams#Leave', + 'Like' => 'https://www.w3.org/ns/activitystreams#Like', + 'Offer' => 'https://www.w3.org/ns/activitystreams#Offer', + 'Invite' => 'https://www.w3.org/ns/activitystreams#Invite', + 'Reject' => 'https://www.w3.org/ns/activitystreams#Reject', + 'TentativeReject' => 'https://www.w3.org/ns/activitystreams#TentativeReject', + 'Remove' => 'https://www.w3.org/ns/activitystreams#Remove', + 'Undo' => 'https://www.w3.org/ns/activitystreams#Undo', + 'Update' => 'https://www.w3.org/ns/activitystreams#Update', + 'View' => 'https://www.w3.org/ns/activitystreams#View', + 'Listen' => 'https://www.w3.org/ns/activitystreams#Listen', + 'Read' => 'https://www.w3.org/ns/activitystreams#Read', + 'Move' => 'https://www.w3.org/ns/activitystreams#Move', + 'Travel' => 'https://www.w3.org/ns/activitystreams#Travel', + 'Announce' => 'https://www.w3.org/ns/activitystreams#Announce', + 'Block' => 'https://www.w3.org/ns/activitystreams#Block', + 'Flag' => 'https://www.w3.org/ns/activitystreams#Flag', + 'Dislike' => 'https://www.w3.org/ns/activitystreams#Dislike', + 'Question' => 'https://www.w3.org/ns/activitystreams#Question' + ]; + + const KNOWN = + [ + 'Accept', + 'Create', + 'Delete', + 'Follow', + 'Like', + 'Undo', + 'Announce' + ]; + + /** + * Converts canonical into verb. + * + * @author GNU Social + * @param string $verb + * @return string + */ + public static function canonical($verb) + { + $ns = 'https://www.w3.org/ns/activitystreams#'; + if (substr($verb, 0, mb_strlen($ns)) == $ns) { + return substr($verb, mb_strlen($ns)); + } else { + return $verb; + } + } +} diff --git a/utils/discoveryhints.php b/utils/discoveryhints.php index 465aada..f3351b0 100755 --- a/utils/discoveryhints.php +++ b/utils/discoveryhints.php @@ -32,9 +32,9 @@ class DiscoveryHints { public static function fromXRD(XML_XRD $xrd) { - $hints = array(); + $hints = []; - if (Event::handle('StartDiscoveryHintsFromXRD', array($xrd, &$hints))) { + if (Event::handle('StartDiscoveryHintsFromXRD', [$xrd, &$hints])) { foreach ($xrd->links as $link) { switch ($link->rel) { case WebFingerResource_Profile::PROFILEPAGE: @@ -55,7 +55,7 @@ class DiscoveryHints break; } } - Event::handle('EndDiscoveryHintsFromXRD', array($xrd, &$hints)); + Event::handle('EndDiscoveryHintsFromXRD', [$xrd, &$hints]); } return $hints; @@ -88,10 +88,10 @@ class DiscoveryHints $hcard = self::_hcard($body, $url); if (empty($hcard)) { - return array(); + return []; } - $hints = array(); + $hints = []; // XXX: don't copy stuff into an array and then copy it again @@ -131,7 +131,7 @@ class DiscoveryHints return null; } - $hcards = array(); + $hcards = []; foreach ($mf2['items'] as $item) { if (!in_array('h-card', $item['type'])) { diff --git a/utils/inbox_handler.php b/utils/inbox_handler.php new file mode 100644 index 0000000..3f78a77 --- /dev/null +++ b/utils/inbox_handler.php @@ -0,0 +1,296 @@ +. + * + * @category Plugin + * @package GNUsocial + * @author Diogo Cordeiro + * @author Daniel Supernault + * @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/ + */ +if (!defined('GNUSOCIAL')) { + exit(1); +} + +/** + * ActivityPub Inbox Handler + * + * @category Plugin + * @package GNUsocial + * @author Diogo Cordeiro + * @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/ + */ +class Activitypub_inbox_handler +{ + private $activity; + private $actor; + private $object; + + /** + * Create a Inbox Handler to receive something from someone. + * + * @author Diogo Cordeiro + * @param Array $activity Activity we are receiving + */ + public function __construct($activity) + { + $this->activity = $activity; + $this->object = $activity['object']; + + // Validate Activity + $this->validate_activity(); + + // Get Actor's Profile + $this->actor = ActivityPub_explorer::get_profile_from_url($this->activity['actor']); + + // Handle the Activity + $this->process(); + } + + /** + * Validates if a given Activity is valid. Throws exception if not. + * + * @author Diogo Cordeiro + * @throws Exception + */ + private function validate_activity() + { + // Activity validation + // Validate data + if (!(isset($this->activity['type']))) { + throw new Exception('Activity Validation Failed: Type was not specified.'); + } + if (!isset($this->activity['actor'])) { + throw new Exception('Activity Validation Failed: Actor was not specified.'); + } + if (!isset($this->activity['object'])) { + throw new Exception('Activity Validation Failed: Object was not specified.'); + } + + // Object validation + switch ($this->activity['type']) { + case 'Accept': + Activitypub_accept::validate_object($this->object); + break; + case 'Create': + Activitypub_create::validate_object($this->object); + break; + case 'Delete': + case 'Follow': + case 'Like': + case 'Announce': + if (!filter_var($this->object, FILTER_VALIDATE_URL)) { + throw new Exception("Object is not a valid Object URI for Activity."); + } + break; + case 'Undo': + Activitypub_undo::validate_object($this->object); + break; + default: + throw new Exception('Unknown Activity Type.'); + } + } + + /** + * Sends the Activity to proper handler in order to be processed. + * + * @author Diogo Cordeiro + */ + private function process() + { + switch ($this->activity['type']) { + case 'Accept': + $this->handle_accept($this->actor, $this->object); + break; + case 'Create': + $this->handle_create($this->actor, $this->object); + break; + case 'Delete': + $this->handle_delete($this->actor, $this->object); + break; + case 'Follow': + $this->handle_follow($this->actor, $this->object); + break; + case 'Like': + $this->handle_like($this->actor, $this->object); + break; + case 'Undo': + $this->handle_undo($this->actor, $this->object); + break; + case 'Announce': + $this->handle_announce($this->actor, $this->object); + break; + } + } + + /** + * Handles an Accept Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_accept($actor, $object) + { + switch ($object['type']) { + case 'Follow': + $this->handle_accept_follow($actor, $object); + break; + } + } + + /** + * Handles an Accept Follow Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_accept_follow($actor, $object) + { + // Get valid Object profile + $object_profile = new Activitypub_explorer; + $object_profile = $object_profile->lookup($object['object'])[0]; + + $pending_list = new Activitypub_pending_follow_requests($actor->getID(), $object_profile->getID()); + $pending_list->remove(); + } + + /** + * Handles a Create Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_create($actor, $object) + { + switch ($object['type']) { + case 'Note': + Activitypub_notice::create_notice($object, $actor); + break; + } + } + + /** + * Handles a Delete Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_delete($actor, $object) + { + $notice = ActivityPubPlugin::grab_notice_from_url($object['object']); + $notice->deleteAs($actor); + } + + /** + * Handles a Follow Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_follow($actor, $object) + { + Activitypub_follow::follow($actor, $object); + } + + /** + * Handles a Like Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_like($actor, $object) + { + $notice = ActivityPubPlugin::grab_notice_from_url($object); + Fave::addNew($actor, $notice); + } + + /** + * Handles a Undo Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_undo($actor, $object) + { + switch ($object['type']) { + case 'Follow': + $this->handle_undo_follow($actor, $object['object']); + break; + case 'Like': + $this->handle_undo_like($actor, $object['object']); + break; + } + } + + /** + * Handles a Undo Like Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_undo_like($actor, $object) + { + $notice = ActivityPubPlugin::grab_notice_from_url($object); + Fave::removeEntry($actor, $notice); + } + + /** + * Handles a Undo Follow Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_undo_follow($actor, $object) + { + // Get Object profile + $object_profile = new Activitypub_explorer; + $object_profile = $object_profile->lookup($object)[0]; + + if (Subscription::exists($actor, $object_profile)) { + Subscription::cancel($actor, $object_profile); + // You are no longer following this person. + } else { + // 409: You are not following this person already. + } + } + + /** + * Handles a Announce Activity received by our inbox. + * + * @author Diogo Cordeiro + * @param Profile $actor Actor + * @param Array $object Activity + */ + private function handle_announce($actor, $object) + { + $object_notice = ActivityPubPlugin::grab_notice_from_url($object); + $object_notice->repeat($actor, 'ActivityPub'); + } +} diff --git a/utils/postman.php b/utils/postman.php index 808d161..ee320da 100755 --- a/utils/postman.php +++ b/utils/postman.php @@ -103,7 +103,7 @@ class Activitypub_postman common_debug('ActivityPub Postman: Delivery result with status code '.$response->getStatusCode().': '.$response->getBody()->getContents()); return $response; } - + /** * Send a follow notification to remote instance * @@ -135,11 +135,39 @@ class Activitypub_postman public function undo_follow() { $data = Activitypub_undo::undo_to_array( - Activitypub_follow::follow_to_array( - ActivityPubPlugin::actor_uri($this->actor), - $this->to[0]->getUrl() - ) - ); + Activitypub_follow::follow_to_array( + ActivityPubPlugin::actor_uri($this->actor), + $this->to[0]->getUrl() + ) + ); + $res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox()); + $res_body = json_decode($res->getBody()->getContents()); + + if ($res->getStatusCode() == 200 || $res->getStatusCode() == 202 || $res->getStatusCode() == 409) { + $pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID()); + $pending_list->remove(); + return true; + } + if (isset($res_body[0]->error)) { + throw new Exception($res_body[0]->error); + } + throw new Exception("An unknown error occurred."); + } + + /** + * Send a Accept Follow notification to remote instance + * + * @author Diogo Cordeiro + */ + public function accept_follow() + { + $data = Activitypub_accept::accept_to_array( + Activitypub_follow::follow_to_array( + $this->to[0]->getUrl(), + ActivityPubPlugin::actor_uri($this->actor) + + ) + ); $res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox()); $res_body = json_decode($res->getBody()->getContents()); @@ -278,7 +306,7 @@ class Activitypub_postman foreach ($this->to as $to_profile) { $i = $to_profile->get_inbox(); // Prevent delivering to self - if ($i == [common_local_url('apSharedInbox')]) { + if ($i == [common_local_url('apInbox')]) { continue; } $to_inboxes[] = $i;