Merge pull request #9211 from annando/send-relais

We can now manage relay servers and can send content to them
This commit is contained in:
Hypolite Petovan 2020-09-17 10:18:06 -04:00 committed by GitHub
commit 3e9aa606ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 217 additions and 3 deletions

View file

@ -27,6 +27,7 @@ The console provides the following commands:
* typo: Checks for parse errors in Friendica files * typo: Checks for parse errors in Friendica files
* postupdate: Execute pending post update scripts (can last days) * postupdate: Execute pending post update scripts (can last days)
* storage: Manage storage backend * storage: Manage storage backend
* relay: Manage ActivityPub relay servers
Please consult *bin/console help* on the command line interface of your server for details about the commands. Please consult *bin/console help* on the command line interface of your server for details about the commands.

134
src/Console/Relay.php Normal file
View file

@ -0,0 +1,134 @@
<?php
/**
* @copyright Copyright (C) 2020, Friendica
*
* @license GNU AGPL version 3 or any later version
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Console;
use Asika\SimpleConsole\CommandArgsException;
use Friendica\Model\APContact;
use Friendica\Model\Contact;
use Friendica\Protocol\ActivityPub\Transmitter;
/**
* tool to control the list of ActivityPub relay servers from the CLI
*
* With this script you can access the relay servers of your node from
* the CLI.
*/
class Relay extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var $dba Friendica\Database\Database
*/
private $dba;
protected function getHelp()
{
$help = <<<HELP
console relay - Manage ActivityPub relay configuration
Synopsis
bin/console relay [-h|--help|-?] [-v]
bin/console relay add <actor> [-h|--help|-?] [-v]
bin/console relay remove <actoor> [-h|--help|-?] [-v]
Description
bin/console relay
Lists all active relay servers
bin/console relay add <actor>
Add a relay actor in the format https://relayserver.tld/actor
bin/console relay remove <actor>
Remove a relay actor in the format https://relayserver.tld/actor
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
return $help;
}
public function __construct(\Friendica\Database\Database $dba, array $argv = null)
{
parent::__construct($argv);
$this->dba = $dba;
}
protected function doExecute()
{
if ($this->getOption('v')) {
$this->out('Executable: ' . $this->executable);
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
$this->out('Options: ' . var_export($this->options, true));
}
if (count($this->args) > 2) {
throw new CommandArgsException('Too many arguments');
}
if (count($this->args) == 1) {
throw new CommandArgsException('Too few arguments');
}
if (count($this->args) == 0) {
$contacts = $this->dba->select('apcontact', ['url'],
["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))",
'Application', 0, Contact::FOLLOWER, Contact::FRIEND]);
while ($contact = $this->dba->fetch($contacts)) {
$this->out($contact['url']);
}
$this->dba->close($contacts);
}
if (count($this->args) == 2) {
$mode = $this->getArgument(0);
$actor = $this->getArgument(1);
$apcontact = APContact::getByURL($actor);
if (empty($apcontact) || ($apcontact['type'] != 'Application')) {
$this->out($actor . ' is no relay actor');
return 1;
}
if ($mode == 'add') {
if (Transmitter::sendRelayFollow($actor)) {
$this->out('Successfully added ' . $actor);
} else {
$this->out($actor . " couldn't be added");
}
} elseif ($mode == 'remove') {
if (Transmitter::sendRelayUndoFollow($actor)) {
$this->out('Successfully removed ' . $actor);
} else {
$this->out($actor . " couldn't be removed");
}
} else {
throw new CommandArgsException($mode . ' is no valid command');
}
}
return 0;
}
}

View file

