forked from GNUsocial/gnu-social
[PLUGIN][ActivityPub] Implement Actor Update
Diverse minor bug fixes
This commit is contained in:
parent
9506909e7a
commit
9512890264
@ -80,7 +80,7 @@ class Inbox extends Controller
|
|||||||
$type = Model::jsonToType($body);
|
$type = Model::jsonToType($body);
|
||||||
|
|
||||||
if ($type->has('actor') === false) {
|
if ($type->has('actor') === false) {
|
||||||
$error('Actor not found in the request.');
|
return $error('Actor not found in the request.');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -88,10 +88,11 @@ class Inbox extends Controller
|
|||||||
$actor = Actor::getById($ap_actor->getActorId());
|
$actor = Actor::getById($ap_actor->getActorId());
|
||||||
DB::flush();
|
DB::flush();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$error('Invalid actor.');
|
return $error('Invalid actor.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$actor_public_key = ActivitypubRsa::getByActor($actor)->getPublicKey();
|
$activitypub_rsa = ActivitypubRsa::getByActor($actor);
|
||||||
|
$actor_public_key = $activitypub_rsa->getPublicKey();
|
||||||
|
|
||||||
$headers = $this->request->headers->all();
|
$headers = $this->request->headers->all();
|
||||||
// Flattify headers
|
// Flattify headers
|
||||||
@ -101,7 +102,7 @@ class Inbox extends Controller
|
|||||||
|
|
||||||
if (!isset($headers['signature'])) {
|
if (!isset($headers['signature'])) {
|
||||||
Log::debug('ActivityPub Inbox: HTTP Signature: Missing Signature header.');
|
Log::debug('ActivityPub Inbox: HTTP Signature: Missing Signature header.');
|
||||||
$error('Missing Signature header.', 400);
|
return $error('Missing Signature header.', 400);
|
||||||
// TODO: support other methods beyond HTTP Signatures
|
// TODO: support other methods beyond HTTP Signatures
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,22 +111,25 @@ class Inbox extends Controller
|
|||||||
Log::debug('ActivityPub Inbox: HTTP Signature Data: ' . print_r($signatureData, true));
|
Log::debug('ActivityPub Inbox: HTTP Signature Data: ' . print_r($signatureData, true));
|
||||||
if (isset($signatureData['error'])) {
|
if (isset($signatureData['error'])) {
|
||||||
Log::debug('ActivityPub Inbox: HTTP Signature: ' . json_encode($signatureData, JSON_PRETTY_PRINT));
|
Log::debug('ActivityPub Inbox: HTTP Signature: ' . json_encode($signatureData, JSON_PRETTY_PRINT));
|
||||||
$error(json_encode($signatureData, JSON_PRETTY_PRINT), 400);
|
return $error(json_encode($signatureData, JSON_PRETTY_PRINT), 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
list($verified, /*$headers*/) = HTTPSignature::verify($actor_public_key, $signatureData, $headers, $path, $body);
|
[$verified, /*$headers*/] = HTTPSignature::verify($actor_public_key, $signatureData, $headers, $path, $body);
|
||||||
|
|
||||||
// If the signature fails verification the first time, update profile as it might have changed public key
|
// If the signature fails verification the first time, update profile as it might have changed public key
|
||||||
if ($verified !== 1) {
|
if ($verified !== 1) {
|
||||||
try {
|
try {
|
||||||
$res = Explorer::get_remote_user_activity($ap_actor->getUri());
|
$res = Explorer::get_remote_user_activity($ap_actor->getUri());
|
||||||
|
if (is_null($res)) {
|
||||||
|
return $error('Invalid remote actor.');
|
||||||
|
}
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
$error('Invalid remote actor.');
|
return $error('Invalid remote actor.');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$actor = ActivitypubActor::update_profile($ap_actor, $res);
|
ActivitypubActor::update_profile($ap_actor, $actor, $activitypub_rsa, $res);
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
$error('Failed to updated remote actor information.');
|
return $error('Failed to updated remote actor information.');
|
||||||
}
|
}
|
||||||
|
|
||||||
[$verified, /*$headers*/] = HTTPSignature::verify($actor_public_key, $signatureData, $headers, $path, $body);
|
[$verified, /*$headers*/] = HTTPSignature::verify($actor_public_key, $signatureData, $headers, $path, $body);
|
||||||
@ -134,7 +138,7 @@ class Inbox extends Controller
|
|||||||
// If it still failed despite profile update
|
// If it still failed despite profile update
|
||||||
if ($verified !== 1) {
|
if ($verified !== 1) {
|
||||||
Log::debug('ActivityPub Inbox: HTTP Signature: Invalid signature.');
|
Log::debug('ActivityPub Inbox: HTTP Signature: Invalid signature.');
|
||||||
$error('Invalid signature.');
|
return $error('Invalid signature.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP signature checked out, make sure the "actor" of the activity matches that of the signature
|
// HTTP signature checked out, make sure the "actor" of the activity matches that of the signature
|
||||||
|
@ -34,6 +34,7 @@ namespace Plugin\ActivityPub\Entity;
|
|||||||
use App\Core\Cache;
|
use App\Core\Cache;
|
||||||
use App\Core\Entity;
|
use App\Core\Entity;
|
||||||
use App\Core\Log;
|
use App\Core\Log;
|
||||||
|
use App\Entity\Actor;
|
||||||
use Component\FreeNetwork\Util\Discovery;
|
use Component\FreeNetwork\Util\Discovery;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
@ -237,6 +238,18 @@ class ActivitypubActor extends Entity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ActivitypubActor $ap_actor
|
||||||
|
* @param Actor $actor
|
||||||
|
* @param ActivitypubRsa $activitypub_rsa
|
||||||
|
* @param string $res
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function update_profile(self &$ap_actor, Actor &$actor, ActivitypubRsa &$activitypub_rsa, string $res): void
|
||||||
|
{
|
||||||
|
\Plugin\ActivityPub\Util\Model\Actor::fromJson($res, ['objects' => ['ActivitypubActor' => &$ap_actor, 'Actor' => &$actor, 'ActivitypubRsa' => &$activitypub_rsa]]);
|
||||||
|
}
|
||||||
|
|
||||||
public static function schemaDef(): array
|
public static function schemaDef(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -55,7 +55,7 @@ use const JSON_UNESCAPED_SLASHES;
|
|||||||
*/
|
*/
|
||||||
class Explorer
|
class Explorer
|
||||||
{
|
{
|
||||||
private array $discovered_actor_profiles = [];
|
private array $discovered_activitypub_actor_profiles = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut function to get a single profile from its URL.
|
* Shortcut function to get a single profile from its URL.
|
||||||
@ -104,7 +104,7 @@ class Explorer
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log::debug('ActivityPub Explorer: Started now looking for ' . $url);
|
Log::debug('ActivityPub Explorer: Started now looking for ' . $url);
|
||||||
$this->discovered_actor_profiles = [];
|
$this->discovered_activitypub_actor_profiles = [];
|
||||||
|
|
||||||
return $this->_lookup($url, $grab_online);
|
return $this->_lookup($url, $grab_online);
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ class Explorer
|
|||||||
* @throws ServerExceptionInterface
|
* @throws ServerExceptionInterface
|
||||||
* @throws TransportExceptionInterface
|
* @throws TransportExceptionInterface
|
||||||
*
|
*
|
||||||
* @return array of Profile objects
|
* @return array of ActivityPub Actor objects
|
||||||
*/
|
*/
|
||||||
private function _lookup(string $url, bool $grab_online = true): array
|
private function _lookup(string $url, bool $grab_online = true): array
|
||||||
{
|
{
|
||||||
@ -135,7 +135,7 @@ class Explorer
|
|||||||
throw new NoSuchActorException('Actor not found.');
|
throw new NoSuchActorException('Actor not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->discovered_actor_profiles;
|
return $this->discovered_activitypub_actor_profiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,7 +160,7 @@ class Explorer
|
|||||||
Log::debug('ActivityPub Explorer: Found a known Aprofile for ' . $uri);
|
Log::debug('ActivityPub Explorer: Found a known Aprofile for ' . $uri);
|
||||||
|
|
||||||
// We found something!
|
// We found something!
|
||||||
$this->discovered_actor_profiles[] = $aprofile;
|
$this->discovered_activitypub_actor_profiles[] = $aprofile;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Log::debug('ActivityPub Explorer: Unable to find a known Aprofile for ' . $uri);
|
Log::debug('ActivityPub Explorer: Unable to find a known Aprofile for ' . $uri);
|
||||||
@ -204,7 +204,7 @@ class Explorer
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$this->discovered_actor_profiles[] = Model\Actor::fromJson(json_encode($res));
|
$this->discovered_activitypub_actor_profiles[] = Model\Actor::fromJson(json_encode($res));
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::debug(
|
Log::debug(
|
||||||
@ -219,19 +219,6 @@ class Explorer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a remote response in order to determine whether this
|
|
||||||
* response is a valid profile or not
|
|
||||||
*
|
|
||||||
* @param array $res remote response
|
|
||||||
*
|
|
||||||
* @return bool success state
|
|
||||||
*/
|
|
||||||
public static function validate_remote_response(array $res): bool
|
|
||||||
{
|
|
||||||
return !(!isset($res['id'], $res['preferredUsername'], $res['inbox'], $res['publicKey']['publicKeyPem']));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a ActivityPub Profile from it's uri
|
* Get a ActivityPub Profile from it's uri
|
||||||
*
|
*
|
||||||
@ -288,28 +275,20 @@ class Explorer
|
|||||||
* @throws RedirectionExceptionInterface
|
* @throws RedirectionExceptionInterface
|
||||||
* @throws ServerExceptionInterface
|
* @throws ServerExceptionInterface
|
||||||
* @throws TransportExceptionInterface
|
* @throws TransportExceptionInterface
|
||||||
|
* @throws Exception
|
||||||
*
|
*
|
||||||
* @return array|false If it is able to fetch, false if it's gone
|
* @return string|null If it is able to fetch, false if it's gone
|
||||||
* // Exceptions when network issues or unsupported Activity format
|
* // Exceptions when network issues or unsupported Activity format
|
||||||
*/
|
*/
|
||||||
public static function get_remote_user_activity(string $url): bool|array
|
public static function get_remote_user_activity(string $url): string|null
|
||||||
{
|
{
|
||||||
$response = HTTPClient::get($url, ['headers' => ACTIVITYPUB::HTTP_CLIENT_HEADERS]);
|
$response = HTTPClient::get($url, ['headers' => ACTIVITYPUB::HTTP_CLIENT_HEADERS]);
|
||||||
// If it was deleted
|
// If it was deleted
|
||||||
if ($response->getStatusCode() == 410) {
|
if ($response->getStatusCode() == 410) {
|
||||||
return false;
|
return null;
|
||||||
} elseif (!HTTPClient::statusCodeIsOkay($response)) { // If it is unavailable
|
} elseif (!HTTPClient::statusCodeIsOkay($response)) { // If it is unavailable
|
||||||
throw new Exception('Non Ok Status Code for given Actor URL.');
|
throw new Exception('Non Ok Status Code for given Actor URL.');
|
||||||
}
|
}
|
||||||
$res = json_decode($response->getContent(), true);
|
return $response->getContent();
|
||||||
if (is_null($res)) {
|
|
||||||
Log::debug('ActivityPub Explorer: Invalid JSON returned from given Actor URL: ' . $response->getContent());
|
|
||||||
throw new Exception('Given Actor URL didn\'t return a valid JSON.');
|
|
||||||
}
|
|
||||||
if (self::validate_remote_response($res)) {
|
|
||||||
Log::debug('ActivityPub Explorer: Found a valid remote actor for ' . $url);
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
throw new Exception('ActivityPub Explorer: Failed to get activity.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ class Activity extends Model
|
|||||||
public static function fromJson(string|AbstractObject $json, array $options = []): ActivitypubActivity
|
public static function fromJson(string|AbstractObject $json, array $options = []): ActivitypubActivity
|
||||||
{
|
{
|
||||||
$type_activity = is_string($json) ? self::jsonToType($json) : $json;
|
$type_activity = is_string($json) ? self::jsonToType($json) : $json;
|
||||||
|
$source = $options['source'];
|
||||||
|
|
||||||
$activity_stream_two_verb_to_gs_verb = fn(string $verb): string => match ($verb) {
|
$activity_stream_two_verb_to_gs_verb = fn(string $verb): string => match ($verb) {
|
||||||
'Create' => 'create',
|
'Create' => 'create',
|
||||||
@ -82,15 +83,15 @@ class Activity extends Model
|
|||||||
$actor = ActivityPub::getActorByUri($type_activity->get('actor'));
|
$actor = ActivityPub::getActorByUri($type_activity->get('actor'));
|
||||||
// Store Object
|
// Store Object
|
||||||
$obj = null;
|
$obj = null;
|
||||||
if (!$type_activity->has('object') || !isset($type_activity->get('object')['type'])) {
|
if (!$type_activity->has('object') || !$type_activity->get('object')->has('type')) {
|
||||||
throw new InvalidArgumentException('Activity Object or Activity Object Type is missing.');
|
throw new InvalidArgumentException('Activity Object or Activity Object Type is missing.');
|
||||||
}
|
}
|
||||||
switch ($type_activity->get('object')['type']) {
|
switch ($type_activity->get('object')->get('type')) {
|
||||||
case 'Note':
|
case 'Note':
|
||||||
$obj = Note::toJson($type_activity->get('object'), ['source' => $source, 'actor_uri' => $type_activity->get('actor'), 'actor_id' => $actor->getId()]);
|
$obj = Note::fromJson($type_activity->get('object'), ['source' => $source, 'actor_uri' => $type_activity->get('actor'), 'actor_id' => $actor->getId()]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!Event::handle('ActivityPubObject', [$type_activity->get('object')['type'], $type_activity->get('object'), &$obj])) {
|
if (!Event::handle('ActivityPubObject', [$type_activity->get('object')->get('type'), $type_activity->get('object'), &$obj])) {
|
||||||
throw new ClientException('Unsupported Object type.');
|
throw new ClientException('Unsupported Object type.');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -100,20 +101,20 @@ class Activity extends Model
|
|||||||
$act = GSActivity::create([
|
$act = GSActivity::create([
|
||||||
'actor_id' => $actor->getId(),
|
'actor_id' => $actor->getId(),
|
||||||
'verb' => $activity_stream_two_verb_to_gs_verb($type_activity->get('type')),
|
'verb' => $activity_stream_two_verb_to_gs_verb($type_activity->get('type')),
|
||||||
'object_type' => $activity_stream_two_object_type_to_gs_table($type_activity->get('object')['type']),
|
'object_type' => $activity_stream_two_object_type_to_gs_table($type_activity->get('object')->get('type')),
|
||||||
'object_id' => $obj->getId(),
|
'object_id' => $obj->getId(),
|
||||||
'is_local' => false,
|
'is_local' => false,
|
||||||
'created' => new DateTime($activity['published'] ?? 'now'),
|
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
||||||
'source' => $source,
|
'source' => $source,
|
||||||
]);
|
]);
|
||||||
DB::persist($act);
|
DB::persist($act);
|
||||||
// Store ActivityPub Activity
|
// Store ActivityPub Activity
|
||||||
$ap_act = ActivitypubActivity::create([
|
$ap_act = ActivitypubActivity::create([
|
||||||
'activity_id' => $act->getId(),
|
'activity_id' => $act->getId(),
|
||||||
'activity_uri' => $activity['id'],
|
'activity_uri' => $type_activity->get('id'),
|
||||||
'object_uri' => $activity['object']['id'],
|
'object_uri' => $type_activity->get('object')->get('id'),
|
||||||
'is_local' => false,
|
'is_local' => false,
|
||||||
'created' => new DateTime($activity['published'] ?? 'now'),
|
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
||||||
'modified' => new DateTime(),
|
'modified' => new DateTime(),
|
||||||
]);
|
]);
|
||||||
DB::persist($ap_act);
|
DB::persist($ap_act);
|
||||||
|
@ -64,10 +64,10 @@ class Actor extends Model
|
|||||||
*
|
*
|
||||||
* @param string|AbstractObject $json
|
* @param string|AbstractObject $json
|
||||||
* @param array $options
|
* @param array $options
|
||||||
* @return GSActor
|
* @return ActivitypubActor
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static function fromJson(string|AbstractObject $json, array $options = []): GSActor
|
public static function fromJson(string|AbstractObject $json, array $options = []): ActivitypubActor
|
||||||
{
|
{
|
||||||
$person = is_string($json) ? self::jsonToType($json) : $json;
|
$person = is_string($json) ? self::jsonToType($json) : $json;
|
||||||
|
|
||||||
@ -81,32 +81,39 @@ class Actor extends Model
|
|||||||
'modified' => new DateTime(),
|
'modified' => new DateTime(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$actor = new GSActor();
|
$actor = $options['objects']['Actor'] ?? new GSActor();
|
||||||
|
|
||||||
foreach ($actor_map as $prop => $val) {
|
foreach ($actor_map as $prop => $val) {
|
||||||
$set = Formatting::snakeCaseToCamelCase("set_{$prop}");
|
$set = Formatting::snakeCaseToCamelCase("set_{$prop}");
|
||||||
$actor->{$set}($val);
|
$actor->{$set}($val);
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::persist($actor);
|
if (!isset($options['objects']['Actor'])) {
|
||||||
|
DB::persist($actor);
|
||||||
|
}
|
||||||
|
|
||||||
// ActivityPub Actor
|
// ActivityPub Actor
|
||||||
$aprofile = ActivitypubActor::create([
|
$ap_actor = ActivitypubActor::create([
|
||||||
'inbox_uri' => $person->get('inbox'),
|
'inbox_uri' => $person->get('inbox'),
|
||||||
'inbox_shared_uri' => ($person->has('endpoints') && isset($person->get('endpoints')['sharedInbox'])) ? $person->get('endpoints')['sharedInbox'] : null,
|
'inbox_shared_uri' => ($person->has('endpoints') && isset($person->get('endpoints')['sharedInbox'])) ? $person->get('endpoints')['sharedInbox'] : null,
|
||||||
'uri' => $person->get('id'),
|
'uri' => $person->get('id'),
|
||||||
'actor_id' => $actor->getId(),
|
'actor_id' => $actor->getId(),
|
||||||
'url' => $person->get('url') ?? null,
|
'url' => $person->get('url') ?? null,
|
||||||
]);
|
], $options['objects']['ActivitypubActor'] ?? null);
|
||||||
|
|
||||||
DB::persist($aprofile);
|
if (!isset($options['objects']['ActivitypubActor'])) {
|
||||||
|
DB::persist($ap_actor);
|
||||||
|
}
|
||||||
|
|
||||||
// Public Key
|
// Public Key
|
||||||
$apRSA = ActivitypubRsa::create([
|
$apRSA = ActivitypubRsa::create([
|
||||||
'actor_id' => $actor->getID(),
|
'actor_id' => $actor->getID(),
|
||||||
'public_key' => ($person->has('publicKey') && isset($person->get('publicKey')['publicKeyPem'])) ? $person->get('publicKey')['publicKeyPem'] : null,
|
'public_key' => ($person->has('publicKey') && isset($person->get('publicKey')['publicKeyPem'])) ? $person->get('publicKey')['publicKeyPem'] : null,
|
||||||
]);
|
], $options['objects']['ActivitypubRsa'] ?? null);
|
||||||
|
|
||||||
DB::persist($apRSA);
|
if (!isset($options['objects']['ActivitypubRsa'])) {
|
||||||
|
DB::persist($apRSA);
|
||||||
|
}
|
||||||
|
|
||||||
// Avatar
|
// Avatar
|
||||||
//if (isset($res['icon']['url'])) {
|
//if (isset($res['icon']['url'])) {
|
||||||
@ -118,8 +125,8 @@ class Actor extends Model
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
Event::handle('ActivityPubNewActor', [&$aprofile, &$actor, &$apRSA]);
|
Event::handle('ActivityPubNewActor', [&$ap_actor, &$actor, &$apRSA]);
|
||||||
return $aprofile;
|
return $ap_actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,11 +58,18 @@ abstract class Entity
|
|||||||
public static function create(array $args, $obj = null)
|
public static function create(array $args, $obj = null)
|
||||||
{
|
{
|
||||||
$class = static::class;
|
$class = static::class;
|
||||||
$obj = $obj ?: new $class();
|
|
||||||
$date = new DateTime();
|
$date = new DateTime();
|
||||||
foreach (['created', 'modified'] as $prop) {
|
if (!is_null($obj)) { // Update modified
|
||||||
if (property_exists($class, $prop)) {
|
if (property_exists($class, 'modified')) {
|
||||||
$args[$prop] = $date;
|
$args['modified'] = $date;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$obj = new $class();
|
||||||
|
foreach (['created', 'modified'] as $prop) {
|
||||||
|
if (property_exists($class, $prop)) {
|
||||||
|
$args[$prop] = $date;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +82,7 @@ abstract class Entity
|
|||||||
throw new InvalidArgumentException($m);
|
throw new InvalidArgumentException($m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $obj;
|
return $obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user