[TESTS] Added unit tests

This commit is contained in:
Daniel 2020-10-16 01:07:01 +01:00 committed by Hugo Sales
parent d53fef09a8
commit 95f95d2dd8
Signed by untrusted user: someonewithpc
GPG Key ID: 7D0C7EAFC9D835A0
29 changed files with 716 additions and 477 deletions

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -42,27 +44,29 @@ class apActorFollowersAction extends ManagedAction
/** /**
* Handle the Followers Collection request * Handle the Followers Collection request
* *
* @return void
* @throws Exception * @throws Exception
*
* @return void
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
protected function handle() protected function handle()
{ {
try { try {
$profile = Profile::getByID($this->trimmed('id')); $profile = Profile::getByID($this->trimmed('id'));
$profile_id = $profile->getID(); $profile_id = $profile->getID();
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
if (!$profile->isLocal()) { if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.", 403); ActivityPubReturn::error('This is not a local user.', 403);
} }
if (!isset($_GET["page"])) { if (!isset($_GET['page'])) {
$page = 0; $page = 0;
} else { } else {
$page = intval($this->trimmed('page')); $page = (int) ($this->trimmed('page'));
} }
if ($page < 0) { if ($page < 0) {
@ -72,32 +76,32 @@ class apActorFollowersAction extends ManagedAction
$since = ($page - 1) * PROFILES_PER_MINILIST; $since = ($page - 1) * PROFILES_PER_MINILIST;
$limit = PROFILES_PER_MINILIST; $limit = PROFILES_PER_MINILIST;
/* Calculate total items */ // Calculate total items
$total_subs = Activitypub_profile::subscriberCount($profile); $total_subs = Activitypub_profile::subscriberCount($profile);
$total_pages = ceil($total_subs / PROFILES_PER_MINILIST); $total_pages = ceil($total_subs / PROFILES_PER_MINILIST);
$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' => common_local_url('apActorFollowers', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''), '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,
]; ];
if ($page == 0) { if ($page == 0) {
$res['first'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'?page=1'; $res['first'] = common_local_url('apActorFollowers', ['id' => $profile_id]) . '?page=1';
} else { } else {
$res['orderedItems'] = $this->generate_followers($profile, $since, $limit); $res['orderedItems'] = $this->generate_followers($profile, $since, $limit);
$res['partOf'] = common_local_url('apActorFollowers', ['id' => $profile_id]); $res['partOf'] = common_local_url('apActorFollowers', ['id' => $profile_id]);
if ($page+1 < $total_pages) { if ($page + 1 < $total_pages) {
$res['next'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'page='.($page+1 == 1 ? 2 : $page+1); $res['next'] = common_local_url('apActorFollowers', ['id' => $profile_id]) . 'page=' . ($page + 1 == 1 ? 2 : $page + 1);
} }
if ($page > 1) { if ($page > 1) {
$res['prev'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1); $res['prev'] = common_local_url('apActorFollowers', ['id' => $profile_id]) . '?page=' . ($page - 1 <= 0 ? 1 : $page - 1);
} }
} }
@ -108,10 +112,13 @@ class apActorFollowersAction extends ManagedAction
* Generates a list of stalkers for a given profile. * Generates a list of stalkers for a given profile.
* *
* @param Profile $profile * @param Profile $profile
* @param int $since * @param int $since
* @param int $limit * @param int $limit
* @return array of URIs *
* @throws Exception * @throws Exception
*
* @return array of URIs
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function generate_followers($profile, $since, $limit) public static function generate_followers($profile, $since, $limit)
@ -120,7 +127,7 @@ class apActorFollowersAction extends ManagedAction
try { try {
$sub = Activitypub_profile::getSubscribers($profile, $since, $limit); $sub = Activitypub_profile::getSubscribers($profile, $since, $limit);
/* Get followers' URLs */ // Get followers' URLs
foreach ($sub as $s) { foreach ($sub as $s) {
$subs[] = $s->getUri(); $subs[] = $s->getUri();
} }

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -42,27 +44,29 @@ class apActorFollowingAction extends ManagedAction
/** /**
* Handle the Following Collection request * Handle the Following Collection request
* *
* @return void
* @throws Exception * @throws Exception
*
* @return void
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
protected function handle() protected function handle()
{ {
try { try {
$profile = Profile::getByID($this->trimmed('id')); $profile = Profile::getByID($this->trimmed('id'));
$profile_id = $profile->getID(); $profile_id = $profile->getID();
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
if (!$profile->isLocal()) { if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.", 403); ActivityPubReturn::error('This is not a local user.', 403);
} }
if (!isset($_GET["page"])) { if (!isset($_GET['page'])) {
$page = 0; $page = 0;
} else { } else {
$page = intval($this->trimmed('page')); $page = (int) ($this->trimmed('page'));
} }
if ($page < 0) { if ($page < 0) {
@ -72,32 +76,32 @@ class apActorFollowingAction extends ManagedAction
$since = ($page - 1) * PROFILES_PER_MINILIST; $since = ($page - 1) * PROFILES_PER_MINILIST;
$limit = PROFILES_PER_MINILIST; $limit = PROFILES_PER_MINILIST;
/* Calculate total items */ // Calculate total items
$total_subs = Activitypub_profile::subscriptionCount($profile); $total_subs = Activitypub_profile::subscriptionCount($profile);
$total_pages = ceil($total_subs / PROFILES_PER_MINILIST); $total_pages = ceil($total_subs / PROFILES_PER_MINILIST);
$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' => common_local_url('apActorFollowing', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''), '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,
]; ];
if ($page == 0) { if ($page == 0) {
$res['first'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'?page=1'; $res['first'] = common_local_url('apActorFollowing', ['id' => $profile_id]) . '?page=1';
} else { } else {
$res['orderedItems'] = $this->generate_following($profile, $since, $limit); $res['orderedItems'] = $this->generate_following($profile, $since, $limit);
$res['partOf'] = common_local_url('apActorFollowing', ['id' => $profile_id]); $res['partOf'] = common_local_url('apActorFollowing', ['id' => $profile_id]);
if ($page+1 < $total_pages) { if ($page + 1 < $total_pages) {
$res['next'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'page='.($page+1 == 1 ? 2 : $page+1); $res['next'] = common_local_url('apActorFollowing', ['id' => $profile_id]) . 'page=' . ($page + 1 == 1 ? 2 : $page + 1);
} }
if ($page > 1) { if ($page > 1) {
$res['prev'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1); $res['prev'] = common_local_url('apActorFollowing', ['id' => $profile_id]) . '?page=' . ($page - 1 <= 0 ? 1 : $page - 1);
} }
} }
@ -108,10 +112,13 @@ class apActorFollowingAction extends ManagedAction
* Generates the list of those a given profile is stalking. * Generates the list of those a given profile is stalking.
* *
* @param Profile $profile * @param Profile $profile
* @param int $since * @param int $since
* @param int $limit * @param int $limit
* @return array of URIs *
* @throws Exception * @throws Exception
*
* @return array of URIs
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function generate_following($profile, $since, $limit) public function generate_following($profile, $since, $limit)
@ -120,7 +127,7 @@ class apActorFollowingAction extends ManagedAction
try { try {
$sub = Activitypub_profile::getSubscribed($profile, $since, $limit); $sub = Activitypub_profile::getSubscribed($profile, $since, $limit);
/* Get followed' URLs */ // Get followed' URLs
foreach ($sub as $s) { foreach ($sub as $s) {
$subs[] = $s->getUri(); $subs[] = $s->getUri();
} }

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -42,27 +44,29 @@ class apActorLikedAction extends ManagedAction
/** /**
* Handle the Liked Collection request * Handle the Liked Collection request
* *
* @return void
* @throws EmptyPkeyValueException * @throws EmptyPkeyValueException
* @throws ServerException * @throws ServerException
*
* @return void
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
protected function handle() protected function handle()
{ {
try { try {
$profile = Profile::getByID($this->trimmed('id')); $profile = Profile::getByID($this->trimmed('id'));
$profile_id = $profile->getID(); $profile_id = $profile->getID();
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
if (!$profile->isLocal()) { if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.", 403); ActivityPubReturn::error('This is not a local user.', 403);
} }
$limit = intval($this->trimmed('limit')); $limit = (int) ($this->trimmed('limit'));
$since_id = intval($this->trimmed('since_id')); $since_id = (int) ($this->trimmed('since_id'));
$max_id = intval($this->trimmed('max_id')); $max_id = (int) ($this->trimmed('max_id'));
$limit = empty($limit) ? 40 : $limit; // Default is 40 $limit = empty($limit) ? 40 : $limit; // Default is 40
$since_id = empty($since_id) ? null : $since_id; $since_id = empty($since_id) ? null : $since_id;
@ -75,20 +79,20 @@ class apActorLikedAction extends ManagedAction
$fave = $this->fetch_faves($profile_id, $limit, $since_id, $max_id); $fave = $this->fetch_faves($profile_id, $limit, $since_id, $max_id);
$faves = array(); $faves = [];
while ($fave->fetch()) { while ($fave->fetch()) {
$faves[] = $this->pretty_fave(clone ($fave)); $faves[] = $this->pretty_fave(clone $fave);
} }
$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' => common_local_url('apActorLiked', ['id' => $profile_id]), 'id' => common_local_url('apActorLiked', ['id' => $profile_id]),
'type' => 'OrderedCollection', 'type' => 'OrderedCollection',
'totalItems' => Fave::countByProfile($profile), 'totalItems' => Fave::countByProfile($profile),
'orderedItems' => $faves 'orderedItems' => $faves,
]; ];
ActivityPubReturn::answer($res); ActivityPubReturn::answer($res);
@ -99,16 +103,19 @@ class apActorLikedAction extends ManagedAction
* as a plugin answer * as a plugin answer
* *
* @param Fave $fave_object * @param Fave $fave_object
* @return array pretty array representating a Fave *
* @throws EmptyPkeyValueException * @throws EmptyPkeyValueException
* @throws ServerException * @throws ServerException
*
* @return array pretty array representating a Fave
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
protected function pretty_fave($fave_object) protected function pretty_fave($fave_object)
{ {
$res = [ $res = [
'created' => $fave_object->created, 'created' => $fave_object->created,
'object' => Activitypub_notice::notice_to_array(Notice::getByID($fave_object->notice_id)) 'object' => Activitypub_notice::notice_to_array(Notice::getByID($fave_object->notice_id)),
]; ];
return $res; return $res;
@ -118,10 +125,12 @@ class apActorLikedAction extends ManagedAction
* Fetch faves * Fetch faves
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*
* @param int $user_id * @param int $user_id
* @param int $limit * @param int $limit
* @param int $since_id * @param int $since_id
* @param int $max_id * @param int $max_id
*
* @return Fave fetchable fave collection * @return Fave fetchable fave collection
*/ */
private static function fetch_faves( private static function fetch_faves(

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -47,55 +49,55 @@ class apActorOutboxAction extends ManagedAction
protected function handle() protected function handle()
{ {
try { try {
$profile = Profile::getByID($this->trimmed('id')); $profile = Profile::getByID($this->trimmed('id'));
$profile_id = $profile->getID(); $profile_id = $profile->getID();
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error('Invalid Actor URI.', 404); ActivityPubReturn::error('Invalid Actor URI.', 404);
} }
if (!$profile->isLocal()) { if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.", 403); ActivityPubReturn::error('This is not a local user.', 403);
} }
if (!isset($_GET["page"])) { if (!isset($_GET['page'])) {
$page = 0; $page = 0;
} else { } else {
$page = intval($this->trimmed('page')); $page = (int) ($this->trimmed('page'));
} }
if ($page < 0) { if ($page < 0) {
ActivityPubReturn::error('Invalid page number.'); ActivityPubReturn::error('Invalid page number.');
} }
$since = ($page - 1) * PROFILES_PER_MINILIST; $since = ($page - 1) * PROFILES_PER_MINILIST;
$limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST; $limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST;
/* Calculate total items */ // Calculate total items
$total_notes = $profile->noticeCount(); $total_notes = $profile->noticeCount();
$total_pages = ceil($total_notes / PROFILES_PER_MINILIST); $total_pages = ceil($total_notes / PROFILES_PER_MINILIST);
$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' => common_local_url('apActorOutbox', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''), 'id' => common_local_url('apActorOutbox', ['id' => $profile_id]) . (($page != 0) ? '?page=' . $page : ''),
'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'), 'type' => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'),
'totalItems' => $total_notes 'totalItems' => $total_notes,
]; ];
if ($page == 0) { if ($page == 0) {
$res['first'] = common_local_url('apActorOutbox', ['id' => $profile_id]).'?page=1'; $res['first'] = common_local_url('apActorOutbox', ['id' => $profile_id]) . '?page=1';
} else { } else {
$res['orderedItems'] = $this->generate_outbox($profile); $res['orderedItems'] = $this->generate_outbox($profile);
$res['partOf'] = common_local_url('apActorOutbox', ['id' => $profile_id]); $res['partOf'] = common_local_url('apActorOutbox', ['id' => $profile_id]);
if ($page+1 < $total_pages) { if ($page + 1 < $total_pages) {
$res['next'] = common_local_url('apActorOutbox', ['id' => $profile_id]).'page='.($page+1 == 1 ? 2 : $page+1); $res['next'] = common_local_url('apActorOutbox', ['id' => $profile_id]) . 'page=' . ($page + 1 == 1 ? 2 : $page + 1);
} }
if ($page > 1) { if ($page > 1) {
$res['prev'] = common_local_url('apActorOutbox', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1); $res['prev'] = common_local_url('apActorOutbox', ['id' => $profile_id]) . '?page=' . ($page - 1 <= 0 ? 1 : $page - 1);
} }
} }
@ -106,17 +108,20 @@ class apActorOutboxAction extends ManagedAction
* Generates a list of people following given profile. * Generates a list of people following given profile.
* *
* @param Profile $profile * @param Profile $profile
* @return array of Notices *
* @throws EmptyPkeyValueException * @throws EmptyPkeyValueException
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws ServerException * @throws ServerException
*
* @return array of Notices
*
* @author Daniel Supernault <danielsupernault@gmail.com> * @author Daniel Supernault <danielsupernault@gmail.com>
*/ */
public function generate_outbox($profile) public function generate_outbox($profile)
{ {
/* Fetch Notices */ // Fetch Notices
$notices = []; $notices = [];
$notice = $profile->getNotices(); $notice = $profile->getNotices();
while ($notice->fetch()) { while ($notice->fetch()) {
$note = $notice; $note = $notice;

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -42,9 +44,11 @@ class apActorProfileAction extends ManagedAction
/** /**
* Handle the Actor Profile request * Handle the Actor Profile request
* *
* @return void
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws ServerException * @throws ServerException
*
* @return void
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
protected function handle() protected function handle()
@ -65,7 +69,7 @@ class apActorProfileAction extends ManagedAction
} }
if (!$profile->isLocal()) { if (!$profile->isLocal()) {
ActivityPubReturn::error("This is not a local user.", 403); ActivityPubReturn::error('This is not a local user.', 403);
} }
$res = Activitypub_profile::profile_to_array($profile); $res = Activitypub_profile::profile_to_array($profile);

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,19 +32,22 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class apInboxAction extends ManagedAction class apInboxAction extends ManagedAction
{ {
protected $needLogin = false; protected $needLogin = false;
protected $canPost = true; protected $canPost = true;
/** /**
* Handle the Inbox request * Handle the Inbox request
* *
* @return void
* @throws ServerException * @throws ServerException
*
* @return void
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
protected function handle() protected function handle()
@ -120,8 +124,8 @@ class apInboxAction extends ManagedAction
} catch (Exception $e) { } catch (Exception $e) {
ActivityPubReturn::error('Failed to updated remote actor information.'); ActivityPubReturn::error('Failed to updated remote actor information.');
} }
$actor_public_key = new Activitypub_rsa(); $actor_public_key = new Activitypub_rsa();
$actor_public_key = $actor_public_key->ensure_public_key($actor); $actor_public_key = $actor_public_key->ensure_public_key($actor);
list($verified, /*$headers*/) = HTTPSignature::verify($actor_public_key, $signatureData, $headers, $path, $body); list($verified, /*$headers*/) = HTTPSignature::verify($actor_public_key, $signatureData, $headers, $path, $body);
} }

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -132,10 +134,12 @@ class apNoticeAction extends ManagedAction
/** /**
* Handle the Notice request * Handle the Notice request
* *
* @return void
* @throws EmptyPkeyValueException * @throws EmptyPkeyValueException
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws ServerException * @throws ServerException
*
* @return void
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
protected function handle(): void protected function handle(): void
@ -143,9 +147,9 @@ class apNoticeAction extends ManagedAction
if (is_null($this->notice)) { if (is_null($this->notice)) {
ActivityPubReturn::error('Invalid Activity URI.', 404); ActivityPubReturn::error('Invalid Activity URI.', 404);
} }
if (!$this->notice->isLocal()) {
// We have no authority on the requested activity. if (!$notice->isLocal()) {
ActivityPubReturn::error("This is not a local activity.", 403); ActivityPubReturn::error('This is not a local notice.', 403);
} }
$res = Activitypub_notice::notice_to_array($this->notice); $res = Activitypub_notice::notice_to_array($this->notice);

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,13 +32,13 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class Activitypub_activityverb2 extends Managed_DataObject class Activitypub_activityverb2 extends Managed_DataObject
{ {
const FULL_LIST = const FULL_LIST = [
[
'Accept' => 'https://www.w3.org/ns/activitystreams#Accept', 'Accept' => 'https://www.w3.org/ns/activitystreams#Accept',
'TentativeAccept' => 'https://www.w3.org/ns/activitystreams#TentativeAccept', 'TentativeAccept' => 'https://www.w3.org/ns/activitystreams#TentativeAccept',
'Add' => 'https://www.w3.org/ns/activitystreams#Add', 'Add' => 'https://www.w3.org/ns/activitystreams#Add',
@ -65,25 +66,26 @@ class Activitypub_activityverb2 extends Managed_DataObject
'Block' => 'https://www.w3.org/ns/activitystreams#Block', 'Block' => 'https://www.w3.org/ns/activitystreams#Block',
'Flag' => 'https://www.w3.org/ns/activitystreams#Flag', 'Flag' => 'https://www.w3.org/ns/activitystreams#Flag',
'Dislike' => 'https://www.w3.org/ns/activitystreams#Dislike', 'Dislike' => 'https://www.w3.org/ns/activitystreams#Dislike',
'Question' => 'https://www.w3.org/ns/activitystreams#Question' 'Question' => 'https://www.w3.org/ns/activitystreams#Question',
]; ];
const KNOWN = const KNOWN = [
[
'Accept', 'Accept',
'Create', 'Create',
'Delete', 'Delete',
'Follow', 'Follow',
'Like', 'Like',
'Undo', 'Undo',
'Announce' 'Announce',
]; ];
/** /**
* Converts canonical into verb. * Converts canonical into verb.
* *
* @author GNU social * @author GNU social
*
* @param string $verb * @param string $verb
*
* @return string * @return string
*/ */
public static function canonical($verb) public static function canonical($verb)

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -33,6 +34,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -44,17 +46,20 @@ class Activitypub_explorer
* Shortcut function to get a single profile from its URL. * Shortcut function to get a single profile from its URL.
* *
* @param string $url * @param string $url
* @param bool $grab_online whether to try online grabbing, defaults to true * @param bool $grab_online whether to try online grabbing, defaults to true
* @return Profile *
* @throws HTTP_Request2_Exception Network issues * @throws HTTP_Request2_Exception Network issues
* @throws NoProfileException This won't happen * @throws NoProfileException This won't happen
* @throws Exception Invalid request * @throws Exception Invalid request
* @throws ServerException Error storing remote actor * @throws ServerException Error storing remote actor
*
* @return Profile
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function get_profile_from_url(string $url, bool $grab_online = true): Profile public static function get_profile_from_url(string $url, bool $grab_online = true): Profile
{ {
$discovery = new Activitypub_explorer(); $discovery = new self();
// Get valid Actor object // Get valid Actor object
$actor_profile = $discovery->lookup($url, $grab_online); $actor_profile = $discovery->lookup($url, $grab_online);
if (!empty($actor_profile)) { if (!empty($actor_profile)) {
@ -68,13 +73,16 @@ class Activitypub_explorer
* This function cleans the $this->discovered_actor_profiles array * This function cleans the $this->discovered_actor_profiles array
* so that there is no erroneous data * so that there is no erroneous data
* *
* @param string $url User's url * @param string $url User's url
* @param bool $grab_online whether to try online grabbing, defaults to true * @param bool $grab_online whether to try online grabbing, defaults to true
* @return array of Profile objects *
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws NoProfileException * @throws NoProfileException
* @throws Exception * @throws Exception
* @throws ServerException * @throws ServerException
*
* @return array of Profile objects
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function lookup(string $url, bool $grab_online = true) public function lookup(string $url, bool $grab_online = true)
@ -94,13 +102,16 @@ class Activitypub_explorer
* This is a recursive function that will accumulate the results on * This is a recursive function that will accumulate the results on
* $discovered_actor_profiles array * $discovered_actor_profiles array
* *
* @param string $url User's url * @param string $url User's url
* @param bool $grab_online whether to try online grabbing, defaults to true * @param bool $grab_online whether to try online grabbing, defaults to true
* @return array of Profile objects *
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
* @throws Exception * @throws Exception
*
* @return array of Profile objects
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function _lookup(string $url, bool $grab_online = true): array private function _lookup(string $url, bool $grab_online = true): array
@ -120,11 +131,14 @@ class Activitypub_explorer
* Get a local user profile from its URL and joins it on * Get a local user profile from its URL and joins it on
* $this->discovered_actor_profiles * $this->discovered_actor_profiles
* *
* @param string $uri Actor's uri * @param string $uri Actor's uri
* @param bool $online * @param bool $online
* @return bool success state *
* @throws NoProfileException * @throws NoProfileException
* @throws Exception * @throws Exception
*
* @return bool success state
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function grab_local_user(string $uri, bool $online = false): bool private function grab_local_user(string $uri, bool $online = false): bool
@ -153,9 +167,9 @@ class Activitypub_explorer
if ($online) { if ($online) {
common_debug('ActivityPub Explorer: Double-checking ' . $alias . ' to confirm it as a legitimate alias'); common_debug('ActivityPub Explorer: Double-checking ' . $alias . ' to confirm it as a legitimate alias');
$disco = new Discovery(); $disco = new Discovery();
$xrd = $disco->lookup($aprofile->getUri()); $xrd = $disco->lookup($aprofile->getUri());
$doublecheck_aliases = array_merge(array($xrd->subject), $xrd->aliases); $doublecheck_aliases = array_merge([$xrd->subject], $xrd->aliases);
if (in_array($uri, $doublecheck_aliases)) { if (in_array($uri, $doublecheck_aliases)) {
// the original URI is present, we're sure now! // the original URI is present, we're sure now!
@ -177,11 +191,11 @@ class Activitypub_explorer
common_debug('ActivityPub Explorer: Unable to find a local Aprofile for ' . $alias . ' - looking for a Profile instead.'); common_debug('ActivityPub Explorer: Unable to find a local Aprofile for ' . $alias . ' - looking for a Profile instead.');
// Well, maybe it is a pure blood? // Well, maybe it is a pure blood?
// Iff, we are in the same instance: // Iff, we are in the same instance:
$ACTIVITYPUB_BASE_ACTOR_URI = common_local_url('userbyid', ['id' => null], null, null, false, true); // @FIXME: Could this be too hardcoded? $ACTIVITYPUB_BASE_ACTOR_URI = common_local_url('userbyid', ['id' => null], null, null, false, true); // @FIXME: Could this be too hardcoded?
$ACTIVITYPUB_BASE_ACTOR_URI_length = strlen($ACTIVITYPUB_BASE_ACTOR_URI); $ACTIVITYPUB_BASE_ACTOR_URI_length = strlen($ACTIVITYPUB_BASE_ACTOR_URI);
if (substr($alias, 0, $ACTIVITYPUB_BASE_ACTOR_URI_length) === $ACTIVITYPUB_BASE_ACTOR_URI) { if (substr($alias, 0, $ACTIVITYPUB_BASE_ACTOR_URI_length) === $ACTIVITYPUB_BASE_ACTOR_URI) {
try { try {
$profile = Profile::getByID((int)substr($alias, $ACTIVITYPUB_BASE_ACTOR_URI_length)); $profile = Profile::getByID((int) substr($alias, $ACTIVITYPUB_BASE_ACTOR_URI_length));
common_debug('ActivityPub Explorer: Found a Profile for ' . $alias); common_debug('ActivityPub Explorer: Found a Profile for ' . $alias);
// We found something! // We found something!
$this->discovered_actor_profiles[] = $profile; $this->discovered_actor_profiles[] = $profile;
@ -208,19 +222,22 @@ class Activitypub_explorer
* $this->discovered_actor_profiles * $this->discovered_actor_profiles
* *
* @param string $url User's url * @param string $url User's url
* @return bool success state *
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
* @throws Exception * @throws Exception
*
* @return bool success state
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function grab_remote_user(string $url): bool private function grab_remote_user(string $url): bool
{ {
common_debug('ActivityPub Explorer: Trying to grab a remote actor for ' . $url); common_debug('ActivityPub Explorer: Trying to grab a remote actor for ' . $url);
$client = new HTTPClient(); $client = new HTTPClient();
$response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS); $response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS);
$res = json_decode($response->getBody(), true); $res = json_decode($response->getBody(), true);
if ($response->getStatus() == 410) { // If it was deleted if ($response->getStatus() == 410) { // If it was deleted
return true; // Nothing to add. return true; // Nothing to add.
} elseif (!$response->isOk()) { // If it is unavailable } elseif (!$response->isOk()) { // If it is unavailable
@ -251,29 +268,32 @@ class Activitypub_explorer
* Save remote user profile in local instance * Save remote user profile in local instance
* *
* @param array $res remote response * @param array $res remote response
* @return Profile remote Profile object *
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
* @throws Exception * @throws Exception
*
* @return Profile remote Profile object
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function store_profile(array $res): Profile private function store_profile(array $res): Profile
{ {
// ActivityPub Profile // ActivityPub Profile
$aprofile = new Activitypub_profile; $aprofile = new Activitypub_profile;
$aprofile->uri = $res['id']; $aprofile->uri = $res['id'];
$aprofile->nickname = $res['preferredUsername']; $aprofile->nickname = $res['preferredUsername'];
$aprofile->fullname = $res['name'] ?? null; $aprofile->fullname = $res['name'] ?? null;
$aprofile->bio = isset($res['summary']) ? substr(strip_tags($res['summary']), 0, 1000) : null; $aprofile->bio = isset($res['summary']) ? substr(strip_tags($res['summary']), 0, 1000) : null;
$aprofile->inboxuri = $res['inbox']; $aprofile->inboxuri = $res['inbox'];
$aprofile->sharedInboxuri = $res['endpoints']['sharedInbox'] ?? $res['inbox']; $aprofile->sharedInboxuri = $res['endpoints']['sharedInbox'] ?? $res['inbox'];
$aprofile->profileurl = $res['url'] ?? $aprofile->uri; $aprofile->profileurl = $res['url'] ?? $aprofile->uri;
$aprofile->do_insert(); $aprofile->do_insert();
$profile = $aprofile->local_profile(); $profile = $aprofile->local_profile();
// Public Key // Public Key
$apRSA = new Activitypub_rsa(); $apRSA = new Activitypub_rsa();
$apRSA->profile_id = $profile->getID(); $apRSA->profile_id = $profile->getID();
$apRSA->public_key = $res['publicKey']['publicKeyPem']; $apRSA->public_key = $res['publicKey']['publicKeyPem'];
$apRSA->store_keys(); $apRSA->store_keys();
@ -296,7 +316,9 @@ class Activitypub_explorer
* response is a valid profile or not * response is a valid profile or not
* *
* @param array $res remote response * @param array $res remote response
*
* @return bool success state * @return bool success state
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function validate_remote_response(array $res): bool public static function validate_remote_response(array $res): bool
@ -315,15 +337,17 @@ class Activitypub_explorer
* this hacky workaround (at least for now) * this hacky workaround (at least for now)
* *
* @param string $v URL * @param string $v URL
* @return bool|Activitypub_profile false if fails | Aprofile object if successful *
* @return Activitypub_profile|bool false if fails | Aprofile object if successful
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function get_aprofile_by_url(string $v) public static function get_aprofile_by_url(string $v)
{ {
$i = Managed_DataObject::getcached("Activitypub_profile", "uri", $v); $i = Managed_DataObject::getcached('Activitypub_profile', 'uri', $v);
if (empty($i)) { // false = cache miss if (empty($i)) { // false = cache miss
$i = new Activitypub_profile; $i = new Activitypub_profile;
$result = $i->get("uri", $v); $result = $i->get('uri', $v);
if ($result) { if ($result) {
// Hit! // Hit!
$i->encache(); $i->encache();
@ -338,14 +362,17 @@ class Activitypub_explorer
* Given a valid actor profile url returns its inboxes * Given a valid actor profile url returns its inboxes
* *
* @param string $url of Actor profile * @param string $url of Actor profile
* @return bool|array false if fails to validate the answer | array with inbox and shared inbox if successful *
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws Exception If an irregular error happens (status code, body format or GONE) * @throws Exception If an irregular error happens (status code, body format or GONE)
*
* @return array|bool false if fails to validate the answer | array with inbox and shared inbox if successful
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function get_actor_inboxes_uri(string $url) public static function get_actor_inboxes_uri(string $url)
{ {
$client = new HTTPClient(); $client = new HTTPClient();
$response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS); $response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS);
if ($response->getStatus() == 410) { // If it was deleted if ($response->getStatus() == 410) { // If it was deleted
throw new Exception('This actor is GONE.'); throw new Exception('This actor is GONE.');
@ -359,8 +386,8 @@ class Activitypub_explorer
} }
if (self::validate_remote_response($res)) { if (self::validate_remote_response($res)) {
return [ return [
'inbox' => $res['inbox'], 'inbox' => $res['inbox'],
'sharedInbox' => isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox'] 'sharedInbox' => isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox'],
]; ];
} }
@ -373,9 +400,12 @@ class Activitypub_explorer
* TODO: Should be in AProfile instead? * TODO: Should be in AProfile instead?
* *
* @param Profile $profile * @param Profile $profile
* @param string $url * @param string $url
* @return Avatar The Avatar we have on disk. (seldom used) *
* @throws Exception in various failure cases * @throws Exception in various failure cases
*
* @return Avatar The Avatar we have on disk. (seldom used)
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function update_avatar(Profile $profile, string $url): Avatar public static function update_avatar(Profile $profile, string $url): Avatar
@ -386,7 +416,7 @@ class Activitypub_explorer
$id = $profile->getID(); $id = $profile->getID();
$type = $imagefile->preferredType(); $type = $imagefile->preferredType();
$filename = Avatar::filename( $filename = Avatar::filename(
$id, $id,
image_type_to_extension($type), image_type_to_extension($type),
@ -412,31 +442,34 @@ class Activitypub_explorer
* Allows the Explorer to transverse a collection of persons. * Allows the Explorer to transverse a collection of persons.
* *
* @param string $url * @param string $url
* @return bool *
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
*
* @return bool
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function travel_collection(string $url): bool private function travel_collection(string $url): bool
{ {
$client = new HTTPClient(); $client = new HTTPClient();
$response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS); $response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS);
$res = json_decode($response->getBody(), true); $res = json_decode($response->getBody(), true);
if (!isset($res['orderedItems'])) { if (!isset($res['orderedItems'])) {
return false; return false;
} }
foreach ($res["orderedItems"] as $profile) { foreach ($res['orderedItems'] as $profile) {
if ($this->_lookup($profile) == false) { if ($this->_lookup($profile) == false) {
common_debug('ActivityPub Explorer: Found an invalid actor for ' . $profile); common_debug('ActivityPub Explorer: Found an invalid actor for ' . $profile);
// TODO: Invalid actor found, fallback to OStatus // TODO: Invalid actor found, fallback to OStatus
} }
} }
// Go through entire collection // Go through entire collection
if (!is_null($res["next"])) { if (!is_null($res['next'])) {
$this->travel_collection($res["next"]); $this->travel_collection($res['next']);
} }
return true; return true;
@ -447,13 +480,16 @@ class Activitypub_explorer
* profile updating and shall not be used for anything else) * profile updating and shall not be used for anything else)
* *
* @param string $url User's url * @param string $url User's url
* @return array|false If it is able to fetch, false if it's gone *
* @throws Exception Either network issues or unsupported Activity format * @throws Exception Either network issues or unsupported Activity format
*
* @return array|false If it is able to fetch, false if it's gone
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function get_remote_user_activity(string $url) public static function get_remote_user_activity(string $url)
{ {
$client = new HTTPClient(); $client = new HTTPClient();
$response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS); $response = $client->get($url, ACTIVITYPUB_HTTP_CLIENT_HEADERS);
// If it was deleted // If it was deleted
if ($response->getStatus() == 410) { if ($response->getStatus() == 410) {
@ -466,7 +502,7 @@ class Activitypub_explorer
common_debug('ActivityPub Explorer: Invalid JSON returned from given Actor URL: ' . $response->getBody()); common_debug('ActivityPub Explorer: Invalid JSON returned from given Actor URL: ' . $response->getBody());
throw new Exception('Given Actor URL didn\'t return a valid JSON.'); throw new Exception('Given Actor URL didn\'t return a valid JSON.');
} }
if (Activitypub_explorer::validate_remote_response($res)) { if (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);
return $res; return $res;
} }

View File

@ -14,22 +14,25 @@
* *
* @category Network * @category Network
* @package Nautilus * @package Nautilus
*
* @author Aaron Parecki <aaron@parecki.com> * @author Aaron Parecki <aaron@parecki.com>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://github.com/aaronpk/Nautilus/blob/master/app/ActivityPub/HTTPSignature.php *
* @see https://github.com/aaronpk/Nautilus/blob/master/app/ActivityPub/HTTPSignature.php
*/ */
class httpsignature
class HttpSignature
{ {
/** /**
* Sign a message with an Actor * Sign a message with an Actor
* *
* @param Profile $user Actor signing * @param Profile $user Actor signing
* @param string $url Inbox url * @param string $url Inbox url
* @param string|bool $body Data to sign (optional) * @param bool|string $body Data to sign (optional)
* @param array $addlHeaders Additional headers (optional) * @param array $addlHeaders Additional headers (optional)
* @return array Headers to be used in curl *
* @throws Exception Attempted to sign something that belongs to an Actor we don't own * @throws Exception Attempted to sign something that belongs to an Actor we don't own
*
* @return array Headers to be used in curl
*/ */
public static function sign(Profile $user, string $url, $body = false, array $addlHeaders = []): array public static function sign(Profile $user, string $url, $body = false, array $addlHeaders = []): array
{ {
@ -37,16 +40,16 @@ class HttpSignature
if ($body) { if ($body) {
$digest = self::_digest($body); $digest = self::_digest($body);
} }
$headers = self::_headersToSign($url, $digest); $headers = self::_headersToSign($url, $digest);
$headers = array_merge($headers, $addlHeaders); $headers = array_merge($headers, $addlHeaders);
$stringToSign = self::_headersToSigningString($headers); $stringToSign = self::_headersToSigningString($headers);
$signedHeaders = implode(' ', array_map('strtolower', array_keys($headers))); $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
$actor_private_key = new Activitypub_rsa(); $actor_private_key = new Activitypub_rsa();
// Intentionally unhandled exception, we want this to explode if that happens as it would be a bug // Intentionally unhandled exception, we want this to explode if that happens as it would be a bug
$actor_private_key = $actor_private_key->get_private_key($user); $actor_private_key = $actor_private_key->get_private_key($user);
$key = openssl_pkey_get_private($actor_private_key); $key = openssl_pkey_get_private($actor_private_key);
openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256); openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
$signature = base64_encode($signature); $signature = base64_encode($signature);
$signatureHeader = 'keyId="' . $user->getUri() . '#public-key' . '",headers="' . $signedHeaders . '",algorithm="rsa-sha256",signature="' . $signature . '"'; $signatureHeader = 'keyId="' . $user->getUri() . '#public-key' . '",headers="' . $signedHeaders . '",algorithm="rsa-sha256",signature="' . $signature . '"';
unset($headers['(request-target)']); unset($headers['(request-target)']);
$headers['Signature'] = $signatureHeader; $headers['Signature'] = $signatureHeader;
@ -56,6 +59,7 @@ class HttpSignature
/** /**
* @param mixed $body * @param mixed $body
*
* @return string * @return string
*/ */
private static function _digest($body): string private static function _digest($body): string
@ -68,9 +72,11 @@ class HttpSignature
/** /**
* @param string $url * @param string $url
* @param mixed $digest * @param mixed $digest
* @return array *
* @throws Exception * @throws Exception
*
* @return array
*/ */
protected static function _headersToSign(string $url, $digest = false): array protected static function _headersToSign(string $url, $digest = false): array
{ {
@ -78,11 +84,11 @@ class HttpSignature
$headers = [ $headers = [
'(request-target)' => 'post ' . parse_url($url, PHP_URL_PATH), '(request-target)' => 'post ' . parse_url($url, PHP_URL_PATH),
'Date' => $date->format('D, d M Y H:i:s \G\M\T'), 'Date' => $date->format('D, d M Y H:i:s \G\M\T'),
'Host' => parse_url($url, PHP_URL_HOST), 'Host' => parse_url($url, PHP_URL_HOST),
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, application/json', 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, application/json',
'User-Agent' => 'GNU social ActivityPub Plugin - '.GNUSOCIAL_ENGINE_URL, 'User-Agent' => 'GNU social ActivityPub Plugin - ' . GNUSOCIAL_ENGINE_URL,
'Content-Type' => 'application/activity+json' 'Content-Type' => 'application/activity+json',
]; ];
if ($digest) { if ($digest) {
@ -94,6 +100,7 @@ class HttpSignature
/** /**
* @param array $headers * @param array $headers
*
* @return string * @return string
*/ */
private static function _headersToSigningString(array $headers): string private static function _headersToSigningString(array $headers): string
@ -105,22 +112,24 @@ class HttpSignature
/** /**
* @param array $headers * @param array $headers
*
* @return array * @return array
*/ */
private static function _headersToCurlArray(array $headers): array private static function _headersToCurlArray(array $headers): array
{ {
return array_map(function ($k, $v) { return array_map(function ($k, $v) {
return "$k: $v"; return "{$k}: {$v}";
}, array_keys($headers), $headers); }, array_keys($headers), $headers);
} }
/** /**
* @param string $signature * @param string $signature
*
* @return array * @return array
*/ */
public static function parseSignatureHeader(string $signature): array public static function parseSignatureHeader(string $signature): array
{ {
$parts = explode(',', $signature); $parts = explode(',', $signature);
$signatureData = []; $signatureData = [];
foreach ($parts as $part) { foreach ($parts as $part) {
@ -131,19 +140,19 @@ class HttpSignature
if (!isset($signatureData['keyId'])) { if (!isset($signatureData['keyId'])) {
return [ return [
'error' => 'No keyId was found in the signature header. Found: ' . implode(', ', array_keys($signatureData)) 'error' => 'No keyId was found in the signature header. Found: ' . implode(', ', array_keys($signatureData)),
]; ];
} }
if (!filter_var($signatureData['keyId'], FILTER_VALIDATE_URL)) { if (!filter_var($signatureData['keyId'], FILTER_VALIDATE_URL)) {
return [ return [
'error' => 'keyId is not a URL: ' . $signatureData['keyId'] 'error' => 'keyId is not a URL: ' . $signatureData['keyId'],
]; ];
} }
if (!isset($signatureData['headers']) || !isset($signatureData['signature'])) { if (!isset($signatureData['headers']) || !isset($signatureData['signature'])) {
return [ return [
'error' => 'Signature is missing headers or signature parts' 'error' => 'Signature is missing headers or signature parts',
]; ];
} }
@ -156,14 +165,15 @@ class HttpSignature
* @param $inputHeaders * @param $inputHeaders
* @param $path * @param $path
* @param $body * @param $body
*
* @return array * @return array
*/ */
public static function verify($publicKey, $signatureData, $inputHeaders, $path, $body): array public static function verify($publicKey, $signatureData, $inputHeaders, $path, $body): array
{ {
// We need this because the used Request headers fields specified by Signature are in lower case. // We need this because the used Request headers fields specified by Signature are in lower case.
$headersContent = array_change_key_case($inputHeaders, CASE_LOWER); $headersContent = array_change_key_case($inputHeaders, CASE_LOWER);
$digest = 'SHA-256=' . base64_encode(hash('sha256', $body, true)); $digest = 'SHA-256=' . base64_encode(hash('sha256', $body, true));
$headersToSign = []; $headersToSign = [];
foreach (explode(' ', $signatureData['headers']) as $h) { foreach (explode(' ', $signatureData['headers']) as $h) {
if ($h == '(request-target)') { if ($h == '(request-target)') {
$headersToSign[$h] = 'post ' . $path; $headersToSign[$h] = 'post ' . $path;

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -43,15 +45,17 @@ class Activitypub_inbox_handler
/** /**
* Create a Inbox Handler to receive something from someone. * Create a Inbox Handler to receive something from someone.
* *
* @param array $activity Activity we are receiving * @param array $activity Activity we are receiving
* @param Profile $actor_profile Actor originating the activity * @param Profile $actor_profile Actor originating the activity
*
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function __construct($activity, $actor_profile = null) public function __construct($activity, $actor_profile = null)
{ {
$this->activity = $activity; $this->activity = $activity;
$this->object = $activity['object']; $this->object = $activity['object'];
// Validate Activity // Validate Activity
if (!$this->validate_activity()) { if (!$this->validate_activity()) {
@ -73,7 +77,9 @@ class Activitypub_inbox_handler
* Validates if a given Activity is valid. Throws exception if not. * Validates if a given Activity is valid. Throws exception if not.
* *
* @throws Exception if invalid * @throws Exception if invalid
*
* @return bool true if valid and acceptable, false if unsupported * @return bool true if valid and acceptable, false if unsupported
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function validate_activity(): bool private function validate_activity(): bool
@ -127,6 +133,7 @@ class Activitypub_inbox_handler
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function process() private function process()
@ -162,6 +169,7 @@ class Activitypub_inbox_handler
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function handle_accept() private function handle_accept()
@ -180,6 +188,7 @@ class Activitypub_inbox_handler
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function handle_accept_follow() private function handle_accept_follow()
@ -200,6 +209,7 @@ class Activitypub_inbox_handler
* Handles a Create Activity received by our inbox. * Handles a Create Activity received by our inbox.
* *
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function handle_create() private function handle_create()
@ -215,6 +225,7 @@ class Activitypub_inbox_handler
* Handle a Create Note Activity received by our inbox. * Handle a Create Note Activity received by our inbox.
* *
* @throws Exception * @throws Exception
*
* @author Bruno Casteleiro <brunoccast@fc.up.pt> * @author Bruno Casteleiro <brunoccast@fc.up.pt>
*/ */
private function handle_create_note() private function handle_create_note()
@ -229,6 +240,9 @@ class Activitypub_inbox_handler
/** /**
* Handles a Delete Activity received by our inbox. * Handles a Delete Activity received by our inbox.
* *
* @throws NoProfileException
* @throws Exception
*
* @author Bruno Casteleiro <brunoccast@fc.up.pt> * @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
@ -236,10 +250,10 @@ class Activitypub_inbox_handler
{ {
$object = $this->object; $object = $this->object;
if (is_string($object)) { if (is_string($object)) {
$client = new HTTPClient(); $client = new HTTPClient();
$response = $client->get($object, ACTIVITYPUB_HTTP_CLIENT_HEADERS); $response = $client->get($object, ACTIVITYPUB_HTTP_CLIENT_HEADERS);
$not_gone = $response->isOk(); $gone = !$response->isOk();
if ($not_gone) { // It's not gone, we're updating it. if (!$gone) { // It's not gone, we're updating it.
$object = json_decode($response->getBody(), true); $object = json_decode($response->getBody(), true);
switch ($object['type']) { switch ($object['type']) {
case 'Person': case 'Person':
@ -269,16 +283,37 @@ class Activitypub_inbox_handler
common_log(LOG_INFO, "Ignoring Delete activity, we do not understand for {$object['type']}."); common_log(LOG_INFO, "Ignoring Delete activity, we do not understand for {$object['type']}.");
} }
} }
} } else {
// We don't know the type of the deleted object :(
// Nor if it's gone or not.
try {
if (is_array($object)) {
$object = $object['id'];
}
$aprofile = Activitypub_profile::fromUri($object, false);
$res = Activitypub_explorer::get_remote_user_activity($object);
Activitypub_profile::update_profile($aprofile, $res);
return;
} catch (Exception $e) {
// Means this wasn't a profile
}
// IFF we reached this point, it either is gone or it's an array try {
// If it's gone, we don't know the type of the deleted object, we only have a Tombstone $client = new HTTPClient();
// If we were given an array, we don't know if it's Gone or not via status code... $response = $client->get($object, ACTIVITYPUB_HTTP_CLIENT_HEADERS);
// In both cases, we will want to fetch the ID and act on that as it is easier than updating the fields // If it was deleted
$object = $object['id'] ?? null; if ($response->getStatus() == 410) {
if (is_null($object)) { $notice = ActivityPubPlugin::grab_notice_from_url($object, false);
return; if ($notice instanceof Notice) {
} $notice->delete();
}
} else {
// We can't update a note's contents so, we'll ignore it for now...
}
return;
} catch (Exception $e) {
// Means we didn't have this note already
}
// Was it a profile? // Was it a profile?
try { try {
@ -316,6 +351,7 @@ class Activitypub_inbox_handler
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function handle_follow() private function handle_follow()
@ -327,6 +363,7 @@ class Activitypub_inbox_handler
* Handles a Like Activity received by our inbox. * Handles a Like Activity received by our inbox.
* *
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function handle_like() private function handle_like()
@ -342,6 +379,7 @@ class Activitypub_inbox_handler
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function handle_undo() private function handle_undo()
@ -364,6 +402,7 @@ class Activitypub_inbox_handler
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function handle_undo_follow() private function handle_undo_follow()
@ -387,6 +426,7 @@ class Activitypub_inbox_handler
* @throws AlreadyFulfilledException * @throws AlreadyFulfilledException
* @throws ServerException * @throws ServerException
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function handle_undo_like() private function handle_undo_like()
@ -399,6 +439,7 @@ class Activitypub_inbox_handler
* Handles a Announce Activity received by our inbox. * Handles a Announce Activity received by our inbox.
* *
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function handle_announce() private function handle_announce()

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,17 +42,19 @@ class Activitypub_accept
* Generates an ActivityPub representation of a Accept * Generates an ActivityPub representation of a Accept
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*
* @param array $object * @param array $object
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*/ */
public static function accept_to_array($object) public static function accept_to_array($object)
{ {
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'id' => common_root_url().'accept_follow_from_'.urlencode($object['actor']).'_to_'.urlencode($object['object']), 'id' => common_root_url() . 'accept_follow_from_' . urlencode($object['actor']) . '_to_' . urlencode($object['object']),
'type' => 'Accept', 'type' => 'Accept',
'actor' => $object['object'], 'actor' => $object['object'],
'object' => $object 'object' => $object,
]; ];
return $res; return $res;
} }
@ -59,8 +63,11 @@ class Activitypub_accept
* Verifies if a given object is acceptable for an Accept Activity. * Verifies if a given object is acceptable for an Accept Activity.
* *
* @param array $object * @param array $object
* @return bool *
* @throws Exception * @throws Exception
*
* @return bool
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function validate_object($object) public static function validate_object($object)
@ -75,7 +82,7 @@ class Activitypub_accept
case 'Follow': case 'Follow':
// Validate data // Validate data
if (!filter_var($object['object'], FILTER_VALIDATE_URL)) { if (!filter_var($object['object'], FILTER_VALIDATE_URL)) {
throw new Exception("Object is not a valid Object URI for Activity."); throw new Exception('Object is not a valid Object URI for Activity.');
} }
break; break;
default: default:

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,31 +42,30 @@ class Activitypub_announce
* Generates an ActivityPub representation of a Announce * Generates an ActivityPub representation of a Announce
* *
* @param Profile $actor * @param Profile $actor
* @param Notice $notice * @param Notice $notice
* @param Notice $repeat_of *
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function announce_to_array( public static function announce_to_array(Profile $actor, Notice $notice): array
Profile $actor, {
Notice $notice, $actor_uri = $actor->getUri();
Notice $repeat_of $notice_url = Activitypub_notice::getUrl($notice);
): array {
$actor_uri = $actor->getUri();
$to = [common_local_url('apActorFollowers', ['id' => $actor->getID()])]; $to = [common_local_url('apActorFollowers', ['id' => $actor->getID()])];
foreach ($notice->getAttentionProfiles() as $to_profile) { foreach ($notice->getAttentionProfiles() as $to_profile) {
$to[] = $to_profile->getUri(); $to[] = $to_profile->getUri();
} }
$cc[]= 'https://www.w3.org/ns/activitystreams#Public'; $cc[] = 'https://www.w3.org/ns/activitystreams#Public';
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'id' => Activitypub_notice::getUri($notice), 'id' => common_root_url() . 'share_from_' . urlencode($actor_uri) . '_to_' . urlencode($notice_url),
'type' => 'Announce', 'type' => 'Announce',
'actor' => $actor_uri, 'actor' => $actor_uri,
'object' => Activitypub_notice::getUri($repeat_of), 'object' => $notice_url,
'to' => $to, 'to' => $to,
'cc' => $cc, 'cc' => $cc,
]; ];

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,13 +42,15 @@ class Activitypub_attachment
* Generates a pretty array from an Attachment object * Generates a pretty array from an Attachment object
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*
* @param Attachment $attachment * @param Attachment $attachment
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*/ */
public static function attachment_to_array($attachment) public static function attachment_to_array($attachment)
{ {
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Document', 'type' => 'Document',
'mediaType' => $attachment->mimetype, 'mediaType' => $attachment->mimetype,
'url' => $attachment->getUrl(), 'url' => $attachment->getUrl(),
@ -55,10 +59,10 @@ class Activitypub_attachment
]; ];
// Image // Image
if (substr($res["mediaType"], 0, 5) == "image") { if (substr($res['mediaType'], 0, 5) == 'image') {
$res["meta"]= [ $res['meta'] = [
'width' => $attachment->width, 'width' => $attachment->width,
'height' => $attachment->height 'height' => $attachment->height,
]; ];
} }

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,23 +42,24 @@ class Activitypub_create
* Generates an ActivityPub representation of a Create * Generates an ActivityPub representation of a Create
* *
* @param string $actor * @param string $actor
* @param string $uri * @param array $object
* @param mixed $object * @param bool $directMessage whether it is a private Create activity or not
* @param bool $directMessage whether it is a private Create activity or not *
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function create_to_array(string $actor, string $uri, $object, bool $directMessage = false): array public static function create_to_array(string $actor, string $uri, $object, bool $directMessage = false): array
{ {
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $uri, 'id' => $object['id'] . '/create',
'type' => 'Create', 'type' => 'Create',
'directMessage' => $directMessage, 'directMessage' => $directMessage,
'to' => $object['to'], 'to' => $object['to'],
'cc' => $object['cc'], 'cc' => $object['cc'],
'actor' => $actor, 'actor' => $actor,
'object' => $object 'object' => $object,
]; ];
return $res; return $res;
} }
@ -65,8 +68,11 @@ class Activitypub_create
* Verifies if a given object is acceptable for a Create Activity. * Verifies if a given object is acceptable for a Create Activity.
* *
* @param array $object * @param array $object
* @return bool True if acceptable, false if valid but unsupported *
* @throws Exception if invalid * @throws Exception if invalid
*
* @return bool True if acceptable, false if valid but unsupported
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function validate_object($object): bool public static function validate_object($object): bool
@ -100,7 +106,9 @@ class Activitypub_create
* https://github.com/w3c/activitypub/issues/196#issuecomment-304958984 * https://github.com/w3c/activitypub/issues/196#issuecomment-304958984
* *
* @param array $activity received Create-Note activity * @param array $activity received Create-Note activity
*
* @return bool true if note is private, false otherwise * @return bool true if note is private, false otherwise
*
* @author Bruno casteleiro <brunoccast@fc.up.pt> * @author Bruno casteleiro <brunoccast@fc.up.pt>
*/ */
public static function isPrivateNote(array $activity): bool public static function isPrivateNote(array $activity): bool

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -39,35 +41,35 @@ class Activitypub_delete
/** /**
* Generates an ActivityStreams 2.0 representation of a Delete * Generates an ActivityStreams 2.0 representation of a Delete
* *
* @param Notice $object * @param string $actor actor URI
* @param string $object object URI
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function delete_to_array($object): array public static function delete_to_array($object): array
{ {
if ($object instanceof Notice) { $res = [
return Activitypub_notice::notice_to_array($object); '@context' => 'https://www.w3.org/ns/activitystreams',
} else if ($object instanceof Profile) { 'id' => $object . '/delete',
$actor_uri = $object->getUri(); 'type' => 'Delete',
return [ 'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'@context' => 'https://www.w3.org/ns/activitystreams', 'actor' => $actor,
'id' => $actor_uri . '#delete', 'object' => $object,
'type' => 'Delete', ];
'to' => ['https://www.w3.org/ns/activitystreams#Public'], return $res;
'actor' => $actor_uri,
'object' => $object
];
} else {
throw new InvalidArgumentException();
}
} }
/** /**
* Verifies if a given object is acceptable for a Delete Activity. * Verifies if a given object is acceptable for a Delete Activity.
* *
* @param array|string $object * @param array|string $object
* @return bool *
* @throws Exception * @throws Exception
*
* @return bool
*
* @author Bruno Casteleiro <brunoccast@fc.up.pt> * @author Bruno Casteleiro <brunoccast@fc.up.pt>
*/ */
public static function validate_object($object): bool public static function validate_object($object): bool
@ -80,7 +82,7 @@ class Activitypub_delete
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.');
} }
if ($object['type'] !== "Tombstone" && $object['type'] !== "Person") { 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'])) {

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,13 +42,15 @@ class Activitypub_error
* Generates a pretty error from a string * Generates a pretty error from a string
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*
* @param string $m * @param string $m
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*/ */
public static function error_message_to_array(string $m): array public static function error_message_to_array(string $m): array
{ {
return [ $res = [
'error'=> $m 'error' => $m,
]; ];
} }
} }

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,24 +42,26 @@ class Activitypub_follow
* Generates an ActivityPub representation of a subscription * Generates an ActivityPub representation of a subscription
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $actor *
* @param string $object * @param string $actor
* @param string|null $id Activity id, to be used when generating for an Accept Activity * @param string $object
* @param null|string $id Activity id, to be used when generating for an Accept Activity
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*/ */
public static function follow_to_array(string $actor, string $object, ?string $id = null): array public static function follow_to_array(string $actor, string $object, ?string $id = null): array
{ {
if ($id === null) { if ($id === null) {
$id = common_root_url().'follow_from_'.urlencode($actor).'_to_'.urlencode($object); $id = common_root_url() . 'follow_from_' . urlencode($actor) . '_to_' . urlencode($object);
} }
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $id, 'id' => $id,
'type' => 'Follow', 'type' => 'Follow',
'actor' => $actor, 'actor' => $actor,
'object' => $object 'object' => $object,
]; ];
return $res; return $res;
} }
@ -65,12 +69,14 @@ class Activitypub_follow
* Handles a Follow Activity received by our inbox. * Handles a Follow Activity received by our inbox.
* *
* @param Profile $actor_profile Remote Actor * @param Profile $actor_profile Remote Actor
* @param string $object Local Actor * @param string $object Local Actor
* @param string $id Activity id * @param string $id Activity id
*
* @throws AlreadyFulfilledException * @throws AlreadyFulfilledException
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws NoProfileException * @throws NoProfileException
* @throws ServerException * @throws ServerException
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function follow(Profile $actor_profile, string $object, string $id) public static function follow(Profile $actor_profile, string $object, string $id)
@ -85,13 +91,13 @@ class Activitypub_follow
if (!Subscription::exists($actor_profile, $object_profile)) { if (!Subscription::exists($actor_profile, $object_profile)) {
Subscription::start($actor_profile, $object_profile); Subscription::start($actor_profile, $object_profile);
Activitypub_profile::subscribeCacheUpdate($actor_profile, $object_profile); Activitypub_profile::subscribeCacheUpdate($actor_profile, $object_profile);
common_debug('ActivityPubPlugin: Accepted Follow request from '.$actor_profile->getUri().' to '.$object); common_debug('ActivityPubPlugin: Accepted Follow request from ' . $actor_profile->getUri() . ' to ' . $object);
} else { } else {
common_debug('ActivityPubPlugin: Received a repeated Follow request from '.$actor_profile->getUri().' to '.$object); common_debug('ActivityPubPlugin: Received a repeated Follow request from ' . $actor_profile->getUri() . ' to ' . $object);
} }
// Notify remote instance that we have accepted their request // Notify remote instance that we have accepted their request
common_debug('ActivityPubPlugin: Notifying remote instance that we have accepted their Follow request request from '.$actor_profile->getUri().' to '.$object); common_debug('ActivityPubPlugin: Notifying remote instance that we have accepted their Follow request request from ' . $actor_profile->getUri() . ' to ' . $object);
$postman = new Activitypub_postman($object_profile, [$actor_aprofile]); $postman = new Activitypub_postman($object_profile, [$actor_aprofile]);
$postman->accept_follow($id); $postman->accept_follow($id);
} }

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -39,8 +41,11 @@ class Activitypub_like
/** /**
* Generates an ActivityPub representation of a Like * Generates an ActivityPub representation of a Like
* *
* @author Diogo Cordeiro <diogo@fc.up.pt>
*
* @param string $actor Actor URI * @param string $actor Actor URI
* @param Notice $notice Notice URI * @param string $object Notice URI
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
@ -48,10 +53,10 @@ class Activitypub_like
{ {
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'id' => Activitypub_notice::getUri($notice), 'id' => common_root_url() . 'like_from_' . urlencode($actor) . '_to_' . urlencode($object),
'type' => 'Like', 'type' => 'Like',
'actor' => $actor, 'actor' => $actor,
'object' => Activitypub_notice::getUri($notice->getParent()), 'object' => $object,
]; ];
return $res; return $res;
} }

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,18 +42,20 @@ class Activitypub_mention_tag
* Generates an ActivityPub representation of a Mention Tag * Generates an ActivityPub representation of a Mention Tag
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*
* @param string $href Actor Uri * @param string $href Actor Uri
* @param string $name Mention name * @param string $name Mention name
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*/ */
public static function mention_tag_to_array_from_values(string $href, string $name): array public static function mention_tag_to_array_from_values(string $href, string $name): array
{ {
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
"type" => "Mention", 'type' => 'Mention',
"href" => $href, 'href' => $href,
"name" => $name 'name' => $name,
]; ];
return $res; return $res;
} }
} }

View File

@ -18,11 +18,11 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -38,7 +38,9 @@ class Activitypub_message
* Generates a pretty message from a Notice object * Generates a pretty message from a Notice object
* *
* @param Notice $message * @param Notice $message
*
* @return array array to be used in a response * @return array array to be used in a response
*
* @author Bruno Casteleiro <brunoccast@fc.up.pt> * @author Bruno Casteleiro <brunoccast@fc.up.pt>
*/ */
public static function message_to_array(Notice $message): array public static function message_to_array(Notice $message): array
@ -54,21 +56,21 @@ class Activitypub_message
$to = []; $to = [];
foreach ($message->getAttentionProfiles() as $to_profile) { foreach ($message->getAttentionProfiles() as $to_profile) {
$to[] = $href = $to_profile->getUri(); $to[] = $href = $to_profile->getUri();
$tags[] = Activitypub_mention_tag::mention_tag_to_array_from_values($href, $to_profile->getNickname().'@'.parse_url($href, PHP_URL_HOST)); $tags[] = Activitypub_mention_tag::mention_tag_to_array_from_values($href, $to_profile->getNickname() . '@' . parse_url($href, PHP_URL_HOST));
} }
$item = [ $item = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'id' => common_local_url('showmessage', ['message' => $message->getID()]), 'id' => common_local_url('showmessage', ['message' => $message->getID()]),
'type' => 'Note', 'type' => 'Note',
'published' => str_replace(' ', 'T', $message->created).'Z', 'published' => str_replace(' ', 'T', $message->created) . 'Z',
'attributedTo' => $from->getUri(), 'attributedTo' => $from->getUri(),
'to' => $to, 'to' => $to,
'cc' => [], 'cc' => [],
'content' => $message->getRendered(), 'content' => $message->getRendered(),
'attachment' => [], 'attachment' => [],
'tag' => $tags 'tag' => $tags,
]; ];
return $item; return $item;
@ -79,10 +81,13 @@ class Activitypub_message
* Returns created Notice. * Returns created Notice.
* *
* @author Bruno Casteleiro <brunoccast@fc.up.pt> * @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @param array $object *
* @param array $object
* @param Profile $actor_profile * @param Profile $actor_profile
* @return Notice *
* @throws Exception * @throws Exception
*
* @return Notice
*/ */
public static function create_message(array $object, Profile $actor_profile = null): Notice public static function create_message(array $object, Profile $actor_profile = null): Notice
{ {

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,16 +42,19 @@ class Activitypub_notice
* Generates a pretty notice from a Notice object * Generates a pretty notice from a Notice object
* *
* @param Notice $notice * @param Notice $notice
* @return array array to be used in a response *
* @throws EmptyPkeyValueException * @throws EmptyPkeyValueException
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws ServerException * @throws ServerException
* @throws Exception * @throws Exception
*
* @return array array to be used in a response
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function notice_to_array(Notice $notice): array public static function notice_to_array(Notice $notice): array
{ {
$profile = $notice->getProfile(); $profile = $notice->getProfile();
$attachments = []; $attachments = [];
foreach ($notice->attachments() as $attachment) { foreach ($notice->attachments() as $attachment) {
$attachments[] = Activitypub_attachment::attachment_to_array($attachment); $attachments[] = Activitypub_attachment::attachment_to_array($attachment);
@ -57,7 +62,7 @@ class Activitypub_notice
$tags = []; $tags = [];
foreach ($notice->getTags() as $tag) { foreach ($notice->getTags() as $tag) {
if ($tag != "") { // Hacky workaround to avoid stupid outputs if ($tag != '') { // Hacky workaround to avoid stupid outputs
$tags[] = Activitypub_tag::tag_to_array($tag); $tags[] = Activitypub_tag::tag_to_array($tag);
} }
} }
@ -75,46 +80,25 @@ class Activitypub_notice
} }
foreach ($notice->getAttentionProfiles() as $to_profile) { foreach ($notice->getAttentionProfiles() as $to_profile) {
$to[] = $href = $to_profile->getUri(); $to[] = $href = $to_profile->getUri();
$tags[] = Activitypub_mention_tag::mention_tag_to_array_from_values($href, $to_profile->getNickname() . '@' . parse_url($href, PHP_URL_HOST)); $tags[] = Activitypub_mention_tag::mention_tag_to_array_from_values($href, $to_profile->getNickname() . '@' . parse_url($href, PHP_URL_HOST));
} }
if (ActivityUtils::compareVerbs($notice->getVerb(), ActivityVerb::DELETE)) { $item = [
$item = [ '@context' => 'https://www.w3.org/ns/activitystreams',
'@context' => 'https://www.w3.org/ns/activitystreams', 'id' => self::getUrl($notice),
'id' => self::getUri($notice), 'type' => 'Note',
'type' => 'Delete', 'published' => str_replace(' ', 'T', $notice->getCreated()) . 'Z',
// XXX: A bit of ugly code here 'url' => self::getUrl($notice),
'object' => array_merge(Activitypub_tombstone::tombstone_to_array((int)substr(explode(':', $notice->getUri())[2], 9)), ['deleted' => str_replace(' ', 'T', $notice->getCreated()) . 'Z']), 'attributedTo' => $profile->getUri(),
'url' => $notice->getUrl(), 'to' => $to,
'actor' => $profile->getUri(), 'cc' => $cc,
'to' => $to, 'conversation' => $notice->getConversationUrl(),
'cc' => $cc, 'content' => $notice->getRendered(),
'conversationId' => $notice->getConversationUrl(false), 'isLocal' => $notice->isLocal(),
'conversationUrl' => $notice->getConversationUrl(), 'attachment' => $attachments,
'content' => $notice->getRendered(), 'tag' => $tags,
'isLocal' => $notice->isLocal(), ];
'attachment' => $attachments,
'tag' => $tags
];
} else { // Note
$item = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => self::note_uri($notice->getID()),
'type' => 'Note',
'published' => str_replace(' ', 'T', $notice->getCreated()) . 'Z',
'url' => $notice->getUrl(),
'attributedTo' => $profile->getUri(),
'to' => $to,
'cc' => $cc,
'conversationId' => $notice->getConversationUrl(false),
'conversationUrl' => $notice->getConversationUrl(),
'content' => $notice->getRendered(),
'isLocal' => $notice->isLocal(),
'attachment' => $attachments,
'tag' => $tags
];
}
// Is this a reply? // Is this a reply?
if (!empty($notice->reply_to)) { if (!empty($notice->reply_to)) {
@ -123,8 +107,8 @@ class Activitypub_notice
// Do we have a location for this notice? // Do we have a location for this notice?
try { try {
$location = Notice_location::locFromStored($notice); $location = Notice_location::locFromStored($notice);
$item['latitude'] = $location->lat; $item['latitude'] = $location->lat;
$item['longitude'] = $location->lon; $item['longitude'] = $location->lon;
} catch (Exception $e) { } catch (Exception $e) {
// Apparently no. // Apparently no.
@ -137,17 +121,20 @@ class Activitypub_notice
* Create a Notice via ActivityPub Note Object. * Create a Notice via ActivityPub Note Object.
* Returns created Notice. * Returns created Notice.
* *
* @param array $object * @param array $object
* @param Profile $actor_profile * @param Profile $actor_profile
* @param bool $directMessage * @param bool $directMessage
* @return Notice *
* @throws Exception * @throws Exception
*
* @return Notice
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function create_notice(array $object, Profile $actor_profile, bool $directMessage = false): Notice public static function create_notice(array $object, Profile $actor_profile, bool $directMessage = false): Notice
{ {
$id = $object['id']; // int $id = $object['id']; // int
$url = isset($object['url']) ? $object['url'] : $id; // string $url = isset($object['url']) ? $object['url'] : $id; // string
$content = $object['content']; // string $content = $object['content']; // string
// possible keys: ['inReplyTo', 'latitude', 'longitude'] // possible keys: ['inReplyTo', 'latitude', 'longitude']
@ -162,15 +149,15 @@ class Activitypub_notice
$settings['longitude'] = $object['longitude']; $settings['longitude'] = $object['longitude'];
} }
$act = new Activity(); $act = new Activity();
$act->verb = ActivityVerb::POST; $act->verb = ActivityVerb::POST;
$act->time = time(); $act->time = time();
$act->actor = $actor_profile->asActivityObject(); $act->actor = $actor_profile->asActivityObject();
$act->context = new ActivityContext(); $act->context = new ActivityContext();
$options = ['source' => 'ActivityPub', $options = ['source' => 'ActivityPub',
'uri' => $id, 'uri' => $id,
'url' => $url, 'url' => $url,
'is_local' => self::getNotePolicyType($object, $actor_profile)]; 'is_local' => self::getNotePolicyType($object, $actor_profile), ];
if ($directMessage) { if ($directMessage) {
$options['scope'] = Notice::MESSAGE_SCOPE; $options['scope'] = Notice::MESSAGE_SCOPE;
@ -179,8 +166,8 @@ class Activitypub_notice
// Is this a reply? // Is this a reply?
if (isset($settings['inReplyTo'])) { if (isset($settings['inReplyTo'])) {
try { try {
$inReplyTo = ActivityPubPlugin::grab_notice_from_url($settings['inReplyTo']); $inReplyTo = ActivityPubPlugin::grab_notice_from_url($settings['inReplyTo']);
$act->context->replyToID = $inReplyTo->getUri(); $act->context->replyToID = $inReplyTo->getUri();
$act->context->replyToUrl = $inReplyTo->getUrl(); $act->context->replyToUrl = $inReplyTo->getUrl();
} catch (Exception $e) { } catch (Exception $e) {
// It failed to grab, maybe we got this note from another source // It failed to grab, maybe we got this note from another source
@ -203,7 +190,7 @@ class Activitypub_notice
} }
} }
$mentions_profiles = []; $mentions_profiles = [];
$discovery = new Activitypub_explorer; $discovery = new Activitypub_explorer;
foreach ($mentions as $mention) { foreach ($mentions as $mention) {
try { try {
$mentioned_profile = $discovery->lookup($mention); $mentioned_profile = $discovery->lookup($mention);
@ -240,32 +227,10 @@ class Activitypub_notice
&& $attachment['type'] === 'Document' && $attachment['type'] === 'Document'
&& array_key_exists('url', $attachment)) { && array_key_exists('url', $attachment)) {
try { try {
$file = new File(); // throws exception on failure
$file->url = $attachment['url']; $attachment = MediaFile::fromUrl($attachment['url'], $actor_profile, $attachment['name']);
$file->title = array_key_exists('type', $attachment) ? $attachment['name'] : null; $act->enclosures[] = $attachment->getEnclosure();
if (array_key_exists('type', $attachment)) { $attachments[] = $attachment;
$file->mimetype = $attachment['mediaType'];
} else {
$http = new HTTPClient();
common_debug(
'Performing HEAD request for incoming activity '
. 'to avoid unnecessarily downloading too '
. 'large files. URL: ' . $file->url
);
$head = $http->head($file->url);
$headers = $head->getHeader();
$headers = array_change_key_case($headers, CASE_LOWER);
if (array_key_exists('content-type', $headers)) {
$file->mimetype = $headers['content-type'];
} else {
continue;
}
if (array_key_exists('content-length', $headers)) {
$file->size = $headers['content-length'];
}
}
$file->saveFile();
$attachments[] = $file;
} catch (Exception $e) { } catch (Exception $e) {
// Whatever. // Whatever.
continue; continue;
@ -274,9 +239,9 @@ class Activitypub_notice
} }
} }
$actobj = new ActivityObject(); $actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE; $actobj->type = ActivityObject::NOTE;
$actobj->content = strip_tags($content, '<p><b><i><u><a><ul><ol><li><br>'); $actobj->content = strip_tags($content, '<p><b><i><u><a><ul><ol><li>');
// Finally add the activity object to our activity // Finally add the activity object to our activity
$act->objects[] = $actobj; $act->objects[] = $actobj;
@ -284,8 +249,8 @@ class Activitypub_notice
$note = Notice::saveActivity($act, $actor_profile, $options); $note = Notice::saveActivity($act, $actor_profile, $options);
// Attachments (last part) // Attachments (last part)
foreach ($attachments as $file) { foreach ($attachments as $attachment) {
File_to_post::processNew($file, $note); $attachment->attachToNotice($note);
} }
return $note; return $note;
@ -295,8 +260,11 @@ class Activitypub_notice
* Validates a note. * Validates a note.
* *
* @param array $object * @param array $object
* @return bool false if unacceptable for GS but valid ActivityPub object *
* @throws Exception if invalid ActivityPub object * @throws Exception if invalid ActivityPub object
*
* @return bool false if unacceptable for GS but valid ActivityPub object
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function validate_note(array $object): bool public static function validate_note(array $object): bool
@ -316,7 +284,7 @@ class Activitypub_notice
common_debug('ActivityPub Notice Validator: Rejected because Object URL is invalid.'); common_debug('ActivityPub Notice Validator: Rejected because Object URL is invalid.');
throw new Exception('Invalid Object URL.'); throw new Exception('Invalid Object URL.');
} }
if (!(isset($object['to']) && isset($object['cc']))) { if (!(isset($object['to'], $object['cc']))) {
common_debug('ActivityPub Notice Validator: Rejected because either Object CC or TO wasn\'t specified.'); common_debug('ActivityPub Notice Validator: Rejected because either Object CC or TO wasn\'t specified.');
throw new Exception('Either Object CC or TO wasn\'t specified.'); throw new Exception('Either Object CC or TO wasn\'t specified.');
} }
@ -331,9 +299,12 @@ class Activitypub_notice
* Get the original representation URL of a given notice. * Get the original representation URL of a given notice.
* *
* @param Notice $notice notice from which to retrieve the URL * @param Notice $notice notice from which to retrieve the URL
* @return string URL *
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws Exception * @throws Exception
*
* @return string URL
*
* @author Bruno Casteleiro <brunoccast@fc.up.pt> * @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @see note_uri when it's not a generic activity but a object type note * @see note_uri when it's not a generic activity but a object type note
*/ */
@ -362,9 +333,11 @@ class Activitypub_notice
/** /**
* Extract note policy type from note targets. * Extract note policy type from note targets.
* *
* @param array $note received Note * @param array $note received Note
* @param Profile $actor_profile Note author * @param Profile $actor_profile Note author
*
* @return int Notice policy type * @return int Notice policy type
*
* @author Bruno Casteleiro <brunoccast@fc.up.pt> * @author Bruno Casteleiro <brunoccast@fc.up.pt>
*/ */
public static function getNotePolicyType(array $note, Profile $actor_profile): int public static function getNotePolicyType(array $note, Profile $actor_profile): int

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,15 +42,17 @@ class Activitypub_reject
* Generates an ActivityPub representation of a Reject * Generates an ActivityPub representation of a Reject
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*
* @param array $object * @param array $object
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*/ */
public static function reject_to_array(array $object): array public static function reject_to_array(array $object): array
{ {
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Reject', 'type' => 'Reject',
'object' => $object 'object' => $object,
]; ];
return $res; return $res;
} }

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,15 +42,17 @@ class Activitypub_tag
* Generates a pretty tag from a Tag object * Generates a pretty tag from a Tag object
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $tag *
* @param array Tag $tag
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*/ */
public static function tag_to_array(string $tag): array public static function tag_to_array(string $tag): array
{ {
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'name' => $tag, 'name' => $tag,
'url' => common_local_url('tag', ['tag' => $tag]), 'url' => common_local_url('tag', ['tag' => $tag]),
]; ];
return $res; return $res;
} }

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -31,6 +32,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -40,17 +42,19 @@ class Activitypub_undo
* Generates an ActivityPub representation of a Undo * Generates an ActivityPub representation of a Undo
* *
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*
* @param array $object * @param array $object
*
* @return array pretty array to be used in a response * @return array pretty array to be used in a response
*/ */
public static function undo_to_array(array $object): array public static function undo_to_array(array $object): array
{ {
$res = [ $res = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $object['id'] . '#undo', 'id' => $object['id'] . '/undo',
'type' => 'Undo', 'type' => 'Undo',
'actor' => $object['actor'], 'actor' => $object['actor'],
'object' => $object 'object' => $object,
]; ];
return $res; return $res;
} }
@ -59,8 +63,11 @@ class Activitypub_undo
* Verifies if a given object is acceptable for a Undo Activity. * Verifies if a given object is acceptable for a Undo Activity.
* *
* @param array $object * @param array $object
* @return bool *
* @throws Exception * @throws Exception
*
* @return bool
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public static function validate_object(array $object): bool public static function validate_object(array $object): bool

View File

@ -18,12 +18,13 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/** /**
@ -34,6 +35,7 @@ defined('GNUSOCIAL') || die();
* *
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -50,14 +52,16 @@ class Activitypub_postman
* Create a postman to deliver something to someone * Create a postman to deliver something to someone
* *
* @param Profile $from sender Profile * @param Profile $from sender Profile
* @param array $to receiver AProfiles * @param array $to receiver Profiles
*
* @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;
$this->actor_uri = $this->actor->getUri(); $this->actor_uri = $this->actor->getUri();
@ -82,21 +86,24 @@ class Activitypub_postman
/** /**
* Send something to remote instance * Send something to remote instance
* *
* @param string $data request body * @param string $data request body
* @param string $inbox url of remote inbox * @param string $inbox url of remote inbox
* @param string $method request method * @param string $method request method
* @return GNUsocial_HTTPResponse *
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws HTTP_Request2_LogicException * @throws Exception
*
* @return GNUsocial_HTTPResponse
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function send($data, $inbox, $method = 'POST') public function send($data, $inbox, $method = 'POST')
{ {
common_debug('ActivityPub Postman: Delivering '.$data.' to '.$inbox); common_debug('ActivityPub Postman: Delivering ' . $data . ' to ' . $inbox);
$headers = HttpSignature::sign($this->actor, $inbox, $data); $headers = HttpSignature::sign($this->actor, $inbox, $data);
common_debug('ActivityPub Postman: Delivery headers were: '.print_r($headers, true)); common_debug('ActivityPub Postman: Delivery headers were: ' . print_r($headers, true));
$this->client->setBody($data); $this->client->setBody($data);
@ -108,25 +115,27 @@ class Activitypub_postman
$response = $this->client->get($inbox, $headers); $response = $this->client->get($inbox, $headers);
break; break;
default: default:
throw new Exception("Unsupported request method for postman."); throw new Exception('Unsupported request method for postman.');
} }
common_debug('ActivityPub Postman: Delivery result with status code '.$response->getStatus().': '.$response->getBody()); common_debug('ActivityPub Postman: Delivery result with status code ' . $response->getStatus() . ': ' . $response->getBody());
return $response; return $response;
} }
/** /**
* Send a follow notification to remote instance * Send a follow notification to remote instance
* *
* @return bool
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws Exception * @throws Exception
*
* @return bool
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function follow() public function follow()
{ {
$data = Activitypub_follow::follow_to_array($this->actor_uri, $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 = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox());
$res_body = json_decode($res->getBody(), true); $res_body = json_decode($res->getBody(), true);
if ($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409) { if ($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409) {
@ -137,18 +146,20 @@ class Activitypub_postman
throw new Exception($res_body['error']); throw new Exception($res_body['error']);
} }
throw new Exception("An unknown error occurred."); throw new Exception('An unknown error occurred.');
} }
/** /**
* Send a Undo Follow notification to remote instance * Send a Undo Follow notification to remote instance
* *
* @return bool
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws Exception * @throws Exception
* @throws Exception * @throws Exception
* @throws Exception * @throws Exception
* @throws Exception * @throws Exception
*
* @return bool
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function undo_follow() public function undo_follow()
@ -157,9 +168,9 @@ class Activitypub_postman
Activitypub_follow::follow_to_array( Activitypub_follow::follow_to_array(
$this->actor_uri, $this->actor_uri,
$this->to[0]->getUrl() $this->to[0]->getUrl()
) )
); );
$res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox()); $res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox());
$res_body = json_decode($res->getBody(), true); $res_body = json_decode($res->getBody(), true);
if ($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409) { if ($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409) {
@ -171,16 +182,19 @@ class Activitypub_postman
if (isset($res_body['error'])) { if (isset($res_body['error'])) {
throw new Exception($res_body['error']); throw new Exception($res_body['error']);
} }
throw new Exception("An unknown error occurred."); throw new Exception('An unknown error occurred.');
} }
/** /**
* Send a Accept Follow notification to remote instance * Send a Accept Follow notification to remote instance
* *
* @param string $id Follow activity id * @param string $id Follow activity id
* @return bool *
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws Exception Description of HTTP Response error or generic error message. * @throws Exception Description of HTTP Response error or generic error message.
*
* @return bool
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function accept_follow(string $id): bool public function accept_follow(string $id): bool
@ -190,9 +204,9 @@ class Activitypub_postman
$this->to[0]->getUrl(), $this->to[0]->getUrl(),
$this->actor_uri, $this->actor_uri,
$id $id
) )
); );
$res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox()); $res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox());
$res_body = json_decode($res->getBody(), true); $res_body = json_decode($res->getBody(), true);
if ($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409) { if ($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409) {
@ -203,16 +217,18 @@ class Activitypub_postman
if (isset($res_body['error'])) { if (isset($res_body['error'])) {
throw new Exception($res_body['error']); throw new Exception($res_body['error']);
} }
throw new Exception("An unknown error occurred."); throw new Exception('An unknown error occurred.');
} }
/** /**
* Send a Like notification to remote instances holding the notice * Send a Like notification to remote instances holding the notice
* *
* @param Notice $notice * @param Notice $notice
*
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function like(Notice $notice): void public function like(Notice $notice): void
@ -239,7 +255,7 @@ class Activitypub_postman
} }
if (!empty($errors)) { if (!empty($errors)) {
common_log(LOG_ERR, sizeof($errors) . " instance/s failed to handle the like activity!"); common_log(LOG_ERR, sizeof($errors) . ' instance/s failed to handle the like activity!');
} }
} }
@ -247,9 +263,11 @@ class Activitypub_postman
* Send a Undo Like notification to remote instances holding the notice * Send a Undo Like notification to remote instances holding the notice
* *
* @param Notice $notice * @param Notice $notice
*
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function undo_like($notice) public function undo_like($notice)
@ -279,7 +297,7 @@ class Activitypub_postman
} }
if (!empty($errors)) { if (!empty($errors)) {
common_log(LOG_ERR, sizeof($errors) . " instance/s failed to handle the undo-like activity!"); common_log(LOG_ERR, sizeof($errors) . ' instance/s failed to handle the undo-like activity!');
} }
} }
@ -287,10 +305,12 @@ class Activitypub_postman
* Send a Create notification to remote instances * Send a Create notification to remote instances
* *
* @param Notice $notice * @param Notice $notice
*
* @throws EmptyPkeyValueException * @throws EmptyPkeyValueException
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws ServerException * @throws ServerException
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function create_note($notice) public function create_note($notice)
@ -319,7 +339,7 @@ class Activitypub_postman
} }
if (!empty($errors)) { if (!empty($errors)) {
common_log(LOG_ERR, sizeof($errors) . " instance/s failed to handle the create-note activity!"); common_log(LOG_ERR, sizeof($errors) . ' instance/s failed to handle the create-note activity!');
} }
} }
@ -327,6 +347,7 @@ class Activitypub_postman
* Send a Create direct-notification to remote instances * Send a Create direct-notification to remote instances
* *
* @param Notice $message * @param Notice $message
*
* @author Bruno Casteleiro <brunoccast@fc.up.pt> * @author Bruno Casteleiro <brunoccast@fc.up.pt>
*/ */
public function create_direct_note(Notice $message) public function create_direct_note(Notice $message)
@ -346,13 +367,12 @@ class Activitypub_postman
if (!($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409)) { if (!($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409)) {
$res_body = json_decode($res->getBody(), true); $res_body = json_decode($res->getBody(), true);
$errors[] = isset($res_body['error']) ? $errors[] = isset($res_body['error']) ?
$res_body['error'] : "An unknown error occurred."; $res_body['error'] : 'An unknown error occurred.';
$to_failed[$inbox] = $message;
} }
} }
if (!empty($errors)) { if (!empty($errors)) {
common_log(LOG_ERR, sizeof($errors) . " instance/s failed to handle the create-note activity!"); common_log(LOG_ERR, sizeof($errors) . ' instance/s failed to handle the create-note activity!');
} }
} }
@ -360,8 +380,10 @@ class Activitypub_postman
* Send a Announce notification to remote instances * Send a Announce notification to remote instances
* *
* @param Notice $notice * @param Notice $notice
* @param Notice $repeat_of *
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function announce(Notice $notice, Notice $repeat_of): void public function announce(Notice $notice, Notice $repeat_of): void
@ -388,7 +410,7 @@ class Activitypub_postman
} }
if (!empty($errors)) { if (!empty($errors)) {
common_log(LOG_ERR, sizeof($errors) . " instance/s failed to handle the announce activity!"); common_log(LOG_ERR, sizeof($errors) . ' instance/s failed to handle the announce activity!');
} }
} }
@ -396,16 +418,18 @@ class Activitypub_postman
* Send a Delete notification to remote instances holding the notice * Send a Delete notification to remote instances holding the notice
* *
* @param Notice $notice * @param Notice $notice
*
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws Exception * @throws Exception
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
public function delete_note($notice) public function delete_note($notice)
{ {
$data = Activitypub_delete::delete_to_array($notice); $data = Activitypub_delete::delete_to_array($notice);
$errors = []; $errors = [];
$data = json_encode($data, JSON_UNESCAPED_SLASHES); $data = json_encode($data, JSON_UNESCAPED_SLASHES);
foreach ($this->to_inbox() as $inbox) { foreach ($this->to_inbox() as $inbox) {
try { try {
$res = $this->send($data, $inbox); $res = $this->send($data, $inbox);
@ -427,8 +451,12 @@ class Activitypub_postman
/** /**
* Send a Delete notification to remote followers of some deleted profile * Send a Delete notification to remote followers of some deleted profile
* *
* @param Profile $deleted_profile * @param Notice $notice
*
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
* @throws InvalidUrlException
* @throws Exception
*
* @author Bruno Casteleiro <brunoccast@fc.up.pt> * @author Bruno Casteleiro <brunoccast@fc.up.pt>
*/ */
public function delete_profile(Profile $deleted_profile) public function delete_profile(Profile $deleted_profile)
@ -453,7 +481,7 @@ class Activitypub_postman
} }
if (!empty($errors)) { if (!empty($errors)) {
common_log(LOG_ERR, sizeof($errors) . " instance/s failed to handle the delete_profile activity!"); common_log(LOG_ERR, sizeof($errors) . ' instance/s failed to handle the delete_profile activity!');
} }
} }
@ -461,8 +489,11 @@ class Activitypub_postman
* Clean list of inboxes to deliver messages * Clean list of inboxes to deliver messages
* *
* @param bool $actorFollowers whether to include the actor's follower collection * @param bool $actorFollowers whether to include the actor's follower collection
* @return array To Inbox URLs *
* @throws Exception * @throws Exception
*
* @return array To Inbox URLs
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
*/ */
private function to_inbox(bool $actorFollowers = true): array private function to_inbox(bool $actorFollowers = true): array
@ -472,7 +503,7 @@ class Activitypub_postman
$followers = apActorFollowersAction::generate_followers($this->actor, 0, null); $followers = apActorFollowersAction::generate_followers($this->actor, 0, null);
foreach ($followers as $sub) { foreach ($followers as $sub) {
try { try {
$this->to[]= Activitypub_profile::from_profile($discovery->lookup($sub)[0]); $this->to[] = Activitypub_profile::from_profile($discovery->lookup($sub)[0]);
} catch (Exception $e) { } catch (Exception $e) {
// Not an ActivityPub Remote Follower, let it go // Not an ActivityPub Remote Follower, let it go
} }

View File

@ -19,17 +19,18 @@
* ActivityPub implementation for GNU social * ActivityPub implementation for GNU social
* *
* @package GNUsocial * @package GNUsocial
*
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>
* @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://www.gnu.org/software/social/ *
* @see http://www.gnu.org/software/social/
*/ */
define('INSTALLDIR', dirname(__DIR__, 3)); define('INSTALLDIR', dirname(__DIR__, 3));
define('PUBLICDIR', INSTALLDIR . DIRECTORY_SEPARATOR . 'public'); define('PUBLICDIR', INSTALLDIR . DIRECTORY_SEPARATOR . 'public');
$shortoptions = 'u:af'; $shortoptions = 'u:af';
$longoptions = ['uri=', 'all', 'force']; $longoptions = ['uri=', 'all', 'force'];
$helptext = <<<END_OF_HELP $helptext = <<<END_OF_HELP
update_activitypub_profiles.php [options] update_activitypub_profiles.php [options]
@ -42,7 +43,7 @@ you have no backup.
END_OF_HELP; END_OF_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc'; require_once INSTALLDIR . '/scripts/commandline.inc';
if (!have_option('q', 'quiet')) { if (!have_option('q', 'quiet')) {
echo "ActivityPub Profiles updater will now start!\n"; echo "ActivityPub Profiles updater will now start!\n";
@ -50,15 +51,15 @@ if (!have_option('q', 'quiet')) {
} }
if (have_option('u', 'uri')) { if (have_option('u', 'uri')) {
$uri = get_option_value('u', 'uri'); $uri = get_option_value('u', 'uri');
$user = Activitypub_profile::from_profile(Activitypub_explorer::get_profile_from_url($uri)); $user = Activitypub_profile::from_profile(Activitypub_explorer::get_profile_from_url($uri));
try { try {
$res = Activitypub_explorer::get_remote_user_activity($uri); $res = Activitypub_explorer::get_remote_user_activity($uri);
} catch (Exception $e) { } catch (Exception $e) {
echo $e->getMessage()."\n"; echo $e->getMessage() . "\n";
exit(1); exit(1);
} }
printfnq('Updated '.Activitypub_profile::update_profile($user, $res)->getBestName()."\n"); printfnq('Updated ' . Activitypub_profile::update_profile($user, $res)->getBestName() . "\n");
exit(0); exit(0);
} elseif (!have_option('a', 'all')) { } elseif (!have_option('a', 'all')) {
show_help(); show_help();
@ -66,7 +67,7 @@ if (have_option('u', 'uri')) {
} }
$user = new Activitypub_profile(); $user = new Activitypub_profile();
$cnt = $user->find(); $cnt = $user->find();
if (!empty($cnt)) { if (!empty($cnt)) {
printfnq("Found {$cnt} ActivityPub profiles:\n"); printfnq("Found {$cnt} ActivityPub profiles:\n");
} else { } else {
@ -79,11 +80,11 @@ if (!empty($cnt)) {
} }
while ($user->fetch()) { while ($user->fetch()) {
try { try {
$res = Activitypub_explorer::get_remote_user_activity($user->uri); $res = Activitypub_explorer::get_remote_user_activity($user->uri);
$updated_profile = Activitypub_profile::update_profile($user, $res); $updated_profile = Activitypub_profile::update_profile($user, $res);
printfnq('Updated '.$updated_profile->getBestName()."\n"); printfnq('Updated ' . $updated_profile->getBestName() . "\n");
} catch (NoProfileException $e) { } catch (NoProfileException $e) {
printfnq('Deleted '.$user->uri."\n"); printfnq('Deleted ' . $user->uri . "\n");
} catch (Exception $e) { } catch (Exception $e) {
// let it go // let it go
} }

View File

@ -44,7 +44,7 @@
<a href="{{ path("favourites", {'nickname' : user_nickname}) }}" class='hover-effect {{ active("favourites") }}'>Favourites</a> <a href="{{ path("favourites", {'nickname' : user_nickname}) }}" class='hover-effect {{ active("favourites") }}'>Favourites</a>
<a href='#'>Reverse Favs</a> <a href='#'>Reverse Favs</a>
<a href="{{ path('settings_personal_info') }}" class='hover-effect {{ active('settings_') }}'>Settings</a> <a href="{{ path('settings_personal_info') }}" class='hover-effect {{ active('settings_') }}'>Settings</a>
<a href='#'>Logout</a> <a href='{{ path('logout') }}'>Logout</a>
</div> </div>
<div class="footer"> <div class="footer">
<a href="{{ path('doc_faq') }}" class='hover-effect {{ active('doc_faq') }}'>FAQ</a> <a href="{{ path('doc_faq') }}" class='hover-effect {{ active('doc_faq') }}'>FAQ</a>

View File

@ -0,0 +1,44 @@
<?php
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace App\Tests\Util\Form\ActorArrayTransformer;
use App\Entity\GSActor;
use App\Util\Form\ActorArrayTransformer;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ActorArrayTransformerTest extends WebTestCase
{
public function testTransform()
{
static::assertSame('', (new ActorArrayTransformer)->transform([]));
$user1 = GSActor::create(['nickname' => 'user1']);
$user2 = GSActor::create(['nickname' => 'user2']);
$user3 = GSActor::create(['nickname' => 'user3']);
$testArr = [$user1, $user2, $user3];
static::assertSame('user1 user2 user3', (new ActorArrayTransformer)->transform($testArr));
}
public function testReverseTransform()
{
$testString = '';
static::assertSame([], (new ActorArrayTransformer)->reverseTransform($testString));
}
}