#71 ActivityPub: Adding support for the DELETE-Person activity

Merged
tenma merged 1 commits from tenma/ap-delete-person into diogo/nightly 5 years ago

+ 56 - 14
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 <diogo@fc.up.pt>
      * @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;
     }
 
     /**
@@ -327,6 +333,30 @@ class ActivityPubPlugin extends Plugin
     }
 
     /**
+     * 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
      *
      * @param array $protocols
@@ -907,11 +937,23 @@ 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
      *
      * @param Notice $message

+ 45 - 9
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 <brunoccast@fc.up.pt>
+     */
+    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 <brunoccast@fc.up.pt>
+     */
+    private function handle_delete_note(Notice $note): void
+    {
+        $note->deleteAs($this->actor);
     }
 
     /**

+ 7 - 6
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.');
             }

+ 33 - 3
plugins/ActivityPub/lib/postman.php

@@ -53,7 +53,7 @@ class Activitypub_postman
      * @throws Exception
      * @author Diogo Cordeiro <diogo@fc.up.pt>
      */
-    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 <diogo@fc.up.pt>
      */
-    public function delete($notice)
+    public function delete_note($notice)
     {
         $data = Activitypub_delete::delete_to_array(
             ActivityPubPlugin::actor_uri($notice->getProfile()),
@@ -384,6 +383,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 <brunoccast@fc.up.pt>
+     */
+    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
      *
      * @author Diogo Cordeiro <diogo@fc.up.pt>