[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
This commit is contained in:
tenma 2019-08-27 15:37:01 +01:00 committed by Diogo Cordeiro
parent f79cd8cee3
commit c06182c38f
4 changed files with 141 additions and 32 deletions

View File

@ -50,7 +50,7 @@ const ACTIVITYPUB_PUBLIC_TO = ['https://www.w3.org/ns/activitystreams#Public',
*/ */
class ActivityPubPlugin extends Plugin 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 * Returns a Actor's URI from its local $profile
@ -89,10 +89,11 @@ class ActivityPubPlugin extends Plugin
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $url Notice's URL * @param string $url Notice's URL
* @return Notice The Notice object * @param bool $grabOnline whether to try online grabbing, defaults to true
* @throws Exception This function or provides a Notice or fails with exception * @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 */ /* Offline Grabbing */
try { try {
@ -113,15 +114,20 @@ class ActivityPubPlugin extends Plugin
} }
} }
/* Online Grabbing */ if ($grabOnline) {
$client = new HTTPClient(); /* Online Grabbing */
$headers = []; $client = new HTTPClient();
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; $headers = [];
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social'; $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$response = $client->get($url, $headers); $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$object = json_decode($response->getBody(), true); $response = $client->get($url, $headers);
Activitypub_notice::validate_note($object); $object = json_decode($response->getBody(), true);
return Activitypub_notice::create_notice($object); 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; 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 * Plugin Nodeinfo information
* *
@ -907,10 +937,22 @@ class ActivityPubPlugin extends Plugin
} }
$postman = new Activitypub_postman($profile, $other); $postman = new Activitypub_postman($profile, $other);
$postman->delete($notice); $postman->delete_note($notice);
return true; 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 * Federate private message
* *

View File

@ -226,18 +226,54 @@ class Activitypub_inbox_handler
$object = $object['id']; $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 { try {
$found = Deleted_notice::getByUri($object); $notice = ActivityPubPlugin::grab_notice_from_url($object, false);
$deleted = ($found instanceof Deleted_notice); if ($notice instanceof Notice) {
} catch (NoResultException $e) { $this->handle_delete_note($notice);
$deleted = false; }
return;
} catch (Exception $e) {
// either already deleted or not a notice at all
// nothing to do..
} }
if (!$deleted) { common_log(LOG_INFO, "Ignoring Delete activity, nothing that we can/need to handle.");
$notice = ActivityPubPlugin::grab_notice_from_url($object); }
$notice->deleteAs($this->actor);
} /**
* 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);
} }
/** /**

View File

@ -48,10 +48,11 @@ class Activitypub_delete
{ {
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $object.'/delete', 'id' => $object.'/delete',
'type' => 'Delete', 'type' => 'Delete',
'actor' => $actor, 'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'object' => $object 'actor' => $actor,
'object' => $object
]; ];
return $res; return $res;
} }
@ -73,10 +74,10 @@ class Activitypub_delete
} else { } else {
if (!isset($object['type'])) { if (!isset($object['type'])) {
throw new Exception('Object type was not specified for Delete Activity.'); 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.'); throw new Exception('Invalid Object type for Delete Activity.');
} }
if (!isset($object['id'])) { if (!isset($object['id'])) {
throw new Exception('Object id was not specified for Delete Activity.'); throw new Exception('Object id was not specified for Delete Activity.');
} }

View File

@ -53,7 +53,7 @@ class Activitypub_postman
* @throws Exception * @throws Exception
* @author Diogo Cordeiro <diogo@fc.up.pt> * @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->actor = $from;
$this->to = $to; $this->to = $to;
@ -359,10 +359,9 @@ class Activitypub_postman
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws Exception * @throws Exception
* @throws Exception
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function delete($notice) public function delete_note($notice)
{ {
$data = Activitypub_delete::delete_to_array( $data = Activitypub_delete::delete_to_array(
ActivityPubPlugin::actor_uri($notice->getProfile()), 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 <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 * Clean list of inboxes to deliver messages
* *