From c06182c38febf2a1b4e016ccb85709304112a7c1 Mon Sep 17 00:00:00 2001 From: tenma Date: Tue, 27 Aug 2019 15:37:01 +0100 Subject: [PATCH] [ActivityPub] Handle DELETE-Person activity ActivityPubPlugin: - update grab_notice_from_url to make online grab optional - subscribe events of user and profile deletion - bump minor version number Activitypub_inbox_handler: - separate handle_delete for delete-note and delete-person Activitypub_postman: - add delete-person logic Activitypub_delete: - update validation method to check for the "Person" type - update to_array method to target the activity --- plugins/ActivityPub/ActivityPubPlugin.php | 70 +++++++++++++++---- plugins/ActivityPub/lib/inbox_handler.php | 54 +++++++++++--- .../lib/models/Activitypub_delete.php | 13 ++-- plugins/ActivityPub/lib/postman.php | 36 +++++++++- 4 files changed, 141 insertions(+), 32 deletions(-) diff --git a/plugins/ActivityPub/ActivityPubPlugin.php b/plugins/ActivityPub/ActivityPubPlugin.php index 2a32223e26..89ded8855c 100644 --- a/plugins/ActivityPub/ActivityPubPlugin.php +++ b/plugins/ActivityPub/ActivityPubPlugin.php @@ -50,7 +50,7 @@ const ACTIVITYPUB_PUBLIC_TO = ['https://www.w3.org/ns/activitystreams#Public', */ class ActivityPubPlugin extends Plugin { - const PLUGIN_VERSION = '0.2.0alpha0'; + const PLUGIN_VERSION = '0.3.0alpha0'; /** * Returns a Actor's URI from its local $profile @@ -89,10 +89,11 @@ class ActivityPubPlugin extends Plugin * * @author Diogo Cordeiro * @param string $url Notice's URL - * @return Notice The Notice object - * @throws Exception This function or provides a Notice or fails with exception + * @param bool $grabOnline whether to try online grabbing, defaults to true + * @return Notice|null The Notice object + * @throws Exception This function or provides a Notice, null, or fails with exception */ - public static function grab_notice_from_url($url) + public static function grab_notice_from_url(string $url, bool $grabOnline = true): ?Notice { /* Offline Grabbing */ try { @@ -113,15 +114,20 @@ class ActivityPubPlugin extends Plugin } } - /* Online Grabbing */ - $client = new HTTPClient(); - $headers = []; - $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; - $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social'; - $response = $client->get($url, $headers); - $object = json_decode($response->getBody(), true); - Activitypub_notice::validate_note($object); - return Activitypub_notice::create_notice($object); + if ($grabOnline) { + /* Online Grabbing */ + $client = new HTTPClient(); + $headers = []; + $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; + $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social'; + $response = $client->get($url, $headers); + $object = json_decode($response->getBody(), true); + Activitypub_notice::validate_note($object); + return Activitypub_notice::create_notice($object); + } + + common_debug('ActivityPubPlugin Notice Grabber: failed to find: '.$url); + return null; } /** @@ -326,6 +332,30 @@ class ActivityPubPlugin extends Plugin return true; } + /** + * Mark an ap_profile object for deletion + * + * @param Profile profile being deleted + * @param array &$related objects with same profile_id to be deleted + * @return void + */ + public function onProfileDeleteRelated(Profile $profile, array &$related): void + { + if ($profile->isLocal()) { + return; + } + + try { + $aprofile = Activitypub_profile::getKV('profile_id', $profile->getID()); + if ($aprofile instanceof Activitypub_profile) { + // mark for deletion + $related[] = 'Activitypub_profile'; + } + } catch (Exception $e) { + // nothing to do + } + } + /** * Plugin Nodeinfo information * @@ -907,10 +937,22 @@ class ActivityPubPlugin extends Plugin } $postman = new Activitypub_postman($profile, $other); - $postman->delete($notice); + $postman->delete_note($notice); return true; } + /** + * Notify remote followers when a user gets deleted + * + * @param Action $action + * @param User $user user being deleted + */ + public function onEndDeleteUser(Action $action, User $user): void + { + $postman = new Activitypub_postman($user->getProfile()); + $postman->delete_profile(); + } + /** * Federate private message * diff --git a/plugins/ActivityPub/lib/inbox_handler.php b/plugins/ActivityPub/lib/inbox_handler.php index 62f6be567a..f48ef74dc4 100644 --- a/plugins/ActivityPub/lib/inbox_handler.php +++ b/plugins/ActivityPub/lib/inbox_handler.php @@ -226,18 +226,54 @@ class Activitypub_inbox_handler $object = $object['id']; } - // Already deleted? (By some admin, perhaps?) + // profile deletion ? + $aprofile = Activitypub_explorer::get_aprofile_by_url($object); + if ($aprofile instanceof Activitypub_profile) { + $this->handle_delete_profile($aprofile); + return; + } + + // note deletion ? try { - $found = Deleted_notice::getByUri($object); - $deleted = ($found instanceof Deleted_notice); - } catch (NoResultException $e) { - $deleted = false; + $notice = ActivityPubPlugin::grab_notice_from_url($object, false); + if ($notice instanceof Notice) { + $this->handle_delete_note($notice); + } + return; + } catch (Exception $e) { + // either already deleted or not a notice at all + // nothing to do.. } - if (!$deleted) { - $notice = ActivityPubPlugin::grab_notice_from_url($object); - $notice->deleteAs($this->actor); - } + common_log(LOG_INFO, "Ignoring Delete activity, nothing that we can/need to handle."); + } + + /** + * Handles a Delete-Profile Activity. + * + * Note that the actual ap_profile is deleted during the ProfileDeleteRelated event, + * subscribed by ActivityPubPlugin. + * + * @param Activitypub_profile $aprofile remote user being deleted + * @return void + * @author Bruno Casteleiro + */ + private function handle_delete_profile(Activitypub_profile $aprofile): void + { + $profile = $aprofile->local_profile(); + $profile->delete(); + } + + /** + * Handles a Delete-Note Activity. + * + * @param Notice $note remote note being deleted + * @return void + * @author Bruno Casteleiro + */ + private function handle_delete_note(Notice $note): void + { + $note->deleteAs($this->actor); } /** diff --git a/plugins/ActivityPub/lib/models/Activitypub_delete.php b/plugins/ActivityPub/lib/models/Activitypub_delete.php index f0e0fd73b7..6cfc97412e 100644 --- a/plugins/ActivityPub/lib/models/Activitypub_delete.php +++ b/plugins/ActivityPub/lib/models/Activitypub_delete.php @@ -48,10 +48,11 @@ class Activitypub_delete { $res = [ '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $object.'/delete', - 'type' => 'Delete', - 'actor' => $actor, - 'object' => $object + 'id' => $object.'/delete', + 'type' => 'Delete', + 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'actor' => $actor, + 'object' => $object ]; return $res; } @@ -73,10 +74,10 @@ class Activitypub_delete } else { if (!isset($object['type'])) { throw new Exception('Object type was not specified for Delete Activity.'); - } else if ($object['type'] !== "Tombstone") { + } + if ($object['type'] !== "Tombstone" && $object['type'] !== "Person") { throw new Exception('Invalid Object type for Delete Activity.'); } - if (!isset($object['id'])) { throw new Exception('Object id was not specified for Delete Activity.'); } diff --git a/plugins/ActivityPub/lib/postman.php b/plugins/ActivityPub/lib/postman.php index 948dbb0ca4..d035c95778 100644 --- a/plugins/ActivityPub/lib/postman.php +++ b/plugins/ActivityPub/lib/postman.php @@ -53,7 +53,7 @@ class Activitypub_postman * @throws Exception * @author Diogo Cordeiro */ - public function __construct(Profile $from, array $to) + public function __construct(Profile $from, array $to = []) { $this->actor = $from; $this->to = $to; @@ -359,10 +359,9 @@ class Activitypub_postman * @throws HTTP_Request2_Exception * @throws InvalidUrlException * @throws Exception - * @throws Exception * @author Diogo Cordeiro */ - public function delete($notice) + public function delete_note($notice) { $data = Activitypub_delete::delete_to_array( ActivityPubPlugin::actor_uri($notice->getProfile()), @@ -383,6 +382,37 @@ class Activitypub_postman } } + /** + * Send a Delete notification to remote followers of some deleted profile + * + * @param Notice $notice + * @throws HTTP_Request2_Exception + * @throws InvalidUrlException + * @throws Exception + * @author Bruno Casteleiro + */ + public function delete_profile() + { + $data = Activitypub_delete::delete_to_array($this->actor_uri, $this->actor_uri); + $data = json_encode($data, JSON_UNESCAPED_SLASHES); + + $errors = []; + foreach ($this->to_inbox() as $inbox) { + $res = $this->send($data, $inbox); + + // accummulate errors for later use, if needed + if (!($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409)) { + $res_body = json_decode($res->getBody(), true); + $errors[] = isset($res_body[0]['error']) ? + $res_body[0]['error'] : "An unknown error occurred."; + } + } + + if (!empty($errors)) { + common_log(LOG_ERR, sizeof($errors) . " instance/s failed to handle the delete_profile activity!"); + } + } + /** * Clean list of inboxes to deliver messages *