From 95f95d2dd8771ccbb9b59489672213423b055e80 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 16 Oct 2020 01:07:01 +0100 Subject: [PATCH] [TESTS] Added unit tests --- .../ActivityPub/actions/apactorfollowers.php | 53 +++--- .../ActivityPub/actions/apactorfollowing.php | 53 +++--- plugins/ActivityPub/actions/apactorliked.php | 41 +++-- plugins/ActivityPub/actions/apactoroutbox.php | 49 +++--- .../ActivityPub/actions/apactorprofile.php | 12 +- plugins/ActivityPub/actions/apinbox.php | 16 +- plugins/ActivityPub/actions/apnotice.php | 16 +- .../lib/Activitypub_activityverb2.php | 18 +- plugins/ActivityPub/lib/explorer.php | 146 ++++++++++------ plugins/ActivityPub/lib/httpsignature.php | 66 ++++--- plugins/ActivityPub/lib/inbox_handler.php | 73 ++++++-- .../lib/models/Activitypub_accept.php | 23 ++- .../lib/models/Activitypub_announce.php | 27 +-- .../lib/models/Activitypub_attachment.php | 16 +- .../lib/models/Activitypub_create.php | 34 ++-- .../lib/models/Activitypub_delete.php | 42 ++--- .../lib/models/Activitypub_error.php | 12 +- .../lib/models/Activitypub_follow.php | 38 ++-- .../lib/models/Activitypub_like.php | 15 +- .../lib/models/Activitypub_mention_tag.php | 16 +- .../lib/models/Activitypub_message.php | 35 ++-- .../lib/models/Activitypub_notice.php | 163 ++++++++---------- .../lib/models/Activitypub_reject.php | 14 +- .../lib/models/Activitypub_tag.php | 14 +- .../lib/models/Activitypub_undo.php | 21 ++- plugins/ActivityPub/lib/postman.php | 111 +++++++----- .../scripts/update_activitypub_profiles.php | 23 +-- templates/left/left.html.twig | 2 +- .../ActorArrayTransformerTest.php | 44 +++++ 29 files changed, 716 insertions(+), 477 deletions(-) create mode 100644 tests/Util/Form/ActorArrayTransformer/ActorArrayTransformerTest.php diff --git a/plugins/ActivityPub/actions/apactorfollowers.php b/plugins/ActivityPub/actions/apactorfollowers.php index 74df047d18..1b37b99307 100644 --- a/plugins/ActivityPub/actions/apactorfollowers.php +++ b/plugins/ActivityPub/actions/apactorfollowers.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -42,27 +44,29 @@ class apActorFollowersAction extends ManagedAction /** * Handle the Followers Collection request * - * @return void * @throws Exception + * + * @return void + * * @author Diogo Cordeiro */ protected function handle() { try { - $profile = Profile::getByID($this->trimmed('id')); + $profile = Profile::getByID($this->trimmed('id')); $profile_id = $profile->getID(); } catch (Exception $e) { ActivityPubReturn::error('Invalid Actor URI.', 404); } if (!$profile->isLocal()) { - ActivityPubReturn::error("This is not a local user.", 403); + ActivityPubReturn::error('This is not a local user.', 403); } - if (!isset($_GET["page"])) { + if (!isset($_GET['page'])) { $page = 0; } else { - $page = intval($this->trimmed('page')); + $page = (int) ($this->trimmed('page')); } if ($page < 0) { @@ -72,32 +76,32 @@ class apActorFollowersAction extends ManagedAction $since = ($page - 1) * PROFILES_PER_MINILIST; $limit = PROFILES_PER_MINILIST; - /* Calculate total items */ + // Calculate total items $total_subs = Activitypub_profile::subscriberCount($profile); $total_pages = ceil($total_subs / PROFILES_PER_MINILIST); $res = [ - '@context' => [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', ], - 'id' => common_local_url('apActorFollowers', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''), - 'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'), - 'totalItems' => $total_subs + 'id' => common_local_url('apActorFollowers', ['id' => $profile_id]) . (($page != 0) ? '?page=' . $page : ''), + 'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'), + 'totalItems' => $total_subs, ]; if ($page == 0) { - $res['first'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'?page=1'; + $res['first'] = common_local_url('apActorFollowers', ['id' => $profile_id]) . '?page=1'; } else { $res['orderedItems'] = $this->generate_followers($profile, $since, $limit); - $res['partOf'] = common_local_url('apActorFollowers', ['id' => $profile_id]); + $res['partOf'] = common_local_url('apActorFollowers', ['id' => $profile_id]); - if ($page+1 < $total_pages) { - $res['next'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'page='.($page+1 == 1 ? 2 : $page+1); + if ($page + 1 < $total_pages) { + $res['next'] = common_local_url('apActorFollowers', ['id' => $profile_id]) . 'page=' . ($page + 1 == 1 ? 2 : $page + 1); } if ($page > 1) { - $res['prev'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1); + $res['prev'] = common_local_url('apActorFollowers', ['id' => $profile_id]) . '?page=' . ($page - 1 <= 0 ? 1 : $page - 1); } } @@ -108,10 +112,13 @@ class apActorFollowersAction extends ManagedAction * Generates a list of stalkers for a given profile. * * @param Profile $profile - * @param int $since - * @param int $limit - * @return array of URIs + * @param int $since + * @param int $limit + * * @throws Exception + * + * @return array of URIs + * * @author Diogo Cordeiro */ public static function generate_followers($profile, $since, $limit) @@ -120,7 +127,7 @@ class apActorFollowersAction extends ManagedAction try { $sub = Activitypub_profile::getSubscribers($profile, $since, $limit); - /* Get followers' URLs */ + // Get followers' URLs foreach ($sub as $s) { $subs[] = $s->getUri(); } diff --git a/plugins/ActivityPub/actions/apactorfollowing.php b/plugins/ActivityPub/actions/apactorfollowing.php index 3382d40692..fea7d2ea05 100644 --- a/plugins/ActivityPub/actions/apactorfollowing.php +++ b/plugins/ActivityPub/actions/apactorfollowing.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -42,27 +44,29 @@ class apActorFollowingAction extends ManagedAction /** * Handle the Following Collection request * - * @return void * @throws Exception + * + * @return void + * * @author Diogo Cordeiro */ protected function handle() { try { - $profile = Profile::getByID($this->trimmed('id')); + $profile = Profile::getByID($this->trimmed('id')); $profile_id = $profile->getID(); } catch (Exception $e) { ActivityPubReturn::error('Invalid Actor URI.', 404); } if (!$profile->isLocal()) { - ActivityPubReturn::error("This is not a local user.", 403); + ActivityPubReturn::error('This is not a local user.', 403); } - if (!isset($_GET["page"])) { + if (!isset($_GET['page'])) { $page = 0; } else { - $page = intval($this->trimmed('page')); + $page = (int) ($this->trimmed('page')); } if ($page < 0) { @@ -72,32 +76,32 @@ class apActorFollowingAction extends ManagedAction $since = ($page - 1) * PROFILES_PER_MINILIST; $limit = PROFILES_PER_MINILIST; - /* Calculate total items */ + // Calculate total items $total_subs = Activitypub_profile::subscriptionCount($profile); $total_pages = ceil($total_subs / PROFILES_PER_MINILIST); $res = [ - '@context' => [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', ], - 'id' => common_local_url('apActorFollowing', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''), - 'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'), - 'totalItems' => $total_subs + 'id' => common_local_url('apActorFollowing', ['id' => $profile_id]) . (($page != 0) ? '?page=' . $page : ''), + 'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'), + 'totalItems' => $total_subs, ]; if ($page == 0) { - $res['first'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'?page=1'; + $res['first'] = common_local_url('apActorFollowing', ['id' => $profile_id]) . '?page=1'; } else { $res['orderedItems'] = $this->generate_following($profile, $since, $limit); - $res['partOf'] = common_local_url('apActorFollowing', ['id' => $profile_id]); + $res['partOf'] = common_local_url('apActorFollowing', ['id' => $profile_id]); - if ($page+1 < $total_pages) { - $res['next'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'page='.($page+1 == 1 ? 2 : $page+1); + if ($page + 1 < $total_pages) { + $res['next'] = common_local_url('apActorFollowing', ['id' => $profile_id]) . 'page=' . ($page + 1 == 1 ? 2 : $page + 1); } if ($page > 1) { - $res['prev'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1); + $res['prev'] = common_local_url('apActorFollowing', ['id' => $profile_id]) . '?page=' . ($page - 1 <= 0 ? 1 : $page - 1); } } @@ -108,10 +112,13 @@ class apActorFollowingAction extends ManagedAction * Generates the list of those a given profile is stalking. * * @param Profile $profile - * @param int $since - * @param int $limit - * @return array of URIs + * @param int $since + * @param int $limit + * * @throws Exception + * + * @return array of URIs + * * @author Diogo Cordeiro */ public function generate_following($profile, $since, $limit) @@ -120,7 +127,7 @@ class apActorFollowingAction extends ManagedAction try { $sub = Activitypub_profile::getSubscribed($profile, $since, $limit); - /* Get followed' URLs */ + // Get followed' URLs foreach ($sub as $s) { $subs[] = $s->getUri(); } diff --git a/plugins/ActivityPub/actions/apactorliked.php b/plugins/ActivityPub/actions/apactorliked.php index fc603966cc..ef50ec675c 100644 --- a/plugins/ActivityPub/actions/apactorliked.php +++ b/plugins/ActivityPub/actions/apactorliked.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -42,27 +44,29 @@ class apActorLikedAction extends ManagedAction /** * Handle the Liked Collection request * - * @return void * @throws EmptyPkeyValueException * @throws ServerException + * + * @return void + * * @author Diogo Cordeiro */ protected function handle() { try { - $profile = Profile::getByID($this->trimmed('id')); + $profile = Profile::getByID($this->trimmed('id')); $profile_id = $profile->getID(); } catch (Exception $e) { ActivityPubReturn::error('Invalid Actor URI.', 404); } if (!$profile->isLocal()) { - ActivityPubReturn::error("This is not a local user.", 403); + ActivityPubReturn::error('This is not a local user.', 403); } - $limit = intval($this->trimmed('limit')); - $since_id = intval($this->trimmed('since_id')); - $max_id = intval($this->trimmed('max_id')); + $limit = (int) ($this->trimmed('limit')); + $since_id = (int) ($this->trimmed('since_id')); + $max_id = (int) ($this->trimmed('max_id')); $limit = empty($limit) ? 40 : $limit; // Default is 40 $since_id = empty($since_id) ? null : $since_id; @@ -75,20 +79,20 @@ class apActorLikedAction extends ManagedAction $fave = $this->fetch_faves($profile_id, $limit, $since_id, $max_id); - $faves = array(); + $faves = []; while ($fave->fetch()) { - $faves[] = $this->pretty_fave(clone ($fave)); + $faves[] = $this->pretty_fave(clone $fave); } $res = [ - '@context' => [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', ], 'id' => common_local_url('apActorLiked', ['id' => $profile_id]), 'type' => 'OrderedCollection', 'totalItems' => Fave::countByProfile($profile), - 'orderedItems' => $faves + 'orderedItems' => $faves, ]; ActivityPubReturn::answer($res); @@ -99,16 +103,19 @@ class apActorLikedAction extends ManagedAction * as a plugin answer * * @param Fave $fave_object - * @return array pretty array representating a Fave + * * @throws EmptyPkeyValueException * @throws ServerException + * + * @return array pretty array representating a Fave + * * @author Diogo Cordeiro */ protected function pretty_fave($fave_object) { $res = [ 'created' => $fave_object->created, - 'object' => Activitypub_notice::notice_to_array(Notice::getByID($fave_object->notice_id)) + 'object' => Activitypub_notice::notice_to_array(Notice::getByID($fave_object->notice_id)), ]; return $res; @@ -118,10 +125,12 @@ class apActorLikedAction extends ManagedAction * Fetch faves * * @author Diogo Cordeiro + * * @param int $user_id * @param int $limit * @param int $since_id * @param int $max_id + * * @return Fave fetchable fave collection */ private static function fetch_faves( diff --git a/plugins/ActivityPub/actions/apactoroutbox.php b/plugins/ActivityPub/actions/apactoroutbox.php index b80ec568fe..b7c74e5627 100644 --- a/plugins/ActivityPub/actions/apactoroutbox.php +++ b/plugins/ActivityPub/actions/apactoroutbox.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -47,55 +49,55 @@ class apActorOutboxAction extends ManagedAction protected function handle() { try { - $profile = Profile::getByID($this->trimmed('id')); + $profile = Profile::getByID($this->trimmed('id')); $profile_id = $profile->getID(); } catch (Exception $e) { ActivityPubReturn::error('Invalid Actor URI.', 404); } if (!$profile->isLocal()) { - ActivityPubReturn::error("This is not a local user.", 403); + ActivityPubReturn::error('This is not a local user.', 403); } - if (!isset($_GET["page"])) { + if (!isset($_GET['page'])) { $page = 0; } else { - $page = intval($this->trimmed('page')); + $page = (int) ($this->trimmed('page')); } if ($page < 0) { ActivityPubReturn::error('Invalid page number.'); } - $since = ($page - 1) * PROFILES_PER_MINILIST; + $since = ($page - 1) * PROFILES_PER_MINILIST; $limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST; - /* Calculate total items */ + // Calculate total items $total_notes = $profile->noticeCount(); $total_pages = ceil($total_notes / PROFILES_PER_MINILIST); $res = [ - '@context' => [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', ], - 'id' => common_local_url('apActorOutbox', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''), - 'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'), - 'totalItems' => $total_notes + 'id' => common_local_url('apActorOutbox', ['id' => $profile_id]) . (($page != 0) ? '?page=' . $page : ''), + 'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'), + 'totalItems' => $total_notes, ]; if ($page == 0) { - $res['first'] = common_local_url('apActorOutbox', ['id' => $profile_id]).'?page=1'; + $res['first'] = common_local_url('apActorOutbox', ['id' => $profile_id]) . '?page=1'; } else { $res['orderedItems'] = $this->generate_outbox($profile); - $res['partOf'] = common_local_url('apActorOutbox', ['id' => $profile_id]); + $res['partOf'] = common_local_url('apActorOutbox', ['id' => $profile_id]); - if ($page+1 < $total_pages) { - $res['next'] = common_local_url('apActorOutbox', ['id' => $profile_id]).'page='.($page+1 == 1 ? 2 : $page+1); + if ($page + 1 < $total_pages) { + $res['next'] = common_local_url('apActorOutbox', ['id' => $profile_id]) . 'page=' . ($page + 1 == 1 ? 2 : $page + 1); } if ($page > 1) { - $res['prev'] = common_local_url('apActorOutbox', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1); + $res['prev'] = common_local_url('apActorOutbox', ['id' => $profile_id]) . '?page=' . ($page - 1 <= 0 ? 1 : $page - 1); } } @@ -106,17 +108,20 @@ class apActorOutboxAction extends ManagedAction * Generates a list of people following given profile. * * @param Profile $profile - * @return array of Notices + * * @throws EmptyPkeyValueException * @throws InvalidUrlException * @throws ServerException + * + * @return array of Notices + * * @author Daniel Supernault */ public function generate_outbox($profile) { - /* Fetch Notices */ + // Fetch Notices $notices = []; - $notice = $profile->getNotices(); + $notice = $profile->getNotices(); while ($notice->fetch()) { $note = $notice; diff --git a/plugins/ActivityPub/actions/apactorprofile.php b/plugins/ActivityPub/actions/apactorprofile.php index 20d4608248..7756534dd7 100644 --- a/plugins/ActivityPub/actions/apactorprofile.php +++ b/plugins/ActivityPub/actions/apactorprofile.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -42,9 +44,11 @@ class apActorProfileAction extends ManagedAction /** * Handle the Actor Profile request * - * @return void * @throws InvalidUrlException * @throws ServerException + * + * @return void + * * @author Diogo Cordeiro */ protected function handle() @@ -65,7 +69,7 @@ class apActorProfileAction extends ManagedAction } if (!$profile->isLocal()) { - ActivityPubReturn::error("This is not a local user.", 403); + ActivityPubReturn::error('This is not a local user.', 403); } $res = Activitypub_profile::profile_to_array($profile); diff --git a/plugins/ActivityPub/actions/apinbox.php b/plugins/ActivityPub/actions/apinbox.php index 20e18b9366..d2cc117d31 100644 --- a/plugins/ActivityPub/actions/apinbox.php +++ b/plugins/ActivityPub/actions/apinbox.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,19 +32,22 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ class apInboxAction extends ManagedAction { protected $needLogin = false; - protected $canPost = true; + protected $canPost = true; /** * Handle the Inbox request * - * @return void * @throws ServerException + * + * @return void + * * @author Diogo Cordeiro */ protected function handle() @@ -120,8 +124,8 @@ class apInboxAction extends ManagedAction } catch (Exception $e) { ActivityPubReturn::error('Failed to updated remote actor information.'); } - $actor_public_key = new Activitypub_rsa(); - $actor_public_key = $actor_public_key->ensure_public_key($actor); + $actor_public_key = new Activitypub_rsa(); + $actor_public_key = $actor_public_key->ensure_public_key($actor); list($verified, /*$headers*/) = HTTPSignature::verify($actor_public_key, $signatureData, $headers, $path, $body); } diff --git a/plugins/ActivityPub/actions/apnotice.php b/plugins/ActivityPub/actions/apnotice.php index 3d91b7abd6..c468a0e2c4 100644 --- a/plugins/ActivityPub/actions/apnotice.php +++ b/plugins/ActivityPub/actions/apnotice.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -132,10 +134,12 @@ class apNoticeAction extends ManagedAction /** * Handle the Notice request * - * @return void * @throws EmptyPkeyValueException * @throws InvalidUrlException * @throws ServerException + * + * @return void + * * @author Diogo Cordeiro */ protected function handle(): void @@ -143,9 +147,9 @@ class apNoticeAction extends ManagedAction if (is_null($this->notice)) { ActivityPubReturn::error('Invalid Activity URI.', 404); } - if (!$this->notice->isLocal()) { - // We have no authority on the requested activity. - ActivityPubReturn::error("This is not a local activity.", 403); + + if (!$notice->isLocal()) { + ActivityPubReturn::error('This is not a local notice.', 403); } $res = Activitypub_notice::notice_to_array($this->notice); diff --git a/plugins/ActivityPub/lib/Activitypub_activityverb2.php b/plugins/ActivityPub/lib/Activitypub_activityverb2.php index aba36406d5..f0af878ff7 100644 --- a/plugins/ActivityPub/lib/Activitypub_activityverb2.php +++ b/plugins/ActivityPub/lib/Activitypub_activityverb2.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,13 +32,13 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ class Activitypub_activityverb2 extends Managed_DataObject { - const FULL_LIST = - [ + 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', @@ -65,25 +66,26 @@ class Activitypub_activityverb2 extends Managed_DataObject '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' + 'Question' => 'https://www.w3.org/ns/activitystreams#Question', ]; - const KNOWN = - [ + const KNOWN = [ 'Accept', 'Create', 'Delete', 'Follow', 'Like', 'Undo', - 'Announce' + 'Announce', ]; /** * Converts canonical into verb. * * @author GNU social + * * @param string $verb + * * @return string */ public static function canonical($verb) diff --git a/plugins/ActivityPub/lib/explorer.php b/plugins/ActivityPub/lib/explorer.php index edbcdabe45..8c0b941717 100644 --- a/plugins/ActivityPub/lib/explorer.php +++ b/plugins/ActivityPub/lib/explorer.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -33,6 +34,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -44,17 +46,20 @@ class Activitypub_explorer * Shortcut function to get a single profile from its URL. * * @param string $url - * @param bool $grab_online whether to try online grabbing, defaults to true - * @return Profile + * @param bool $grab_online whether to try online grabbing, defaults to true + * * @throws HTTP_Request2_Exception Network issues - * @throws NoProfileException This won't happen - * @throws Exception Invalid request - * @throws ServerException Error storing remote actor + * @throws NoProfileException This won't happen + * @throws Exception Invalid request + * @throws ServerException Error storing remote actor + * + * @return Profile + * * @author Diogo Cordeiro */ public static function get_profile_from_url(string $url, bool $grab_online = true): Profile { - $discovery = new Activitypub_explorer(); + $discovery = new self(); // Get valid Actor object $actor_profile = $discovery->lookup($url, $grab_online); if (!empty($actor_profile)) { @@ -68,13 +73,16 @@ class Activitypub_explorer * This function cleans the $this->discovered_actor_profiles array * so that there is no erroneous data * - * @param string $url User's url - * @param bool $grab_online whether to try online grabbing, defaults to true - * @return array of Profile objects + * @param string $url User's url + * @param bool $grab_online whether to try online grabbing, defaults to true + * * @throws HTTP_Request2_Exception * @throws NoProfileException * @throws Exception * @throws ServerException + * + * @return array of Profile objects + * * @author Diogo Cordeiro */ public function lookup(string $url, bool $grab_online = true) @@ -94,13 +102,16 @@ class Activitypub_explorer * This is a recursive function that will accumulate the results on * $discovered_actor_profiles array * - * @param string $url User's url - * @param bool $grab_online whether to try online grabbing, defaults to true - * @return array of Profile objects + * @param string $url User's url + * @param bool $grab_online whether to try online grabbing, defaults to true + * * @throws HTTP_Request2_Exception * @throws NoProfileException * @throws ServerException * @throws Exception + * + * @return array of Profile objects + * * @author Diogo Cordeiro */ private function _lookup(string $url, bool $grab_online = true): array @@ -120,11 +131,14 @@ class Activitypub_explorer * Get a local user profile from its URL and joins it on * $this->discovered_actor_profiles * - * @param string $uri Actor's uri - * @param bool $online - * @return bool success state + * @param string $uri Actor's uri + * @param bool $online + * * @throws NoProfileException * @throws Exception + * + * @return bool success state + * * @author Diogo Cordeiro */ private function grab_local_user(string $uri, bool $online = false): bool @@ -153,9 +167,9 @@ class Activitypub_explorer if ($online) { common_debug('ActivityPub Explorer: Double-checking ' . $alias . ' to confirm it as a legitimate alias'); - $disco = new Discovery(); - $xrd = $disco->lookup($aprofile->getUri()); - $doublecheck_aliases = array_merge(array($xrd->subject), $xrd->aliases); + $disco = new Discovery(); + $xrd = $disco->lookup($aprofile->getUri()); + $doublecheck_aliases = array_merge([$xrd->subject], $xrd->aliases); if (in_array($uri, $doublecheck_aliases)) { // the original URI is present, we're sure now! @@ -177,11 +191,11 @@ class Activitypub_explorer common_debug('ActivityPub Explorer: Unable to find a local Aprofile for ' . $alias . ' - looking for a Profile instead.'); // Well, maybe it is a pure blood? // Iff, we are in the same instance: - $ACTIVITYPUB_BASE_ACTOR_URI = common_local_url('userbyid', ['id' => null], null, null, false, true); // @FIXME: Could this be too hardcoded? + $ACTIVITYPUB_BASE_ACTOR_URI = common_local_url('userbyid', ['id' => null], null, null, false, true); // @FIXME: Could this be too hardcoded? $ACTIVITYPUB_BASE_ACTOR_URI_length = strlen($ACTIVITYPUB_BASE_ACTOR_URI); if (substr($alias, 0, $ACTIVITYPUB_BASE_ACTOR_URI_length) === $ACTIVITYPUB_BASE_ACTOR_URI) { try { - $profile = Profile::getByID((int)substr($alias, $ACTIVITYPUB_BASE_ACTOR_URI_length)); + $profile = Profile::getByID((int) substr($alias, $ACTIVITYPUB_BASE_ACTOR_URI_length)); common_debug('ActivityPub Explorer: Found a Profile for ' . $alias); // We found something! $this->discovered_actor_profiles[] = $profile; @@ -208,19 +222,22 @@ class Activitypub_explorer * $this->discovered_actor_profiles * * @param string $url User's url - * @return bool success state + * * @throws HTTP_Request2_Exception * @throws NoProfileException * @throws ServerException * @throws Exception + * + * @return bool success state + * * @author Diogo Cordeiro */ private function grab_remote_user(string $url): bool { common_debug('ActivityPub Explorer: Trying to grab a remote actor for ' . $url); - $client = new HTTPClient(); + $client = new HTTPClient(); $response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS); - $res = json_decode($response->getBody(), true); + $res = json_decode($response->getBody(), true); if ($response->getStatus() == 410) { // If it was deleted return true; // Nothing to add. } elseif (!$response->isOk()) { // If it is unavailable @@ -251,29 +268,32 @@ class Activitypub_explorer * Save remote user profile in local instance * * @param array $res remote response - * @return Profile remote Profile object + * * @throws NoProfileException * @throws ServerException * @throws Exception + * + * @return Profile remote Profile object + * * @author Diogo Cordeiro */ private function store_profile(array $res): Profile { // ActivityPub Profile - $aprofile = new Activitypub_profile; - $aprofile->uri = $res['id']; - $aprofile->nickname = $res['preferredUsername']; - $aprofile->fullname = $res['name'] ?? null; - $aprofile->bio = isset($res['summary']) ? substr(strip_tags($res['summary']), 0, 1000) : null; - $aprofile->inboxuri = $res['inbox']; + $aprofile = new Activitypub_profile; + $aprofile->uri = $res['id']; + $aprofile->nickname = $res['preferredUsername']; + $aprofile->fullname = $res['name'] ?? null; + $aprofile->bio = isset($res['summary']) ? substr(strip_tags($res['summary']), 0, 1000) : null; + $aprofile->inboxuri = $res['inbox']; $aprofile->sharedInboxuri = $res['endpoints']['sharedInbox'] ?? $res['inbox']; - $aprofile->profileurl = $res['url'] ?? $aprofile->uri; + $aprofile->profileurl = $res['url'] ?? $aprofile->uri; $aprofile->do_insert(); $profile = $aprofile->local_profile(); // Public Key - $apRSA = new Activitypub_rsa(); + $apRSA = new Activitypub_rsa(); $apRSA->profile_id = $profile->getID(); $apRSA->public_key = $res['publicKey']['publicKeyPem']; $apRSA->store_keys(); @@ -296,7 +316,9 @@ class Activitypub_explorer * response is a valid profile or not * * @param array $res remote response + * * @return bool success state + * * @author Diogo Cordeiro */ public static function validate_remote_response(array $res): bool @@ -315,15 +337,17 @@ class Activitypub_explorer * this hacky workaround (at least for now) * * @param string $v URL - * @return bool|Activitypub_profile false if fails | Aprofile object if successful + * + * @return Activitypub_profile|bool false if fails | Aprofile object if successful + * * @author Diogo Cordeiro */ public static function get_aprofile_by_url(string $v) { - $i = Managed_DataObject::getcached("Activitypub_profile", "uri", $v); + $i = Managed_DataObject::getcached('Activitypub_profile', 'uri', $v); if (empty($i)) { // false = cache miss - $i = new Activitypub_profile; - $result = $i->get("uri", $v); + $i = new Activitypub_profile; + $result = $i->get('uri', $v); if ($result) { // Hit! $i->encache(); @@ -338,14 +362,17 @@ class Activitypub_explorer * Given a valid actor profile url returns its inboxes * * @param string $url of Actor profile - * @return bool|array false if fails to validate the answer | array with inbox and shared inbox if successful + * * @throws HTTP_Request2_Exception - * @throws Exception If an irregular error happens (status code, body format or GONE) + * @throws Exception If an irregular error happens (status code, body format or GONE) + * + * @return array|bool false if fails to validate the answer | array with inbox and shared inbox if successful + * * @author Diogo Cordeiro */ public static function get_actor_inboxes_uri(string $url) { - $client = new HTTPClient(); + $client = new HTTPClient(); $response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS); if ($response->getStatus() == 410) { // If it was deleted throw new Exception('This actor is GONE.'); @@ -359,8 +386,8 @@ class Activitypub_explorer } if (self::validate_remote_response($res)) { return [ - 'inbox' => $res['inbox'], - 'sharedInbox' => isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox'] + 'inbox' => $res['inbox'], + 'sharedInbox' => isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox'], ]; } @@ -373,9 +400,12 @@ class Activitypub_explorer * TODO: Should be in AProfile instead? * * @param Profile $profile - * @param string $url - * @return Avatar The Avatar we have on disk. (seldom used) + * @param string $url + * * @throws Exception in various failure cases + * + * @return Avatar The Avatar we have on disk. (seldom used) + * * @author Diogo Cordeiro */ public static function update_avatar(Profile $profile, string $url): Avatar @@ -386,7 +416,7 @@ class Activitypub_explorer $id = $profile->getID(); - $type = $imagefile->preferredType(); + $type = $imagefile->preferredType(); $filename = Avatar::filename( $id, image_type_to_extension($type), @@ -412,31 +442,34 @@ class Activitypub_explorer * Allows the Explorer to transverse a collection of persons. * * @param string $url - * @return bool + * * @throws HTTP_Request2_Exception * @throws NoProfileException * @throws ServerException + * + * @return bool + * * @author Diogo Cordeiro */ private function travel_collection(string $url): bool { - $client = new HTTPClient(); + $client = new HTTPClient(); $response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS); - $res = json_decode($response->getBody(), true); + $res = json_decode($response->getBody(), true); if (!isset($res['orderedItems'])) { return false; } - foreach ($res["orderedItems"] as $profile) { + foreach ($res['orderedItems'] as $profile) { if ($this->_lookup($profile) == false) { common_debug('ActivityPub Explorer: Found an invalid actor for ' . $profile); // TODO: Invalid actor found, fallback to OStatus } } // Go through entire collection - if (!is_null($res["next"])) { - $this->travel_collection($res["next"]); + if (!is_null($res['next'])) { + $this->travel_collection($res['next']); } return true; @@ -447,13 +480,16 @@ class Activitypub_explorer * profile updating and shall not be used for anything else) * * @param string $url User's url - * @return array|false If it is able to fetch, false if it's gone + * * @throws Exception Either network issues or unsupported Activity format + * + * @return array|false If it is able to fetch, false if it's gone + * * @author Diogo Cordeiro */ public static function get_remote_user_activity(string $url) { - $client = new HTTPClient(); + $client = new HTTPClient(); $response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS); // If it was deleted if ($response->getStatus() == 410) { @@ -466,7 +502,7 @@ class Activitypub_explorer common_debug('ActivityPub Explorer: Invalid JSON returned from given Actor URL: ' . $response->getBody()); throw new Exception('Given Actor URL didn\'t return a valid JSON.'); } - if (Activitypub_explorer::validate_remote_response($res)) { + if (self::validate_remote_response($res)) { common_debug('ActivityPub Explorer: Found a valid remote actor for ' . $url); return $res; } diff --git a/plugins/ActivityPub/lib/httpsignature.php b/plugins/ActivityPub/lib/httpsignature.php index 6a5b96e4e0..03a8545cd9 100644 --- a/plugins/ActivityPub/lib/httpsignature.php +++ b/plugins/ActivityPub/lib/httpsignature.php @@ -14,22 +14,25 @@ * * @category Network * @package Nautilus + * * @author Aaron Parecki * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 - * @link https://github.com/aaronpk/Nautilus/blob/master/app/ActivityPub/HTTPSignature.php + * + * @see https://github.com/aaronpk/Nautilus/blob/master/app/ActivityPub/HTTPSignature.php */ - -class HttpSignature +class httpsignature { /** * Sign a message with an Actor * - * @param Profile $user Actor signing - * @param string $url Inbox url - * @param string|bool $body Data to sign (optional) - * @param array $addlHeaders Additional headers (optional) - * @return array Headers to be used in curl + * @param Profile $user Actor signing + * @param string $url Inbox url + * @param bool|string $body Data to sign (optional) + * @param array $addlHeaders Additional headers (optional) + * * @throws Exception Attempted to sign something that belongs to an Actor we don't own + * + * @return array Headers to be used in curl */ public static function sign(Profile $user, string $url, $body = false, array $addlHeaders = []): array { @@ -37,16 +40,16 @@ class HttpSignature if ($body) { $digest = self::_digest($body); } - $headers = self::_headersToSign($url, $digest); - $headers = array_merge($headers, $addlHeaders); - $stringToSign = self::_headersToSigningString($headers); - $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers))); + $headers = self::_headersToSign($url, $digest); + $headers = array_merge($headers, $addlHeaders); + $stringToSign = self::_headersToSigningString($headers); + $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers))); $actor_private_key = new Activitypub_rsa(); // Intentionally unhandled exception, we want this to explode if that happens as it would be a bug $actor_private_key = $actor_private_key->get_private_key($user); - $key = openssl_pkey_get_private($actor_private_key); + $key = openssl_pkey_get_private($actor_private_key); openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256); - $signature = base64_encode($signature); + $signature = base64_encode($signature); $signatureHeader = 'keyId="' . $user->getUri() . '#public-key' . '",headers="' . $signedHeaders . '",algorithm="rsa-sha256",signature="' . $signature . '"'; unset($headers['(request-target)']); $headers['Signature'] = $signatureHeader; @@ -56,6 +59,7 @@ class HttpSignature /** * @param mixed $body + * * @return string */ private static function _digest($body): string @@ -68,9 +72,11 @@ class HttpSignature /** * @param string $url - * @param mixed $digest - * @return array + * @param mixed $digest + * * @throws Exception + * + * @return array */ protected static function _headersToSign(string $url, $digest = false): array { @@ -78,11 +84,11 @@ class HttpSignature $headers = [ '(request-target)' => 'post ' . parse_url($url, PHP_URL_PATH), - 'Date' => $date->format('D, d M Y H:i:s \G\M\T'), - 'Host' => parse_url($url, PHP_URL_HOST), - 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, application/json', - 'User-Agent' => 'GNU social ActivityPub Plugin - '.GNUSOCIAL_ENGINE_URL, - 'Content-Type' => 'application/activity+json' + 'Date' => $date->format('D, d M Y H:i:s \G\M\T'), + 'Host' => parse_url($url, PHP_URL_HOST), + 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, application/json', + 'User-Agent' => 'GNU social ActivityPub Plugin - ' . GNUSOCIAL_ENGINE_URL, + 'Content-Type' => 'application/activity+json', ]; if ($digest) { @@ -94,6 +100,7 @@ class HttpSignature /** * @param array $headers + * * @return string */ private static function _headersToSigningString(array $headers): string @@ -105,22 +112,24 @@ class HttpSignature /** * @param array $headers + * * @return array */ private static function _headersToCurlArray(array $headers): array { return array_map(function ($k, $v) { - return "$k: $v"; + return "{$k}: {$v}"; }, array_keys($headers), $headers); } /** * @param string $signature + * * @return array */ public static function parseSignatureHeader(string $signature): array { - $parts = explode(',', $signature); + $parts = explode(',', $signature); $signatureData = []; foreach ($parts as $part) { @@ -131,19 +140,19 @@ class HttpSignature if (!isset($signatureData['keyId'])) { return [ - 'error' => 'No keyId was found in the signature header. Found: ' . implode(', ', array_keys($signatureData)) + 'error' => 'No keyId was found in the signature header. Found: ' . implode(', ', array_keys($signatureData)), ]; } if (!filter_var($signatureData['keyId'], FILTER_VALIDATE_URL)) { return [ - 'error' => 'keyId is not a URL: ' . $signatureData['keyId'] + 'error' => 'keyId is not a URL: ' . $signatureData['keyId'], ]; } if (!isset($signatureData['headers']) || !isset($signatureData['signature'])) { return [ - 'error' => 'Signature is missing headers or signature parts' + 'error' => 'Signature is missing headers or signature parts', ]; } @@ -156,14 +165,15 @@ class HttpSignature * @param $inputHeaders * @param $path * @param $body + * * @return array */ public static function verify($publicKey, $signatureData, $inputHeaders, $path, $body): array { // We need this because the used Request headers fields specified by Signature are in lower case. $headersContent = array_change_key_case($inputHeaders, CASE_LOWER); - $digest = 'SHA-256=' . base64_encode(hash('sha256', $body, true)); - $headersToSign = []; + $digest = 'SHA-256=' . base64_encode(hash('sha256', $body, true)); + $headersToSign = []; foreach (explode(' ', $signatureData['headers']) as $h) { if ($h == '(request-target)') { $headersToSign[$h] = 'post ' . $path; diff --git a/plugins/ActivityPub/lib/inbox_handler.php b/plugins/ActivityPub/lib/inbox_handler.php index bbc28c6aad..490da16d0e 100644 --- a/plugins/ActivityPub/lib/inbox_handler.php +++ b/plugins/ActivityPub/lib/inbox_handler.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -43,15 +45,17 @@ class Activitypub_inbox_handler /** * Create a Inbox Handler to receive something from someone. * - * @param array $activity Activity we are receiving + * @param array $activity Activity we are receiving * @param Profile $actor_profile Actor originating the activity + * * @throws Exception + * * @author Diogo Cordeiro */ public function __construct($activity, $actor_profile = null) { $this->activity = $activity; - $this->object = $activity['object']; + $this->object = $activity['object']; // Validate Activity if (!$this->validate_activity()) { @@ -73,7 +77,9 @@ class Activitypub_inbox_handler * Validates if a given Activity is valid. Throws exception if not. * * @throws Exception if invalid + * * @return bool true if valid and acceptable, false if unsupported + * * @author Diogo Cordeiro */ private function validate_activity(): bool @@ -127,6 +133,7 @@ class Activitypub_inbox_handler * @throws NoProfileException * @throws ServerException * @throws Exception + * * @author Diogo Cordeiro */ private function process() @@ -162,6 +169,7 @@ class Activitypub_inbox_handler * @throws HTTP_Request2_Exception * @throws NoProfileException * @throws ServerException + * * @author Diogo Cordeiro */ private function handle_accept() @@ -180,6 +188,7 @@ class Activitypub_inbox_handler * @throws NoProfileException * @throws ServerException * @throws Exception + * * @author Diogo Cordeiro */ private function handle_accept_follow() @@ -200,6 +209,7 @@ class Activitypub_inbox_handler * Handles a Create Activity received by our inbox. * * @throws Exception + * * @author Diogo Cordeiro */ private function handle_create() @@ -215,6 +225,7 @@ class Activitypub_inbox_handler * Handle a Create Note Activity received by our inbox. * * @throws Exception + * * @author Bruno Casteleiro */ private function handle_create_note() @@ -229,6 +240,9 @@ class Activitypub_inbox_handler /** * Handles a Delete Activity received by our inbox. * + * @throws NoProfileException + * @throws Exception + * * @author Bruno Casteleiro * @author Diogo Cordeiro */ @@ -236,10 +250,10 @@ class Activitypub_inbox_handler { $object = $this->object; if (is_string($object)) { - $client = new HTTPClient(); + $client = new HTTPClient(); $response = $client->get($object, ACTIVITYPUB_HTTP_CLIENT_HEADERS); - $not_gone = $response->isOk(); - if ($not_gone) { // It's not gone, we're updating it. + $gone = !$response->isOk(); + if (!$gone) { // It's not gone, we're updating it. $object = json_decode($response->getBody(), true); switch ($object['type']) { case 'Person': @@ -269,16 +283,37 @@ class Activitypub_inbox_handler common_log(LOG_INFO, "Ignoring Delete activity, we do not understand for {$object['type']}."); } } - } + } else { + // We don't know the type of the deleted object :( + // Nor if it's gone or not. + try { + if (is_array($object)) { + $object = $object['id']; + } + $aprofile = Activitypub_profile::fromUri($object, false); + $res = Activitypub_explorer::get_remote_user_activity($object); + Activitypub_profile::update_profile($aprofile, $res); + return; + } catch (Exception $e) { + // Means this wasn't a profile + } - // IFF we reached this point, it either is gone or it's an array - // If it's gone, we don't know the type of the deleted object, we only have a Tombstone - // If we were given an array, we don't know if it's Gone or not via status code... - // In both cases, we will want to fetch the ID and act on that as it is easier than updating the fields - $object = $object['id'] ?? null; - if (is_null($object)) { - return; - } + try { + $client = new HTTPClient(); + $response = $client->get($object, ACTIVITYPUB_HTTP_CLIENT_HEADERS); + // If it was deleted + if ($response->getStatus() == 410) { + $notice = ActivityPubPlugin::grab_notice_from_url($object, false); + if ($notice instanceof Notice) { + $notice->delete(); + } + } else { + // We can't update a note's contents so, we'll ignore it for now... + } + return; + } catch (Exception $e) { + // Means we didn't have this note already + } // Was it a profile? try { @@ -316,6 +351,7 @@ class Activitypub_inbox_handler * @throws HTTP_Request2_Exception * @throws NoProfileException * @throws ServerException + * * @author Diogo Cordeiro */ private function handle_follow() @@ -327,6 +363,7 @@ class Activitypub_inbox_handler * Handles a Like Activity received by our inbox. * * @throws Exception + * * @author Diogo Cordeiro */ private function handle_like() @@ -342,6 +379,7 @@ class Activitypub_inbox_handler * @throws HTTP_Request2_Exception * @throws NoProfileException * @throws ServerException + * * @author Diogo Cordeiro */ private function handle_undo() @@ -364,6 +402,7 @@ class Activitypub_inbox_handler * @throws NoProfileException * @throws ServerException * @throws Exception + * * @author Diogo Cordeiro */ private function handle_undo_follow() @@ -387,6 +426,7 @@ class Activitypub_inbox_handler * @throws AlreadyFulfilledException * @throws ServerException * @throws Exception + * * @author Diogo Cordeiro */ private function handle_undo_like() @@ -399,6 +439,7 @@ class Activitypub_inbox_handler * Handles a Announce Activity received by our inbox. * * @throws Exception + * * @author Diogo Cordeiro */ private function handle_announce() diff --git a/plugins/ActivityPub/lib/models/Activitypub_accept.php b/plugins/ActivityPub/lib/models/Activitypub_accept.php index 29b95cca69..3cdf3c98a9 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_accept.php +++ b/plugins/ActivityPub/lib/models/Activitypub_accept.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -40,17 +42,19 @@ class Activitypub_accept * Generates an ActivityPub representation of a Accept * * @author Diogo Cordeiro + * * @param array $object + * * @return array pretty array to be used in a response */ public static function accept_to_array($object) { $res = [ '@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'], - 'object' => $object + 'id' => common_root_url() . 'accept_follow_from_' . urlencode($object['actor']) . '_to_' . urlencode($object['object']), + 'type' => 'Accept', + 'actor' => $object['object'], + 'object' => $object, ]; return $res; } @@ -59,8 +63,11 @@ class Activitypub_accept * Verifies if a given object is acceptable for an Accept Activity. * * @param array $object - * @return bool + * * @throws Exception + * + * @return bool + * * @author Diogo Cordeiro */ public static function validate_object($object) @@ -75,7 +82,7 @@ class Activitypub_accept case 'Follow': // Validate data if (!filter_var($object['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; default: diff --git a/plugins/ActivityPub/lib/models/Activitypub_announce.php b/plugins/ActivityPub/lib/models/Activitypub_announce.php index 84d24b1359..1fc0c196fc 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_announce.php +++ b/plugins/ActivityPub/lib/models/Activitypub_announce.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -40,31 +42,30 @@ class Activitypub_announce * Generates an ActivityPub representation of a Announce * * @param Profile $actor - * @param Notice $notice - * @param Notice $repeat_of + * @param Notice $notice + * * @return array pretty array to be used in a response + * * @author Diogo Cordeiro */ - public static function announce_to_array( - Profile $actor, - Notice $notice, - Notice $repeat_of - ): array { - $actor_uri = $actor->getUri(); + public static function announce_to_array(Profile $actor, Notice $notice): array + { + $actor_uri = $actor->getUri(); + $notice_url = Activitypub_notice::getUrl($notice); $to = [common_local_url('apActorFollowers', ['id' => $actor->getID()])]; foreach ($notice->getAttentionProfiles() as $to_profile) { $to[] = $to_profile->getUri(); } - $cc[]= 'https://www.w3.org/ns/activitystreams#Public'; + $cc[] = 'https://www.w3.org/ns/activitystreams#Public'; $res = [ '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => Activitypub_notice::getUri($notice), + 'id' => common_root_url() . 'share_from_' . urlencode($actor_uri) . '_to_' . urlencode($notice_url), 'type' => 'Announce', 'actor' => $actor_uri, - 'object' => Activitypub_notice::getUri($repeat_of), + 'object' => $notice_url, 'to' => $to, 'cc' => $cc, ]; diff --git a/plugins/ActivityPub/lib/models/Activitypub_attachment.php b/plugins/ActivityPub/lib/models/Activitypub_attachment.php index dc3a91d3fa..5e0737a0f5 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_attachment.php +++ b/plugins/ActivityPub/lib/models/Activitypub_attachment.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -40,13 +42,15 @@ class Activitypub_attachment * Generates a pretty array from an Attachment object * * @author Diogo Cordeiro + * * @param Attachment $attachment + * * @return array pretty array to be used in a response */ public static function attachment_to_array($attachment) { $res = [ - '@context' => 'https://www.w3.org/ns/activitystreams', + '@context' => 'https://www.w3.org/ns/activitystreams', 'type' => 'Document', 'mediaType' => $attachment->mimetype, 'url' => $attachment->getUrl(), @@ -55,10 +59,10 @@ class Activitypub_attachment ]; // Image - if (substr($res["mediaType"], 0, 5) == "image") { - $res["meta"]= [ + if (substr($res['mediaType'], 0, 5) == 'image') { + $res['meta'] = [ 'width' => $attachment->width, - 'height' => $attachment->height + 'height' => $attachment->height, ]; } diff --git a/plugins/ActivityPub/lib/models/Activitypub_create.php b/plugins/ActivityPub/lib/models/Activitypub_create.php index 569569ffee..a5c6993841 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_create.php +++ b/plugins/ActivityPub/lib/models/Activitypub_create.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -40,23 +42,24 @@ class Activitypub_create * Generates an ActivityPub representation of a Create * * @param string $actor - * @param string $uri - * @param mixed $object - * @param bool $directMessage whether it is a private Create activity or not + * @param array $object + * @param bool $directMessage whether it is a private Create activity or not + * * @return array pretty array to be used in a response + * * @author Diogo Cordeiro */ public static function create_to_array(string $actor, string $uri, $object, bool $directMessage = false): array { $res = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $uri, - 'type' => 'Create', + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $object['id'] . '/create', + 'type' => 'Create', 'directMessage' => $directMessage, - 'to' => $object['to'], - 'cc' => $object['cc'], - 'actor' => $actor, - 'object' => $object + 'to' => $object['to'], + 'cc' => $object['cc'], + 'actor' => $actor, + 'object' => $object, ]; return $res; } @@ -65,8 +68,11 @@ class Activitypub_create * Verifies if a given object is acceptable for a Create Activity. * * @param array $object - * @return bool True if acceptable, false if valid but unsupported + * * @throws Exception if invalid + * + * @return bool True if acceptable, false if valid but unsupported + * * @author Diogo Cordeiro */ public static function validate_object($object): bool @@ -100,7 +106,9 @@ class Activitypub_create * https://github.com/w3c/activitypub/issues/196#issuecomment-304958984 * * @param array $activity received Create-Note activity + * * @return bool true if note is private, false otherwise + * * @author Bruno casteleiro */ public static function isPrivateNote(array $activity): bool diff --git a/plugins/ActivityPub/lib/models/Activitypub_delete.php b/plugins/ActivityPub/lib/models/Activitypub_delete.php index 741740cdc2..315b547648 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_delete.php +++ b/plugins/ActivityPub/lib/models/Activitypub_delete.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -39,35 +41,35 @@ class Activitypub_delete /** * Generates an ActivityStreams 2.0 representation of a Delete * - * @param Notice $object + * @param string $actor actor URI + * @param string $object object URI + * * @return array pretty array to be used in a response + * * @author Diogo Cordeiro */ public static function delete_to_array($object): array { - if ($object instanceof Notice) { - return Activitypub_notice::notice_to_array($object); - } else if ($object instanceof Profile) { - $actor_uri = $object->getUri(); - return [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $actor_uri . '#delete', - 'type' => 'Delete', - 'to' => ['https://www.w3.org/ns/activitystreams#Public'], - 'actor' => $actor_uri, - 'object' => $object - ]; - } else { - throw new InvalidArgumentException(); - } + $res = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $object . '/delete', + 'type' => 'Delete', + 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'actor' => $actor, + 'object' => $object, + ]; + return $res; } /** * Verifies if a given object is acceptable for a Delete Activity. * * @param array|string $object - * @return bool + * * @throws Exception + * + * @return bool + * * @author Bruno Casteleiro */ public static function validate_object($object): bool @@ -80,7 +82,7 @@ class Activitypub_delete if (!isset($object['type'])) { throw new Exception('Object type was not specified for Delete Activity.'); } - if ($object['type'] !== "Tombstone" && $object['type'] !== "Person") { + if ($object['type'] !== 'Tombstone' && $object['type'] !== 'Person') { throw new Exception('Invalid Object type for Delete Activity.'); } if (!isset($object['id'])) { diff --git a/plugins/ActivityPub/lib/models/Activitypub_error.php b/plugins/ActivityPub/lib/models/Activitypub_error.php index aa182029d1..15540ad643 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_error.php +++ b/plugins/ActivityPub/lib/models/Activitypub_error.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -40,13 +42,15 @@ class Activitypub_error * Generates a pretty error from a string * * @author Diogo Cordeiro + * * @param string $m + * * @return array pretty array to be used in a response */ public static function error_message_to_array(string $m): array { - return [ - 'error'=> $m + $res = [ + 'error' => $m, ]; } } diff --git a/plugins/ActivityPub/lib/models/Activitypub_follow.php b/plugins/ActivityPub/lib/models/Activitypub_follow.php index c4bcc5d304..082503e882 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_follow.php +++ b/plugins/ActivityPub/lib/models/Activitypub_follow.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -40,24 +42,26 @@ class Activitypub_follow * Generates an ActivityPub representation of a subscription * * @author Diogo Cordeiro - * @param string $actor - * @param string $object - * @param string|null $id Activity id, to be used when generating for an Accept Activity + * + * @param string $actor + * @param string $object + * @param null|string $id Activity id, to be used when generating for an Accept Activity + * * @return array pretty array to be used in a response */ public static function follow_to_array(string $actor, string $object, ?string $id = null): array { if ($id === null) { - $id = common_root_url().'follow_from_'.urlencode($actor).'_to_'.urlencode($object); + $id = common_root_url() . 'follow_from_' . urlencode($actor) . '_to_' . urlencode($object); } $res = [ '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $id, - 'type' => 'Follow', - 'actor' => $actor, - 'object' => $object - ]; + 'id' => $id, + 'type' => 'Follow', + 'actor' => $actor, + 'object' => $object, + ]; return $res; } @@ -65,12 +69,14 @@ class Activitypub_follow * Handles a Follow Activity received by our inbox. * * @param Profile $actor_profile Remote Actor - * @param string $object Local Actor - * @param string $id Activity id + * @param string $object Local Actor + * @param string $id Activity id + * * @throws AlreadyFulfilledException * @throws HTTP_Request2_Exception * @throws NoProfileException * @throws ServerException + * * @author Diogo Cordeiro */ public static function follow(Profile $actor_profile, string $object, string $id) @@ -85,13 +91,13 @@ class Activitypub_follow if (!Subscription::exists($actor_profile, $object_profile)) { Subscription::start($actor_profile, $object_profile); Activitypub_profile::subscribeCacheUpdate($actor_profile, $object_profile); - common_debug('ActivityPubPlugin: Accepted Follow request from '.$actor_profile->getUri().' to '.$object); + common_debug('ActivityPubPlugin: Accepted Follow request from ' . $actor_profile->getUri() . ' to ' . $object); } else { - common_debug('ActivityPubPlugin: Received a repeated Follow request from '.$actor_profile->getUri().' to '.$object); + common_debug('ActivityPubPlugin: Received a repeated Follow request from ' . $actor_profile->getUri() . ' 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 '.$actor_profile->getUri().' to '.$object); + common_debug('ActivityPubPlugin: Notifying remote instance that we have accepted their Follow request request from ' . $actor_profile->getUri() . ' to ' . $object); $postman = new Activitypub_postman($object_profile, [$actor_aprofile]); $postman->accept_follow($id); } diff --git a/plugins/ActivityPub/lib/models/Activitypub_like.php b/plugins/ActivityPub/lib/models/Activitypub_like.php index 147025b29c..0080c564d3 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_like.php +++ b/plugins/ActivityPub/lib/models/Activitypub_like.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -39,8 +41,11 @@ class Activitypub_like /** * Generates an ActivityPub representation of a Like * + * @author Diogo Cordeiro + * * @param string $actor Actor URI - * @param Notice $notice Notice URI + * @param string $object Notice URI + * * @return array pretty array to be used in a response * @author Diogo Cordeiro */ @@ -48,10 +53,10 @@ class Activitypub_like { $res = [ '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => Activitypub_notice::getUri($notice), + 'id' => common_root_url() . 'like_from_' . urlencode($actor) . '_to_' . urlencode($object), 'type' => 'Like', 'actor' => $actor, - 'object' => Activitypub_notice::getUri($notice->getParent()), + 'object' => $object, ]; return $res; } diff --git a/plugins/ActivityPub/lib/models/Activitypub_mention_tag.php b/plugins/ActivityPub/lib/models/Activitypub_mention_tag.php index 330f8d455c..8e16f9626a 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_mention_tag.php +++ b/plugins/ActivityPub/lib/models/Activitypub_mention_tag.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -40,18 +42,20 @@ class Activitypub_mention_tag * Generates an ActivityPub representation of a Mention Tag * * @author Diogo Cordeiro + * * @param string $href Actor Uri * @param string $name Mention name + * * @return array pretty array to be used in a response */ public static function mention_tag_to_array_from_values(string $href, string $name): array { $res = [ '@context' => 'https://www.w3.org/ns/activitystreams', - "type" => "Mention", - "href" => $href, - "name" => $name - ]; + 'type' => 'Mention', + 'href' => $href, + 'name' => $name, + ]; return $res; } } diff --git a/plugins/ActivityPub/lib/models/Activitypub_message.php b/plugins/ActivityPub/lib/models/Activitypub_message.php index adbbfa5d4c..686c97dbc6 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_message.php +++ b/plugins/ActivityPub/lib/models/Activitypub_message.php @@ -18,11 +18,11 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - defined('GNUSOCIAL') || die(); /** @@ -38,7 +38,9 @@ class Activitypub_message * Generates a pretty message from a Notice object * * @param Notice $message + * * @return array array to be used in a response + * * @author Bruno Casteleiro */ public static function message_to_array(Notice $message): array @@ -54,21 +56,21 @@ class Activitypub_message $to = []; foreach ($message->getAttentionProfiles() as $to_profile) { - $to[] = $href = $to_profile->getUri(); - $tags[] = Activitypub_mention_tag::mention_tag_to_array_from_values($href, $to_profile->getNickname().'@'.parse_url($href, PHP_URL_HOST)); + $to[] = $href = $to_profile->getUri(); + $tags[] = Activitypub_mention_tag::mention_tag_to_array_from_values($href, $to_profile->getNickname() . '@' . parse_url($href, PHP_URL_HOST)); } $item = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => common_local_url('showmessage', ['message' => $message->getID()]), - 'type' => 'Note', - 'published' => str_replace(' ', 'T', $message->created).'Z', - 'attributedTo' => $from->getUri(), - 'to' => $to, - 'cc' => [], - 'content' => $message->getRendered(), - 'attachment' => [], - 'tag' => $tags + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => common_local_url('showmessage', ['message' => $message->getID()]), + 'type' => 'Note', + 'published' => str_replace(' ', 'T', $message->created) . 'Z', + 'attributedTo' => $from->getUri(), + 'to' => $to, + 'cc' => [], + 'content' => $message->getRendered(), + 'attachment' => [], + 'tag' => $tags, ]; return $item; @@ -79,10 +81,13 @@ class Activitypub_message * Returns created Notice. * * @author Bruno Casteleiro - * @param array $object + * + * @param array $object * @param Profile $actor_profile - * @return Notice + * * @throws Exception + * + * @return Notice */ public static function create_message(array $object, Profile $actor_profile = null): Notice { diff --git a/plugins/ActivityPub/lib/models/Activitypub_notice.php b/plugins/ActivityPub/lib/models/Activitypub_notice.php index cf929a98cb..d8217d1b32 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_notice.php +++ b/plugins/ActivityPub/lib/models/Activitypub_notice.php @@ -18,12 +18,13 @@ * ActivityPub implementation for GNU social * * @package GNUsocial + * * @author Diogo Cordeiro * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * @link http://www.gnu.org/software/social/ + * + * @see http://www.gnu.org/software/social/ */ - defined('GNUSOCIAL') || die(); /** @@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die(); * * @category Plugin * @package GNUsocial + * * @author Diogo Cordeiro * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -40,16 +42,19 @@ class Activitypub_notice * Generates a pretty notice from a Notice object * * @param Notice $notice - * @return array array to be used in a response + * * @throws EmptyPkeyValueException * @throws InvalidUrlException * @throws ServerException * @throws Exception + * + * @return array array to be used in a response + * * @author Diogo Cordeiro */ public static function notice_to_array(Notice $notice): array { - $profile = $notice->getProfile(); + $profile = $notice->getProfile(); $attachments = []; foreach ($notice->attachments() as $attachment) { $attachments[] = Activitypub_attachment::attachment_to_array($attachment); @@ -57,7 +62,7 @@ class Activitypub_notice $tags = []; foreach ($notice->getTags() as $tag) { - if ($tag != "") { // Hacky workaround to avoid stupid outputs + if ($tag != '') { // Hacky workaround to avoid stupid outputs $tags[] = Activitypub_tag::tag_to_array($tag); } } @@ -75,46 +80,25 @@ class Activitypub_notice } foreach ($notice->getAttentionProfiles() as $to_profile) { - $to[] = $href = $to_profile->getUri(); + $to[] = $href = $to_profile->getUri(); $tags[] = Activitypub_mention_tag::mention_tag_to_array_from_values($href, $to_profile->getNickname() . '@' . parse_url($href, PHP_URL_HOST)); } - if (ActivityUtils::compareVerbs($notice->getVerb(), ActivityVerb::DELETE)) { - $item = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => self::getUri($notice), - 'type' => 'Delete', - // XXX: A bit of ugly code here - 'object' => array_merge(Activitypub_tombstone::tombstone_to_array((int)substr(explode(':', $notice->getUri())[2], 9)), ['deleted' => str_replace(' ', 'T', $notice->getCreated()) . 'Z']), - 'url' => $notice->getUrl(), - 'actor' => $profile->getUri(), - 'to' => $to, - 'cc' => $cc, - 'conversationId' => $notice->getConversationUrl(false), - 'conversationUrl' => $notice->getConversationUrl(), - 'content' => $notice->getRendered(), - 'isLocal' => $notice->isLocal(), - 'attachment' => $attachments, - 'tag' => $tags - ]; - } else { // Note - $item = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => self::note_uri($notice->getID()), - 'type' => 'Note', - 'published' => str_replace(' ', 'T', $notice->getCreated()) . 'Z', - 'url' => $notice->getUrl(), - 'attributedTo' => $profile->getUri(), - 'to' => $to, - 'cc' => $cc, - 'conversationId' => $notice->getConversationUrl(false), - 'conversationUrl' => $notice->getConversationUrl(), - 'content' => $notice->getRendered(), - 'isLocal' => $notice->isLocal(), - 'attachment' => $attachments, - 'tag' => $tags - ]; - } + $item = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => self::getUrl($notice), + 'type' => 'Note', + 'published' => str_replace(' ', 'T', $notice->getCreated()) . 'Z', + 'url' => self::getUrl($notice), + 'attributedTo' => $profile->getUri(), + 'to' => $to, + 'cc' => $cc, + 'conversation' => $notice->getConversationUrl(), + 'content' => $notice->getRendered(), + 'isLocal' => $notice->isLocal(), + 'attachment' => $attachments, + 'tag' => $tags, + ]; // Is this a reply? if (!empty($notice->reply_to)) { @@ -123,8 +107,8 @@ class Activitypub_notice // Do we have a location for this notice? try { - $location = Notice_location::locFromStored($notice); - $item['latitude'] = $location->lat; + $location = Notice_location::locFromStored($notice); + $item['latitude'] = $location->lat; $item['longitude'] = $location->lon; } catch (Exception $e) { // Apparently no. @@ -137,17 +121,20 @@ class Activitypub_notice * Create a Notice via ActivityPub Note Object. * Returns created Notice. * - * @param array $object + * @param array $object * @param Profile $actor_profile - * @param bool $directMessage - * @return Notice + * @param bool $directMessage + * * @throws Exception + * + * @return Notice + * * @author Diogo Cordeiro */ public static function create_notice(array $object, Profile $actor_profile, bool $directMessage = false): Notice { - $id = $object['id']; // int - $url = isset($object['url']) ? $object['url'] : $id; // string + $id = $object['id']; // int + $url = isset($object['url']) ? $object['url'] : $id; // string $content = $object['content']; // string // possible keys: ['inReplyTo', 'latitude', 'longitude'] @@ -162,15 +149,15 @@ class Activitypub_notice $settings['longitude'] = $object['longitude']; } - $act = new Activity(); - $act->verb = ActivityVerb::POST; - $act->time = time(); - $act->actor = $actor_profile->asActivityObject(); + $act = new Activity(); + $act->verb = ActivityVerb::POST; + $act->time = time(); + $act->actor = $actor_profile->asActivityObject(); $act->context = new ActivityContext(); - $options = ['source' => 'ActivityPub', - 'uri' => $id, - 'url' => $url, - 'is_local' => self::getNotePolicyType($object, $actor_profile)]; + $options = ['source' => 'ActivityPub', + 'uri' => $id, + 'url' => $url, + 'is_local' => self::getNotePolicyType($object, $actor_profile), ]; if ($directMessage) { $options['scope'] = Notice::MESSAGE_SCOPE; @@ -179,8 +166,8 @@ class Activitypub_notice // Is this a reply? if (isset($settings['inReplyTo'])) { try { - $inReplyTo = ActivityPubPlugin::grab_notice_from_url($settings['inReplyTo']); - $act->context->replyToID = $inReplyTo->getUri(); + $inReplyTo = ActivityPubPlugin::grab_notice_from_url($settings['inReplyTo']); + $act->context->replyToID = $inReplyTo->getUri(); $act->context->replyToUrl = $inReplyTo->getUrl(); } catch (Exception $e) { // It failed to grab, maybe we got this note from another source @@ -203,7 +190,7 @@ class Activitypub_notice } } $mentions_profiles = []; - $discovery = new Activitypub_explorer; + $discovery = new Activitypub_explorer; foreach ($mentions as $mention) { try { $mentioned_profile = $discovery->lookup($mention); @@ -240,32 +227,10 @@ class Activitypub_notice && $attachment['type'] === 'Document' && array_key_exists('url', $attachment)) { try { - $file = new File(); - $file->url = $attachment['url']; - $file->title = array_key_exists('type', $attachment) ? $attachment['name'] : null; - if (array_key_exists('type', $attachment)) { - $file->mimetype = $attachment['mediaType']; - } else { - $http = new HTTPClient(); - common_debug( - 'Performing HEAD request for incoming activity ' - . 'to avoid unnecessarily downloading too ' - . 'large files. URL: ' . $file->url - ); - $head = $http->head($file->url); - $headers = $head->getHeader(); - $headers = array_change_key_case($headers, CASE_LOWER); - if (array_key_exists('content-type', $headers)) { - $file->mimetype = $headers['content-type']; - } else { - continue; - } - if (array_key_exists('content-length', $headers)) { - $file->size = $headers['content-length']; - } - } - $file->saveFile(); - $attachments[] = $file; + // throws exception on failure + $attachment = MediaFile::fromUrl($attachment['url'], $actor_profile, $attachment['name']); + $act->enclosures[] = $attachment->getEnclosure(); + $attachments[] = $attachment; } catch (Exception $e) { // Whatever. continue; @@ -274,9 +239,9 @@ class Activitypub_notice } } - $actobj = new ActivityObject(); - $actobj->type = ActivityObject::NOTE; - $actobj->content = strip_tags($content, '