Proper ActivityPub collections

This commit is contained in:
Diogo Cordeiro 2018-08-02 07:01:39 +01:00
parent 0384890f7b
commit 5226ca7d81
6 changed files with 113 additions and 85 deletions

View File

@ -121,9 +121,11 @@ class ActivityPubPlugin extends Plugin
$response = $client->get($url, $headers); $response = $client->get($url, $headers);
$res = json_decode($response->getBody(), true); $res = json_decode($response->getBody(), true);
$settings = []; $settings = [];
if (!Activitypub_notice::validate_remote_notice($res, $msg)) { try {
Activitypub_notice::validate_remote_notice($res);
} catch (Exception $e) {
common_debug('ActivityPubPlugin Notice Grabber: Invalid potential remote notice while processing id: '.$url. '. He returned the following: '.json_encode($res, JSON_UNESCAPED_SLASHES)); common_debug('ActivityPubPlugin Notice Grabber: Invalid potential remote notice while processing id: '.$url. '. He returned the following: '.json_encode($res, JSON_UNESCAPED_SLASHES));
throw new Exception($msg); throw $e;
} }
if (isset($res->inReplyTo)) { if (isset($res->inReplyTo)) {

View File

@ -53,7 +53,7 @@ class apActorFollowersAction extends ManagedAction
{ {
try { try {
$profile = Profile::getByID($this->trimmed('id')); $profile = Profile::getByID($this->trimmed('id'));
$url = ActivityPubPlugin::actor_url($profile); $profile_id = $profile->getID();
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
@ -63,12 +63,12 @@ class apActorFollowersAction extends ManagedAction
} }
if (!isset($_GET["page"])) { if (!isset($_GET["page"])) {
$page = 1; $page = 0;
} else { } else {
$page = intval($this->trimmed('page')); $page = intval($this->trimmed('page'));
} }
if ($page <= 0) { if ($page < 0) {
ActivityPubReturn::error('Invalid page number.'); ActivityPubReturn::error('Invalid page number.');
} }
@ -77,23 +77,15 @@ class apActorFollowersAction extends ManagedAction
/* Fetch Followers */ /* Fetch Followers */
try { try {
$sub = $profile->getSubscribers($since, $limit); $sub = $profile->getSubscribers($since, $limit);
} catch (NoResultException $e) { } catch (NoResultException $e) {
ActivityPubReturn::error('This user has no followers.'); // Just let the exception go on its merry way
} }
/* Calculate total items */ /* Calculate total items */
$total_subs = $profile->subscriberCount(); $total_subs = $profile->subscriberCount();
$total_pages = ceil($total_subs / PROFILES_PER_MINILIST); $total_pages = ceil($total_subs / PROFILES_PER_MINILIST);
if ($total_pages == 0) {
ActivityPubReturn::error('This user has no followers.');
}
if ($page > $total_pages) {
ActivityPubReturn::error("There are only {$total_pages} pages.");
}
/* Get followers' URLs */ /* Get followers' URLs */
$subs = array(); $subs = array();
while ($sub->fetch()) { while ($sub->fetch()) {
@ -101,17 +93,29 @@ class apActorFollowersAction extends ManagedAction
} }
$res = [ $res = [
'@context' => [ '@context' => [
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1", "https://w3id.org/security/v1",
], ],
'id' => "{$url}/followers.json", 'id' => common_local_url('apActorFollowers', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''),
'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'), 'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'),
'totalItems' => $total_subs, 'totalItems' => $total_subs,
'next' => $page+1 > $total_pages ? null : "{$url}/followers.json?page=".($page+1 == 1 ? 2 : $page+1), 'orderedItems' => $subs
'prev' => $page == 1 ? null : "{$url}/followers.json?page=".($page-1 <= 0 ? 1 : $page-1), ];
'orderedItems' => $subs
]; if ($page == 0) {
$res['first'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'?page=1';
} else {
$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) {
$res['prev'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1);
}
}
ActivityPubReturn::answer($res); ActivityPubReturn::answer($res);
} }

View File

