diff --git a/plugins/ActivityPub/ActivityPubPlugin.php b/plugins/ActivityPub/ActivityPubPlugin.php index 03d3a1da71..c4eaab183d 100644 --- a/plugins/ActivityPub/ActivityPubPlugin.php +++ b/plugins/ActivityPub/ActivityPubPlugin.php @@ -747,10 +747,12 @@ class ActivityPubPlugin extends Plugin return true; } - // The deleting user must have permission to do so, but - // it still doesn't own the notitce, so we just need to - // handle things locally - if (!$notice->isLocal()) { + // We handle things locally either because: + // 1. the deleting user has special permissions to do so, + // but still doesn't own the notice + // 2. the notice is an announce, and there's no undo-share + // logic in GS's AP implementation + if (!$notice->isLocal() || $notice->isRepeat()) { return true; } diff --git a/plugins/ActivityPub/classes/Activitypub_announce.php b/plugins/ActivityPub/classes/Activitypub_announce.php index a85a480a1c..66ea3f316b 100644 --- a/plugins/ActivityPub/classes/Activitypub_announce.php +++ b/plugins/ActivityPub/classes/Activitypub_announce.php @@ -39,18 +39,31 @@ class Activitypub_announce extends Managed_DataObject /** * Generates an ActivityPub representation of a Announce * - * @param $actor - * @param array $object + * @param Profile $actor + * @param Notice $notice * @return array pretty array to be used in a response * @author Diogo Cordeiro */ - public static function announce_to_array($actor, $object) + public static function announce_to_array(Profile $actor, Notice $notice): array { + $actor_uri = ActivityPubPlugin::actor_uri($actor); + $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'; + $res = [ '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => common_root_url().'share_from_'.urlencode($actor_uri).'_to_'.urlencode($notice_url), "type" => "Announce", - "actor" => $actor, - "object" => $object + "actor" => $actor_uri, + "object" => $notice_url, + "to" => $to, + "cc" => $cc ]; return $res; } diff --git a/plugins/ActivityPub/classes/Activitypub_follow.php b/plugins/ActivityPub/classes/Activitypub_follow.php index 2999c8c13b..466971afca 100644 --- a/plugins/ActivityPub/classes/Activitypub_follow.php +++ b/plugins/ActivityPub/classes/Activitypub_follow.php @@ -42,13 +42,18 @@ class Activitypub_follow extends Managed_DataObject * @author Diogo Cordeiro * @param string $actor * @param string $object + * @param string|null $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($actor, $object) + 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); + } + $res = [ '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => common_root_url().'follow_from_'.urlencode($actor).'_to_'.urlencode($object), + 'id' => $id, 'type' => 'Follow', 'actor' => $actor, 'object' => $object @@ -61,13 +66,14 @@ class Activitypub_follow extends Managed_DataObject * * @param Profile $actor_profile Remote Actor * @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($actor_profile, $object) + public static function follow(Profile $actor_profile, string $object, string $id) { // Get Actor's Aprofile $actor_aprofile = Activitypub_profile::from_profile($actor_profile); @@ -87,6 +93,6 @@ class Activitypub_follow extends Managed_DataObject // 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($object_profile, [$actor_aprofile]); - $postman->accept_follow(); + $postman->accept_follow($id); } } diff --git a/plugins/ActivityPub/classes/Activitypub_notice.php b/plugins/ActivityPub/classes/Activitypub_notice.php index 5114bfd107..bdc8ab1579 100644 --- a/plugins/ActivityPub/classes/Activitypub_notice.php +++ b/plugins/ActivityPub/classes/Activitypub_notice.php @@ -77,7 +77,7 @@ class Activitypub_notice extends Managed_DataObject 'published' => str_replace(' ', 'T', $notice->getCreated()).'Z', 'url' => self::getUrl($notice), 'attributedTo' => ActivityPubPlugin::actor_uri($profile), - 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'to' => $to, 'cc' => $cc, 'conversation' => $notice->getConversationUrl(), 'content' => $notice->getRendered(), @@ -235,9 +235,7 @@ class Activitypub_notice extends Managed_DataObject common_debug('ActivityPub Notice Validator: Rejected because Content was not specified.'); throw new Exception('Object content was not specified.'); } - if (!isset($object['url'])) { - throw new Exception('Object URL was not specified.'); - } elseif (!filter_var($object['url'], FILTER_VALIDATE_URL)) { + if (isset($object['url']) && !filter_var($object['url'], FILTER_VALIDATE_URL)) { common_debug('ActivityPub Notice Validator: Rejected because Object URL is invalid.'); throw new Exception('Invalid Object URL.'); } diff --git a/plugins/ActivityPub/lib/inbox_handler.php b/plugins/ActivityPub/lib/inbox_handler.php index c602815737..4207dbc853 100644 --- a/plugins/ActivityPub/lib/inbox_handler.php +++ b/plugins/ActivityPub/lib/inbox_handler.php @@ -129,7 +129,7 @@ class Activitypub_inbox_handler $this->handle_delete($this->actor, $this->object); break; case 'Follow': - $this->handle_follow($this->actor, $this->object); + $this->handle_follow($this->actor, $this->activity); break; case 'Like': $this->handle_like($this->actor, $this->object); @@ -232,7 +232,7 @@ class Activitypub_inbox_handler * Handles a Follow Activity received by our inbox. * * @param Profile $actor Actor - * @param array $object Activity + * @param array $activity Activity * @throws AlreadyFulfilledException * @throws HTTP_Request2_Exception * @throws NoProfileException @@ -241,9 +241,9 @@ class Activitypub_inbox_handler * @throws \HttpSignatures\Exception * @author Diogo Cordeiro */ - private function handle_follow($actor, $object) + private function handle_follow($actor, $activity) { - Activitypub_follow::follow($actor, $object); + Activitypub_follow::follow($actor, $activity['object'], $activity['id']); } /** diff --git a/plugins/ActivityPub/lib/postman.php b/plugins/ActivityPub/lib/postman.php index 92a36228a8..485c0f995c 100644 --- a/plugins/ActivityPub/lib/postman.php +++ b/plugins/ActivityPub/lib/postman.php @@ -119,7 +119,7 @@ class Activitypub_postman */ public function follow() { - $data = Activitypub_follow::follow_to_array(ActivityPubPlugin::actor_uri($this->actor), $this->to[0]->getUrl()); + $data = Activitypub_follow::follow_to_array($this->actor_uri, $this->to[0]->getUrl()); $res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox()); $res_body = json_decode($res->getBody()); @@ -149,7 +149,7 @@ class Activitypub_postman { $data = Activitypub_undo::undo_to_array( Activitypub_follow::follow_to_array( - ActivityPubPlugin::actor_uri($this->actor), + $this->actor_uri, $this->to[0]->getUrl() ) ); @@ -171,23 +171,21 @@ class Activitypub_postman /** * Send a Accept Follow notification to remote instance * + * @param string $id Follow activity id * @return bool * @throws HTTP_Request2_Exception - * @throws Exception - * @throws Exception - * @throws Exception - * @throws Exception + * @throws Exception Description of HTTP Response error or generic error message. * @author Diogo Cordeiro */ - public function accept_follow() + public function accept_follow(string $id): bool { $data = Activitypub_accept::accept_to_array( Activitypub_follow::follow_to_array( $this->to[0]->getUrl(), - ActivityPubPlugin::actor_uri($this->actor) - - ) - ); + $this->actor_uri, + $id + ) + ); $res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox()); $res_body = json_decode($res->getBody()); @@ -214,7 +212,7 @@ class Activitypub_postman public function like($notice) { $data = Activitypub_like::like_to_array( - ActivityPubPlugin::actor_uri($this->actor), + $this->actor_uri, Activitypub_notice::getUrl($notice) ); $data = json_encode($data, JSON_UNESCAPED_SLASHES); @@ -248,7 +246,7 @@ class Activitypub_postman { $data = Activitypub_undo::undo_to_array( Activitypub_like::like_to_array( - ActivityPubPlugin::actor_uri($this->actor), + $this->actor_uri, Activitypub_notice::getUrl($notice) ) ); @@ -314,11 +312,8 @@ class Activitypub_postman */ public function announce($notice) { - $data = Activitypub_announce::announce_to_array( - ActivityPubPlugin::actor_uri($this->actor), - Activitypub_notice::getUrl($notice) - ); - $data = json_encode($data, JSON_UNESCAPED_SLASHES); + $data = json_encode(Activitypub_announce::announce_to_array($this->actor, $notice), + JSON_UNESCAPED_SLASHES); foreach ($this->to_inbox() as $inbox) { $res = $this->send($data, $inbox);