First unstable federation release
This commit is contained in:
parent
20738f48cd
commit
f8048c7565
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
/vendor/
|
||||
composer.lock
|
||||
|
@ -29,7 +29,11 @@ if (!defined('GNUSOCIAL')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Ensure proper timezone
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
// Import required files by the plugin
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "discoveryhints.php";
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "explorer.php";
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "postman.php";
|
||||
@ -92,7 +96,7 @@ class ActivityPubPlugin extends Plugin
|
||||
} catch (Exception $e) {
|
||||
try {
|
||||
$candidate = Notice::getByID(intval(substr($url, strlen(common_local_url('shownotice', ['notice' => ''])))));
|
||||
if ($candidate->getUrl() == $url) {
|
||||
if ($candidate->getUrl() == $url) { // Sanity check
|
||||
return $candidate;
|
||||
} else {
|
||||
throw new Exception("Notice not found.");
|
||||
@ -111,23 +115,28 @@ class ActivityPubPlugin extends Plugin
|
||||
*/
|
||||
public function onRouterInitialized(URLMapper $m)
|
||||
{
|
||||
ActivityPubURLMapperOverwrite::overwrite_variable(
|
||||
ActivityPubURLMapperOverwrite::variable(
|
||||
$m,
|
||||
'user/:id',
|
||||
['action' => 'showstream'],
|
||||
['id' => '[0-9]+'],
|
||||
['id' => '[0-9]+'],
|
||||
'apActorProfile'
|
||||
);
|
||||
|
||||
// Special route for webfinger purposes
|
||||
ActivityPubURLMapperOverwrite::overwrite_variable(
|
||||
ActivityPubURLMapperOverwrite::variable(
|
||||
$m,
|
||||
':nickname',
|
||||
['action' => 'showstream'],
|
||||
['nickname' => Nickname::DISPLAY_FMT],
|
||||
'apActorProfile'
|
||||
);
|
||||
|
||||
ActivityPubURLMapperOverwrite::variable(
|
||||
$m,
|
||||
'notice/:id',
|
||||
['id' => '[0-9]+'],
|
||||
'apNotice'
|
||||
);
|
||||
|
||||
$m->connect(
|
||||
'user/:id/liked.json',
|
||||
['action' => 'apActorLiked'],
|
||||
@ -170,9 +179,7 @@ class ActivityPubPlugin extends Plugin
|
||||
'version' => GNUSOCIAL_VERSION,
|
||||
'author' => 'Diogo Cordeiro, Daniel Supernault',
|
||||
'homepage' => 'https://www.gnu.org/software/social/',
|
||||
'rawdescription' =>
|
||||
// Todo: Translation
|
||||
'Adds ActivityPub Support'];
|
||||
'rawdescription' => 'Adds ActivityPub Support'];
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -771,7 +778,7 @@ class ActivityPubPlugin extends Plugin
|
||||
if (method_exists('ActivityUtils', 'compareVerbs')) {
|
||||
$is_post_verb = ActivityUtils::compareVerbs(
|
||||
$notice->verb,
|
||||
array(ActivityVerb::POST)
|
||||
[ActivityVerb::POST]
|
||||
);
|
||||
} else {
|
||||
$is_post_verb = ($notice->verb == ActivityVerb::POST ? true : false);
|
||||
@ -887,6 +894,53 @@ class ActivityPubReturn
|
||||
echo json_encode($res, JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select content type from HTTP Accept header
|
||||
*
|
||||
* @author Maciej Łebkowski <m.lebkowski@gmail.com>
|
||||
* @param array $mimeTypes Supported Types
|
||||
* @return array|null of supported mime types sorted | null if none valid
|
||||
*/
|
||||
public static function getBestSupportedMimeType($mimeTypes = null)
|
||||
{
|
||||
// Values will be stored in this array
|
||||
$AcceptTypes = array();
|
||||
|
||||
// Accept header is case insensitive, and whitespace isn’t important
|
||||
$accept = strtolower(str_replace(' ', '', $_SERVER['HTTP_ACCEPT']));
|
||||
// divide it into parts in the place of a ","
|
||||
$accept = explode(',', $accept);
|
||||
foreach ($accept as $a) {
|
||||
// the default quality is 1.
|
||||
$q = 1;
|
||||
// check if there is a different quality
|
||||
if (strpos($a, ';q=')) {
|
||||
// divide "mime/type;q=X" into two parts: "mime/type" i "X"
|
||||
list($a, $q) = explode(';q=', $a);
|
||||
}
|
||||
// mime-type $a is accepted with the quality $q
|
||||
// WARNING: $q == 0 means, that mime-type isn’t supported!
|
||||
$AcceptTypes[$a] = $q;
|
||||
}
|
||||
arsort($AcceptTypes);
|
||||
|
||||
// if no parameter was passed, just return parsed data
|
||||
if (!$mimeTypes) {
|
||||
return $AcceptTypes;
|
||||
}
|
||||
|
||||
$mimeTypes = array_map('strtolower', (array)$mimeTypes);
|
||||
|
||||
// let’s check our supported types:
|
||||
foreach ($AcceptTypes as $mime => $q) {
|
||||
if ($q && in_array($mime, $mimeTypes)) {
|
||||
return $mime;
|
||||
}
|
||||
}
|
||||
// no mime-type found
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -894,15 +948,16 @@ class ActivityPubReturn
|
||||
*/
|
||||
class ActivityPubURLMapperOverwrite extends URLMapper
|
||||
{
|
||||
public static function overwrite_variable($m, $path, $args, $paramPatterns, $newaction)
|
||||
public static function variable($m, $path, $paramPatterns, $newaction)
|
||||
{
|
||||
$mimes = [
|
||||
'application/activity+json',
|
||||
'application/ld+json',
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
];
|
||||
'application/json',
|
||||
'application/activity+json',
|
||||
'application/ld+json',
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
];
|
||||
|
||||
if (in_array($_SERVER["HTTP_ACCEPT"], $mimes) == false) {
|
||||
if (is_null(ActivityPubReturn::getBestSupportedMimeType($mimes))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
68
actions/apnotice.php
Normal file
68
actions/apnotice.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @copyright 2018 Free Software Foundation http://fsf.org
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link https://www.gnu.org/software/social/
|
||||
*/
|
||||
if (!defined('GNUSOCIAL')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice (Local notices only)
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://www.gnu.org/software/social/
|
||||
*/
|
||||
class apNoticeAction extends ManagedAction
|
||||
{
|
||||
protected $needLogin = false;
|
||||
protected $canPost = true;
|
||||
|
||||
/**
|
||||
* Handle the Notice request
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return void
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
try {
|
||||
$notice = Notice::getByID($this->trimmed('id'));
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error('Invalid Notice URI.', 404);
|
||||
}
|
||||
|
||||
if (!$notice->isLocal()) {
|
||||
ActivityPubReturn::error("This is not a local notice.");
|
||||
}
|
||||
|
||||
$res = Activitypub_notice::notice_to_array($notice);
|
||||
|
||||
ActivityPubReturn::answer($res);
|
||||
}
|
||||
}
|
@ -128,7 +128,7 @@ foreach ($to_profiles as $tp) {
|
||||
}
|
||||
|
||||
// Add location if that is set
|
||||
if (isset ($data->object->latitude, $data->object->longitude)) {
|
||||
if (isset($data->object->latitude, $data->object->longitude)) {
|
||||
$act->context->location = Location::fromLatLon($data->object->latitude, $data->object->longitude);
|
||||
}
|
||||
|
||||
|
@ -50,12 +50,15 @@ class Activitypub_create extends Managed_DataObject
|
||||
*/
|
||||
public static function create_to_array($id, $actor, $object)
|
||||
{
|
||||
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => $id,
|
||||
"type" => "Create",
|
||||
"actor" => $actor,
|
||||
"object" => $object
|
||||
);
|
||||
$res = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => $id,
|
||||
'type' => 'Create',
|
||||
'to' => $object['to'],
|
||||
'cc' => $object['cc'],
|
||||
'actor' => $actor,
|
||||
'object' => $object
|
||||
];
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
60
classes/Activitypub_mention_tag.php
Normal file
60
classes/Activitypub_mention_tag.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @copyright 2018 Free Software Foundation http://fsf.org
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link https://www.gnu.org/software/social/
|
||||
*/
|
||||
if (!defined('GNUSOCIAL')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* ActivityPub Mention Tag representation
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://www.gnu.org/software/social/
|
||||
*/
|
||||
class Activitypub_mention_tag extends Managed_DataObject
|
||||
{
|
||||
/**
|
||||
* Generates an ActivityPub representation of a Mention Tag
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $href Actor Uri
|
||||
* @param array $name Mention name
|
||||
* @return pretty array to be used in a response
|
||||
*/
|
||||
public static function mention_tag_to_array_from_values($href, $name)
|
||||
{
|
||||
$res = [
|
||||
"type" => "Mention",
|
||||
"href" => $href,
|
||||
"name" => $name
|
||||
];
|
||||
return $res;
|
||||
}
|
||||
}
|
@ -50,12 +50,12 @@ class Activitypub_notice extends Managed_DataObject
|
||||
public static function notice_to_array($notice)
|
||||
{
|
||||
$profile = $notice->getProfile();
|
||||
$attachments = array();
|
||||
$attachments = [];
|
||||
foreach ($notice->attachments() as $attachment) {
|
||||
$attachments[] = Activitypub_attachment::attachment_to_array($attachment);
|
||||
}
|
||||
|
||||
$tags = array();
|
||||
$tags = [];
|
||||
foreach ($notice->getTags() as $tag) {
|
||||
if ($tag != "") { // Hacky workaround to avoid stupid outputs
|
||||
$tags[] = Activitypub_tag::tag_to_array($tag);
|
||||
@ -64,30 +64,36 @@ class Activitypub_notice extends Managed_DataObject
|
||||
|
||||
$to = [];
|
||||
foreach ($notice->getAttentionProfiles() as $to_profile) {
|
||||
$to[] = $to_profile->getUri();
|
||||
}
|
||||
if (empty($to)) {
|
||||
$to = array("https://www.w3.org/ns/activitystreams#Public");
|
||||
$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));
|
||||
}
|
||||
|
||||
// In a world without walls and fences, we should make everything Public!
|
||||
$to[]= 'https://www.w3.org/ns/activitystreams#Public';
|
||||
|
||||
$item = [
|
||||
'context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => $notice->getUrl(),
|
||||
'type' => 'Note',
|
||||
'inReplyTo' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl(),
|
||||
'published' => $notice->getCreated(),
|
||||
'published' => str_replace(' ', 'T', $notice->getCreated()).'Z',
|
||||
'url' => $notice->getUrl(),
|
||||
'atributedTo' => ActivityPubPlugin::actor_uri($profile),
|
||||
'to' => $to,
|
||||
'cc' => common_local_url('apActorFollowers', ['id' => $profile->getID()]),
|
||||
'atomUri' => $notice->getUrl(),
|
||||
'inReplyToAtomUri' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl(),
|
||||
'conversation' => $notice->getConversationUrl(),
|
||||
'content' => $notice->getContent(),
|
||||
'is_local' => $notice->isLocal(),
|
||||
'isLocal' => $notice->isLocal(),
|
||||
'attachment' => $attachments,
|
||||
'tag' => $tags
|
||||
];
|
||||
|
||||
// Is this a reply?
|
||||
if (!empty($notice->reply_to)) {
|
||||
$item['inReplyTo'] = Notice::getById($notice->reply_to)->getUrl();
|
||||
$item['inReplyToAtomUri'] = Notice::getById($notice->reply_to)->getUrl();
|
||||
}
|
||||
|
||||
// Do we have a location for this notice?
|
||||
try {
|
||||
$location = Notice_location::locFromStored($notice);
|
||||
|
@ -79,15 +79,22 @@ class Activitypub_pending_follow_requests extends Managed_DataObject
|
||||
/**
|
||||
* Add Follow request to table.
|
||||
*
|
||||
* @author Diogo Cordeiro
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param int32 $actor actor id
|
||||
* @param int32 $remote_actor remote actor id
|
||||
* @return boolean true if added, false otherwise
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
return !$this->exists() && $this->insert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a Follow request is pending.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return boolean true if is pending, false otherwise
|
||||
*/
|
||||
public function exists()
|
||||
{
|
||||
$this->_reldb = clone ($this);
|
||||
@ -98,6 +105,12 @@ class Activitypub_pending_follow_requests extends Managed_DataObject
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a request from the pending table.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return boolean true if removed, false otherwise
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
return $this->exists() && $this->_reldb->delete();
|
||||
|
@ -98,20 +98,7 @@ class Activitypub_profile extends Managed_DataObject
|
||||
$res = [
|
||||
'@context' => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
[
|
||||
'manuallyApprovesFollowers' => 'as=>manuallyApprovesFollowers',
|
||||
'sensitive' => 'as=>sensitive',
|
||||
'movedTo' => 'as=>movedTo',
|
||||
'Hashtag' => 'as=>Hashtag',
|
||||
'ostatus' => 'http=>//ostatus.org#',
|
||||
'atomUri' => 'ostatus=>atomUri',
|
||||
'inReplyToAtomUri' => 'ostatus=>inReplyToAtomUri',
|
||||
'conversation' => 'ostatus=>conversation',
|
||||
'schema' => 'http=>//schema.org#',
|
||||
'PropertyValue' => 'schema=>PropertyValue',
|
||||
'value' => 'schema=>value'
|
||||
]
|
||||
"https://w3id.org/security/v1"
|
||||
],
|
||||
'id' => $uri,
|
||||
'type' => 'Person',
|
||||
@ -125,7 +112,7 @@ class Activitypub_profile extends Managed_DataObject
|
||||
'url' => $profile->getUrl(),
|
||||
'manuallyApprovesFollowers' => false,
|
||||
'publicKey' => [
|
||||
'id' => $uri."#main-key",
|
||||
'id' => $uri."#public-key",
|
||||
'owner' => $uri,
|
||||
'publicKeyPem' => $public_key
|
||||
],
|
||||
|
@ -68,6 +68,23 @@ class Activitypub_rsa extends Managed_DataObject
|
||||
];
|
||||
}
|
||||
|
||||
public function get_private_key($profile)
|
||||
{
|
||||
$this->profile_id = $profile->getID();
|
||||
$apRSA = self::getKV('profile_id', $this->profile_id);
|
||||
if (!$apRSA instanceof Activitypub_rsa) {
|
||||
// No existing key pair for this profile
|
||||
if ($profile->isLocal()) {
|
||||
self::generate_keys($this->private_key, $this->public_key);
|
||||
$this->store_keys();
|
||||
} else {
|
||||
throw new Exception('This is a remote Profile, there is no Private Key for this Profile.');
|
||||
}
|
||||
}
|
||||
return $apRSA->private_key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Guarantees a Public Key for a given profile.
|
||||
*
|
||||
|
@ -5,9 +5,6 @@
|
||||
"require": {
|
||||
"pixelfed/http-signatures-guzzlehttp": "^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.2"
|
||||
},
|
||||
"license": "AGPL",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
1813
composer.lock
generated
1813
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -100,6 +100,8 @@ class Activitypub_explorer
|
||||
if (self::validate_remote_response($res)) {
|
||||
$this->temp_res = $res;
|
||||
return true;
|
||||
} else {
|
||||
common_debug('ActivityPub Explorer: Invalid potential remote actor while ensuring URI: '.$url. '. He returned the following: '.json_encode($res, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -116,9 +118,9 @@ class Activitypub_explorer
|
||||
private function grab_local_user($uri, $online = false)
|
||||
{
|
||||
if ($online) {
|
||||
common_debug("Explorer is searching locally for ".$uri. " online.");
|
||||
common_debug('ActivityPub Explorer: Searching locally for '.$uri. ' with online resources.');
|
||||
} else {
|
||||
common_debug("Explorer is searching locally for ".$uri. " offline.");
|
||||
common_debug('ActivityPub Explorer: Searching locally for '.$uri. ' offline.');
|
||||
}
|
||||
// Ensure proper remote URI
|
||||
// If an exception occurs here it's better to just leave everything
|
||||
@ -132,32 +134,34 @@ class Activitypub_explorer
|
||||
$aprofile = self::get_aprofile_by_url($uri);
|
||||
if ($aprofile instanceof Activitypub_profile) {
|
||||
$profile = $aprofile->local_profile();
|
||||
common_debug("Explorer found a local Aprofile for ".$uri);
|
||||
common_debug('ActivityPub Explorer: Found a local Aprofile for '.$uri);
|
||||
// We found something!
|
||||
$this->discovered_actor_profiles[]= $profile;
|
||||
unset($this->temp_res); // IMPORTANT to avoid _dangerous_ noise in the Explorer system
|
||||
return true;
|
||||
} else {
|
||||
common_debug("Explorer didn't find a local Aprofile for ".$uri);
|
||||
common_debug('ActivityPub Explorer: Unable to find a local Aprofile for '.$uri.' - looking for a Profile instead.');
|
||||
// Well, maybe it is a pure blood?
|
||||
// Iff, we are in the same instance:
|
||||
$ACTIVITYPUB_BASE_INSTANCE_URI_length = strlen(ACTIVITYPUB_BASE_INSTANCE_URI);
|
||||
if (substr($uri, 0, $ACTIVITYPUB_BASE_INSTANCE_URI_length) == ACTIVITYPUB_BASE_INSTANCE_URI) {
|
||||
try {
|
||||
$profile = Profile::getByID(intval(substr($uri, $ACTIVITYPUB_BASE_INSTANCE_URI_length)));
|
||||
|
||||
common_debug('ActivityPub Explorer: Found a Profile for '.$uri);
|
||||
// We found something!
|
||||
$this->discovered_actor_profiles[]= $profile;
|
||||
unset($this->temp_res); // IMPORTANT to avoid _dangerous_ noise in the Explorer system
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
// Let the exception go on its merry way.
|
||||
common_debug('ActivityPub Explorer: Unable to find a Profile for '.$uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If offline grabbing failed, attempt again with online resources
|
||||
if (!$online) {
|
||||
common_debug('ActivityPub Explorer: Will try everything again with online resources against: '.$uri);
|
||||
return $this->grab_local_user($uri, true);
|
||||
}
|
||||
|
||||
@ -174,7 +178,7 @@ class Activitypub_explorer
|
||||
*/
|
||||
private function grab_remote_user($url)
|
||||
{
|
||||
common_debug("Explorer is grabbing a remote profile for ".$url);
|
||||
common_debug('ActivityPub Explorer: Trying to grab a remote actor for '.$url);
|
||||
if (!isset($this->temp_res)) {
|
||||
$client = new HTTPClient();
|
||||
$headers = array();
|
||||
@ -187,8 +191,10 @@ class Activitypub_explorer
|
||||
unset($this->temp_res);
|
||||
}
|
||||
if (isset($res["orderedItems"])) { // It's a potential collection of actors!!!
|
||||
common_debug('ActivityPub Explorer: Found a collection of actors for '.$url);
|
||||
foreach ($res["orderedItems"] as $profile) {
|
||||
if ($this->_lookup($profile) == false) {
|
||||
common_debug('ActivityPub Explorer: Found an inavlid actor for '.$profile);
|
||||
// XXX: Invalid actor found, not sure how we handle those
|
||||
}
|
||||
}
|
||||
@ -198,8 +204,11 @@ class Activitypub_explorer
|
||||
}
|
||||
return true;
|
||||
} elseif (self::validate_remote_response($res)) {
|
||||
common_debug('ActivityPub Explorer: Found a valid remote actor for '.$url);
|
||||
$this->discovered_actor_profiles[]= $this->store_profile($res);
|
||||
return true;
|
||||
} else {
|
||||
common_debug('ActivityPub Explorer: Invalid potential remote actor while grabbing remotely: '.$url. '. He returned the following: '.json_encode($res, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -218,8 +227,8 @@ class Activitypub_explorer
|
||||
$aprofile = new Activitypub_profile;
|
||||
$aprofile->uri = $res['id'];
|
||||
$aprofile->nickname = $res['preferredUsername'];
|
||||
$aprofile->fullname = $res['name'];
|
||||
$aprofile->bio = substr($res['summary'], 0, 1000);
|
||||
$aprofile->fullname = isset($res['name']) ? $res['name'] : null;
|
||||
$aprofile->bio = isset($res['summary']) ? substr($res['summary'], 0, 1000) : null;
|
||||
$aprofile->inboxuri = $res['inbox'];
|
||||
$aprofile->sharedInboxuri = isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox'];
|
||||
|
||||
@ -245,7 +254,7 @@ class Activitypub_explorer
|
||||
*/
|
||||
public static function validate_remote_response($res)
|
||||
{
|
||||
if (!isset($res['id'], $res['preferredUsername'], $res['name'], $res['summary'], $res['inbox'], $res['publicKey']['publicKeyPem'])) {
|
||||
if (!isset($res['id'], $res['preferredUsername'], $res['inbox'], $res['publicKey']['publicKeyPem'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,10 @@ if (!defined('GNUSOCIAL')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use HttpSignatures\Context;
|
||||
use HttpSignatures\GuzzleHttpSignatures;
|
||||
|
||||
/**
|
||||
* ActivityPub's own Postman
|
||||
*
|
||||
@ -44,6 +48,7 @@ if (!defined('GNUSOCIAL')) {
|
||||
class Activitypub_postman
|
||||
{
|
||||
private $actor;
|
||||
private $actor_uri;
|
||||
private $to = [];
|
||||
private $client;
|
||||
private $headers;
|
||||
@ -57,12 +62,46 @@ class Activitypub_postman
|
||||
*/
|
||||
public function __construct($from, $to = [])
|
||||
{
|
||||
$this->client = new HTTPClient();
|
||||
$this->actor = $from;
|
||||
$this->to = $to;
|
||||
$this->headers = [];
|
||||
$this->headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
||||
$this->headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
|
||||
$this->actor_uri = ActivityPubPlugin::actor_uri($this->actor);
|
||||
|
||||
$actor_private_key = new Activitypub_rsa();
|
||||
$actor_private_key = $actor_private_key->get_private_key($this->actor);
|
||||
|
||||
$context = new Context([
|
||||
'keys' => [$this->actor_uri."#public-key" => $actor_private_key],
|
||||
'algorithm' => 'rsa-sha256',
|
||||
'headers' => ['(request-target)', 'date', 'content-type', 'accept', 'user-agent'],
|
||||
]);
|
||||
|
||||
$this->to = $to;
|
||||
$this->headers = [
|
||||
'content-type' => 'application/activity+json',
|
||||
'accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'user-agent' => 'GNUSocialBot v0.1 - https://gnu.io/social',
|
||||
'date' => date('D, d M Y h:i:s') . ' GMT'
|
||||
];
|
||||
|
||||
$handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context);
|
||||
$this->client = new Client(['handler' => $handlerStack]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send something to remote instance
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $data request body
|
||||
* @param string $inbox url of remote inbox
|
||||
* @param string $method request method
|
||||
* @return Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
public function send($data, $inbox, $method = 'POST')
|
||||
{
|
||||
common_debug('ActivityPub Postman: Delivering '.$data.' to '.$inbox);
|
||||
$response = $this->client->request($method, $inbox, ['headers' => array_merge($this->headers, ['(request-target)' => strtolower($method).' '.parse_url($inbox, PHP_URL_PATH)]),'body' => $data]);
|
||||
common_debug('ActivityPub Postman: Delivery result: '.$response->getBody()->getContents());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,13 +113,12 @@ class Activitypub_postman
|
||||
public function follow()
|
||||
{
|
||||
$data = Activitypub_follow::follow_to_array(ActivityPubPlugin::actor_uri($this->actor), $this->to[0]->getUrl());
|
||||
$this->client->setBody(json_encode($data));
|
||||
$res = $this->client->post($this->to[0]->get_inbox(), $this->headers);
|
||||
$res_body = json_decode($res->getBody());
|
||||
$res = $this->send(json_encode($data), $this->to[0]->get_inbox());
|
||||
$res_body = json_decode($res->getBody()->getContents());
|
||||
|
||||
if ($res->isOk() || $res->getStatus() == 409) {
|
||||
if ($res->getStatusCode() == 200 || $res->getStatusCode() == 409) {
|
||||
$pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID());
|
||||
if (! ($res->getStatus() == 409 || $res_body->type == "Accept")) {
|
||||
if (! ($res->getStatusCode() == 409 || $res_body->type == "Accept")) {
|
||||
$pending_list->add();
|
||||
throw new Exception("Your follow request is pending acceptation.");
|
||||
}
|
||||
@ -106,11 +144,10 @@ class Activitypub_postman
|
||||
$this->to[0]->getUrl()
|
||||
)
|
||||
);
|
||||
$this->client->setBody(json_encode($data));
|
||||
$res = $this->client->post($this->to[0]->get_inbox(), $this->headers);
|
||||
$res_body = json_decode($res->getBody());
|
||||
$res = $this->send(json_encode($data), $this->to[0]->get_inbox());
|
||||
$res_body = json_decode($res->getBody()->getContents());
|
||||
|
||||
if ($res->isOk() || $res->getStatus() == 409) {
|
||||
if ($res->getStatusCode() == 200 || $res->getStatusCode() == 409) {
|
||||
$pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID());
|
||||
$pending_list->remove();
|
||||
return true;
|
||||
@ -133,9 +170,10 @@ class Activitypub_postman
|
||||
ActivityPubPlugin::actor_uri($this->actor),
|
||||
Activitypub_notice::notice_to_array($notice)
|
||||
);
|
||||
$this->client->setBody(json_encode($data));
|
||||
$data = json_encode($data);
|
||||
|
||||
foreach ($this->to_inbox() as $inbox) {
|
||||
$this->client->post($inbox, $this->headers);
|
||||
$this->send($data, $inbox);
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,9 +191,10 @@ class Activitypub_postman
|
||||
Activitypub_notice::notice_to_array($notice)
|
||||
)
|
||||
);
|
||||
$this->client->setBody(json_encode($data));
|
||||
$data = json_encode($data);
|
||||
|
||||
foreach ($this->to_inbox() as $inbox) {
|
||||
$this->client->post($inbox, $this->headers);
|
||||
$this->send($data, $inbox);
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,15 +208,16 @@ class Activitypub_postman
|
||||
{
|
||||
$data = Activitypub_create::create_to_array(
|
||||
$notice->getUrl(),
|
||||
ActivityPubPlugin::actor_uri($this->actor),
|
||||
array_merge(Activitypub_notice::notice_to_array($notice), ['cc' => common_local_url('apActorFollowers', ['id' => $this->actor->getID()]),])
|
||||
$this->actor_uri,
|
||||
Activitypub_notice::notice_to_array($notice)
|
||||
);
|
||||
if (isset($notice->reply_to)) {
|
||||
$data["object"]["reply_to"] = $notice->getParent()->getUrl();
|
||||
}
|
||||
$this->client->setBody(json_encode($data));
|
||||
$data = json_encode($data);
|
||||
|
||||
foreach ($this->to_inbox() as $inbox) {
|
||||
$this->client->post($inbox, $this->headers);
|
||||
$this->send($data, $inbox);
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,9 +233,10 @@ class Activitypub_postman
|
||||
ActivityPubPlugin::actor_uri($this->actor),
|
||||
Activitypub_notice::notice_to_array($notice)
|
||||
);
|
||||
$this->client->setBody(json_encode($data));
|
||||
$data = json_encode($data);
|
||||
|
||||
foreach ($this->to_inbox() as $inbox) {
|
||||
$this->client->post($inbox, $this->headers);
|
||||
$this->send($data, $inbox);
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,12 +249,12 @@ class Activitypub_postman
|
||||
public function delete($notice)
|
||||
{
|
||||
$data = Activitypub_delete::delete_to_array(Activitypub_notice::notice_to_array($notice));
|
||||
$this->client->setBody(json_encode($data));
|
||||
$errors = array();
|
||||
$errors = [];
|
||||
$data = json_encode($data);
|
||||
foreach ($this->to_inbox() as $inbox) {
|
||||
$res = $this->client->post($inbox, $this->headers);
|
||||
if (!$res->isOk()) {
|
||||
$res_body = json_decode($res->getBody());
|
||||
$res = $this->send($data, $inbox);
|
||||
if (!$res->getStatusCode() == 200) {
|
||||
$res_body = json_decode($res->getBody()->getContents());
|
||||
if (isset($res_body[0]->error)) {
|
||||
$errors[] = ($res_body[0]->error);
|
||||
continue;
|
||||
@ -234,7 +275,7 @@ class Activitypub_postman
|
||||
*/
|
||||
private function to_inbox()
|
||||
{
|
||||
$to_inboxes = array();
|
||||
$to_inboxes = [];
|
||||
foreach ($this->to as $to_profile) {
|
||||
$i = $to_profile->get_inbox();
|
||||
// Prevent delivering to self
|
||||
|
Reference in New Issue
Block a user