@ -53,7 +53,7 @@ class apActorFollowingAction extends ManagedAction
{ {
try { try {
$profile = Profile::getByID($this->trimmed('id')); $profile = Profile::getByID($this->trimmed('id'));
$url = ActivityPubPlugin::actor_url($profile); $profile_id = $profile->getID();
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
@ -63,12 +63,12 @@ class apActorFollowingAction extends ManagedAction
} }
if (!isset($_GET["page"])) { if (!isset($_GET["page"])) {
$page = 1; $page = 0;
} else { } else {
$page = intval($this->trimmed('page')); $page = intval($this->trimmed('page'));
} }
if ($page <= 0) { if ($page < 0) {
ActivityPubReturn::error('Invalid page number.'); ActivityPubReturn::error('Invalid page number.');
} }
@ -77,23 +77,15 @@ class apActorFollowingAction extends ManagedAction
/* Fetch Following */ /* Fetch Following */
try { try {
$sub = $profile->getSubscribed($since, $limit); $sub = $profile->getSubscribed($since, $limit);
} catch (NoResultException $e) { } catch (NoResultException $e) {
ActivityPubReturn::error('This user is not following anyone.'); // Just let the exception go on its merry way
} }
/* Calculate total items */ /* Calculate total items */
$total_subs = $profile->subscriptionCount(); $total_subs = $profile->subscriptionCount();
$total_pages = ceil($total_subs / PROFILES_PER_MINILIST); $total_pages = ceil($total_subs / PROFILES_PER_MINILIST);
if ($total_pages == 0) {
ActivityPubReturn::error('This user is not following anyone.');
}
if ($page > $total_pages) {
ActivityPubReturn::error("There are only {$total_pages} pages.");
}
/* Get followed' URLs */ /* Get followed' URLs */
$subs = array(); $subs = array();
while ($sub->fetch()) { while ($sub->fetch()) {
@ -101,17 +93,29 @@ class apActorFollowingAction extends ManagedAction
} }
$res = [ $res = [
'@context' => [ '@context' => [
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1", "https://w3id.org/security/v1",
], ],
'id' => "{$url}/following.json", 'id' => common_local_url('apActorFollowing', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''),
'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'), 'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'),
'totalItems' => $total_subs, 'totalItems' => $total_subs,
'next' => $page+1 > $total_pages ? null : "{$url}/followers.json?page=".($page+1 == 1 ? 2 : $page+1), 'orderedItems' => $subs
'prev' => $page == 1 ? null : "{$url}/followers.json?page=".($page-1 <= 0 ? 1 : $page-1), ];
'orderedItems' => $subs
]; if ($page == 0) {
$res['first'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'?page=1';
} else {
$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) {
$res['prev'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1);
}
}
ActivityPubReturn::answer($res); ActivityPubReturn::answer($res);
} }

View File

@ -33,9 +33,11 @@ $valid_object_types = ['Note'];
$res = $data->object; $res = $data->object;
if (!Activitypub_notice::validate_remote_notice((array) $res, $msg)) { try {
common_debug('ActivityPub Inbox Create Note: Invalid note: '.$msg); Activitypub_notice::validate_remote_notice((array) $res);
ActivityPubReturn::error($msg); } catch (Exception $e) {
common_debug('ActivityPub Inbox Create Note: Invalid note: '.$e->getMessage());
ActivityPubReturn::error($e->getMessage());
} }
$settings = []; $settings = [];

View File