@ -64,6 +64,7 @@ Commands:
postupdate Execute pending post update scripts (can last days) postupdate Execute pending post update scripts (can last days)
serverblock Manage blocked servers serverblock Manage blocked servers
storage Manage storage backend storage Manage storage backend
relay Manage ActivityPub relay servers
Options: Options:
-h|--help|-? Show help information -h|--help|-? Show help information
@ -92,6 +93,7 @@ HELP;
'postupdate' => Friendica\Console\PostUpdate::class, 'postupdate' => Friendica\Console\PostUpdate::class,
'serverblock' => Friendica\Console\ServerBlock::class, 'serverblock' => Friendica\Console\ServerBlock::class,
'storage' => Friendica\Console\Storage::class, 'storage' => Friendica\Console\Storage::class,
'relay' => Friendica\Console\Relay::class,
]; ];
/** /**

View file

@ -163,11 +163,13 @@ class Receiver
if ($type != 'as:Announce') { if ($type != 'as:Announce') {
Logger::info('Not an announcement', ['activity' => $activity]); Logger::info('Not an announcement', ['activity' => $activity]);
return;
} }
$object_id = JsonLD::fetchElement($activity, 'as:object', '@id'); $object_id = JsonLD::fetchElement($activity, 'as:object', '@id');
if (empty($object_id)) { if (empty($object_id)) {
Logger::info('No object id found', ['activity' => $activity]); Logger::info('No object id found', ['activity' => $activity]);
return;
} }
Logger::info('Got relayed message id', ['id' => $object_id]); Logger::info('Got relayed message id', ['id' => $object_id]);
@ -179,6 +181,13 @@ class Receiver
} }
Processor::fetchMissingActivity($object_id); Processor::fetchMissingActivity($object_id);
$item_id = Item::searchByLink($object_id);
if ($item_id) {
Logger::info('Relayed message had been fetched and stored', ['id' => $object_id, 'item' => $item_id]);
} else {
Logger::notice('Relayed message had not been stored', ['id' => $object_id]);
}
} }
/** /**

View file

@ -61,6 +61,68 @@ require_once 'mod/share.php';
*/ */
class Transmitter class Transmitter
{ {
/**
* Add relay servers to the list of inboxes
*
* @param array $inboxes
* @return array inboxes with added relay servers
*/
public static function addRelayServerInboxes(array $inboxes)
{
$contacts = DBA::select('apcontact', ['inbox'],
["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))",
'Application', 0, Contact::FOLLOWER, Contact::FRIEND]);
while ($contact = DBA::fetch($contacts)) {
$inboxes[] = $contact['inbox'];
}
DBA::close($contacts);
return $inboxes;
}
/**
* Subscribe to a relay
*
* @param string $url Subscribe actor url
* @return bool success
*/
public static function sendRelayFollow(string $url)
{
$contact_id = Contact::getIdForURL($url);
if (!$contact_id) {
return false;
}
$activity_id = ActivityPub\Transmitter::activityIDFromContact($contact_id);
$success = ActivityPub\Transmitter::sendActivity('Follow', $url, 0, $activity_id);
if ($success) {
DBA::update('contact', ['rel' => Contact::FRIEND], ['id' => $contact_id]);
}
return $success;
}
/**
* Unsubscribe from a relay
*
* @param string $url Subscribe actor url
* @return bool success
*/
public static function sendRelayUndoFollow(string $url)
{
$contact_id = Contact::getIdForURL($url);
if (!$contact_id) {
return false;
}
$success = self::sendContactUndo($url, $contact_id, 0);
if ($success) {
DBA::update('contact', ['rel' => Contact::SHARING], ['id' => $contact_id]);
}
return $success;
}
/** /**
* Collects a list of contacts of the given owner * Collects a list of contacts of the given owner
* *
@ -1917,18 +1979,19 @@ class Transmitter
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
* @throws \Exception * @throws \Exception
* @return bool success
*/ */
public static function sendContactUndo($target, $cid, $uid) public static function sendContactUndo($target, $cid, $uid)
{ {
$profile = APContact::getByURL($target); $profile = APContact::getByURL($target);
if (empty($profile['inbox'])) { if (empty($profile['inbox'])) {
Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]); Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]);
return; return false;
} }
$object_id = self::activityIDFromContact($cid); $object_id = self::activityIDFromContact($cid);
if (empty($object_id)) { if (empty($object_id)) {
return; return false;
} }
$id = DI::baseUrl() . '/activity/' . System::createGUID(); $id = DI::baseUrl() . '/activity/' . System::createGUID();
@ -1947,7 +2010,7 @@ class Transmitter
Logger::log('Sending undo to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG); Logger::log('Sending undo to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG);
$signed = LDSignature::sign($data, $owner); $signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid); return HTTPSignature::transmit($signed, $profile['inbox'], $uid);
} }
private static function prependMentions($body, int $uriid) private static function prependMentions($body, int $uriid)

View file

@ -786,6 +786,11 @@ class Notifier
if ($target_item['origin']) { if ($target_item['origin']) {
$inboxes = ActivityPub\Transmitter::fetchTargetInboxes($target_item, $uid); $inboxes = ActivityPub\Transmitter::fetchTargetInboxes($target_item, $uid);
if (in_array($target_item['private'], [Item::PUBLIC])) {
$inboxes = ActivityPub\Transmitter::addRelayServerInboxes($inboxes);
}
Logger::log('Origin item ' . $target_item['id'] . ' with URL ' . $target_item['uri'] . ' will be distributed.', Logger::DEBUG); Logger::log('Origin item ' . $target_item['id'] . ' with URL ' . $target_item['uri'] . ' will be distributed.', Logger::DEBUG);
} elseif (Item::isForumPost($target_item, $owner)) { } elseif (Item::isForumPost($target_item, $owner)) {
$inboxes = ActivityPub\Transmitter::fetchTargetInboxes($target_item, $uid, false, 0, true); $inboxes = ActivityPub\Transmitter::fetchTargetInboxes($target_item, $uid, false, 0, true);