@ -202,50 +202,40 @@ class Activitypub_notice extends Managed_DataObject
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @param Array $data * @param Array $data
* @param string $msg I/O
* @return boolean true in case of success * @return boolean true in case of success
* @throws Exception * @throws Exception
*/ */
public static function validate_remote_notice($data, &$msg) public static function validate_remote_notice($data)
{ {
if (!isset($data['attributedTo'])) { /*if (!isset($data['attributedTo'])) {
common_debug('ActivityPub Notice Validator: Rejected because attributedTo was not specified.'); common_debug('ActivityPub Notice Validator: Rejected because attributedTo was not specified.');
$msg = 'No attributedTo specified.'; throw new Exception('No attributedTo specified.');
return false;
} }
if (!isset($data['id'])) { if (!isset($data['id'])) {
common_debug('ActivityPub Notice Validator: Rejected because Object ID was not specified.'); common_debug('ActivityPub Notice Validator: Rejected because Object ID was not specified.');
$msg = 'Object ID not specified.'; throw new Exception('Object ID not specified.');
return false;
} elseif (!filter_var($data['id'], FILTER_VALIDATE_URL)) { } elseif (!filter_var($data['id'], FILTER_VALIDATE_URL)) {
common_debug('ActivityPub Notice Validator: Rejected because Object ID is invalid.'); common_debug('ActivityPub Notice Validator: Rejected because Object ID is invalid.');
$msg = 'Invalid Object ID.'; throw new Exception('Invalid Object ID.');
return false;
} }
if (!isset($data['type']) || $data['type'] !== 'Note') { if (!isset($data['type']) || $data['type'] !== 'Note') {
common_debug('ActivityPub Notice Validator: Rejected because of Type.'); common_debug('ActivityPub Notice Validator: Rejected because of Type.');
$msg = 'Invalid Object type.'; throw new Exception('Invalid Object type.');
return false;
} }
if (!isset($data['content'])) { if (!isset($data['content'])) {
common_debug('ActivityPub Notice Validator: Rejected because Content was not specified.'); common_debug('ActivityPub Notice Validator: Rejected because Content was not specified.');
$msg = 'Object content was not specified.'; throw new Exception('Object content was not specified.');
return false;
} }
if (!isset($data['url'])) { if (!isset($data['url'])) {
common_debug('ActivityPub Notice Validator: Rejected because Object URL was not specified.'); throw new Exception('Object URL was not specified.');
$msg = 'Object URL was not specified.';
return false;
} elseif (!filter_var($data['url'], FILTER_VALIDATE_URL)) { } elseif (!filter_var($data['url'], FILTER_VALIDATE_URL)) {
common_debug('ActivityPub Notice Validator: Rejected because Object URL is invalid.'); common_debug('ActivityPub Notice Validator: Rejected because Object URL is invalid.');
$msg = 'Invalid Object URL.'; throw new Exception('Invalid Object URL.');
return false;
} }
if (!isset($data['cc'])) { if (!isset($data['cc'])) {
common_debug('ActivityPub Notice Validator: Rejected because Object CC was not specified.'); common_debug('ActivityPub Notice Validator: Rejected because Object CC was not specified.');
$msg = 'Object CC was not specified.'; throw new Exception('Object CC was not specified.');
return false; }*/
}
return true; return true;
} }
} }

View File

@ -215,18 +215,9 @@ class Activitypub_explorer
$res = $this->temp_res; $res = $this->temp_res;
unset($this->temp_res); unset($this->temp_res);
} }
if (isset($res["orderedItems"])) { // It's a potential collection of actors!!! if (isset($res['type']) && $res['type'] === 'OrderedCollection' && isset ($res['first'])) { // It's a potential collection of actors!!!
common_debug('ActivityPub Explorer: Found a collection of actors for '.$url); common_debug('ActivityPub Explorer: Found a collection of actors for '.$url);
foreach ($res["orderedItems"] as $profile) { $this->travell_collection($res['first']);
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->_lookup($res["next"]);
}
return true; return true;
} elseif (self::validate_remote_response($res)) { } elseif (self::validate_remote_response($res)) {
common_debug('ActivityPub Explorer: Found a valid remote actor for '.$url); common_debug('ActivityPub Explorer: Found a valid remote actor for '.$url);
@ -408,4 +399,39 @@ class Activitypub_explorer
return false; return false;
} }
/**
* Allows the Explorer to transverse a collection of persons.
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param type $url
* @return boolean
*/
private function travel_collection($url)
{
$client = new HTTPClient();
$headers = array();
$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);
$res = json_decode($response->getBody(), true);
if (!isset($res['orderedItems']))
{
return false;
}
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->_lookup($res["next"]);
}
return true;
}
} }