[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);
|
||||
|
||||
if ($type->has('actor') === false) {
|
||||
$error('Actor not found in the request.');
|
||||
return $error('Actor not found in the request.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -88,10 +88,11 @@ class Inbox extends Controller
|
|||
$actor = Actor::getById($ap_actor->getActorId());
|
||||
DB::flush();
|
||||
} 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();
|
||||
// Flattify headers
|
||||
|
@ -101,7 +102,7 @@ class Inbox extends Controller
|
|||
|
||||
if (!isset($headers['signature'])) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -110,22 +111,25 @@ class Inbox extends Controller
|
|||
Log::debug('ActivityPub Inbox: HTTP Signature Data: ' . print_r($signatureData, true));
|
||||
if (isset($signatureData['error'])) {
|
||||
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 ($verified !== 1) {
|
||||
try {
|
||||
$res = Explorer::get_remote_user_activity($ap_actor->getUri());
|
||||
if (is_null($res)) {
|
||||
return $error('Invalid remote actor.');
|
||||
}
|
||||
} catch (Exception) {
|
||||
$error('Invalid remote actor.');
|
||||
return $error('Invalid remote actor.');
|
||||
}
|
||||
try {
|
||||
$actor = ActivitypubActor::update_profile($ap_actor, $res);
|
||||
ActivitypubActor::update_profile($ap_actor, $actor, $activitypub_rsa, $res);
|
||||
} 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);
|
||||
|
@ -134,7 +138,7 @@ class Inbox extends Controller
|
|||
// If it still failed despite profile update
|
||||
if ($verified !== 1) {
|
||||
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
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace Plugin\ActivityPub\Entity;
|
|||
use App\Core\Cache;
|
||||
use App\Core\Entity;
|
||||
use App\Core\Log;
|
||||
use App\Entity\Actor;
|
||||
use Component\FreeNetwork\Util\Discovery;
|
||||
use DateTimeInterface;
|
||||
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
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -55,7 +55,7 @@ use const JSON_UNESCAPED_SLASHES;
|
|||
*/
|
||||
class Explorer
|
||||
{
|
||||
private array $discovered_actor_profiles = [];
|
||||
private array $discovered_activitypub_actor_profiles = [];
|
||||
|
||||
/**
|
||||
* 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);
|
||||
$this->discovered_actor_profiles = [];
|
||||
$this->discovered_activitypub_actor_profiles = [];
|
||||
|
||||
return $this->_lookup($url, $grab_online);
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ class Explorer
|
|||
* @throws ServerExceptionInterface
|
||||
* @throws TransportExceptionInterface
|
||||
*
|
||||
* @return array of Profile objects
|
||||
* @return array of ActivityPub Actor objects
|
||||
*/
|
||||
private function _lookup(string $url, bool $grab_online = true): array
|
||||
{
|
||||
|
@ -135,7 +135,7 @@ class Explorer
|
|||
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);
|
||||
|
||||
// We found something!
|
||||
$this->discovered_actor_profiles[] = $aprofile;
|
||||
$this->discovered_activitypub_actor_profiles[] = $aprofile;
|
||||
return true;
|
||||
} else {
|
||||
Log::debug('ActivityPub Explorer: Unable to find a known Aprofile for ' . $uri);
|
||||
|
@ -204,7 +204,7 @@ class Explorer
|
|||
return true;
|
||||
} else {
|
||||
try {
|
||||
$this->discovered_actor_profiles[] = Model\Actor::fromJson(json_encode($res));
|
||||
$this->discovered_activitypub_actor_profiles[] = Model\Actor::fromJson(json_encode($res));
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
Log::debug(
|
||||
|
@ -219,19 +219,6 @@ class Explorer
|
|||
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
|
||||
*
|
||||
|
@ -288,28 +275,20 @@ class Explorer
|
|||
* @throws RedirectionExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
* @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
|
||||
*/
|
||||
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]);
|
||||
// If it was deleted
|
||||
if ($response->getStatusCode() == 410) {
|
||||
return false;
|
||||
return null;
|
||||
} elseif (!HTTPClient::statusCodeIsOkay($response)) { // If it is unavailable
|
||||
throw new Exception('Non Ok Status Code for given Actor URL.');
|
||||
}
|
||||
$res = json_decode($response->getContent(), true);
|
||||
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.');
|
||||
return $response->getContent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ class Activity extends Model
|
|||
public static function fromJson(string|AbstractObject $json, array $options = []): ActivitypubActivity
|
||||
{
|
||||
$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) {
|
||||
'Create' => 'create',
|
||||
|
@ -82,15 +83,15 @@ class Activity extends Model
|
|||
$actor = ActivityPub::getActorByUri($type_activity->get('actor'));
|
||||
// Store Object
|
||||
$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.');
|
||||
}
|
||||
switch ($type_activity->get('object')['type']) {
|
||||
switch ($type_activity->get('object')->get('type')) {
|
||||
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;
|
||||
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.');
|
||||
}
|
||||
break;
|
||||
|
@ -100,20 +101,20 @@ class Activity extends Model
|
|||
$act = GSActivity::create([
|
||||
'actor_id' => $actor->getId(),
|
||||
'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(),
|
||||
'is_local' => false,
|
||||
'created' => new DateTime($activity['published'] ?? 'now'),
|
||||
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
||||
'source' => $source,
|
||||
]);
|
||||
DB::persist($act);
|
||||
// Store ActivityPub Activity
|
||||
$ap_act = ActivitypubActivity::create([
|
||||
'activity_id' => $act->getId(),
|
||||
'activity_uri' => $activity['id'],
|
||||
'object_uri' => $activity['object']['id'],
|
||||
'activity_uri' => $type_activity->get('id'),
|
||||
'object_uri' => $type_activity->get('object')->get('id'),
|
||||
'is_local' => false,
|
||||
'created' => new DateTime($activity['published'] ?? 'now'),
|
||||
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
||||
'modified' => new DateTime(),
|
||||
]);
|
||||
DB::persist($ap_act);
|
||||
|
|
|
@ -64,10 +64,10 @@ class Actor extends Model
|
|||
*
|
||||
* @param string|AbstractObject $json
|
||||
* @param array $options
|
||||
* @return GSActor
|
||||
* @return ActivitypubActor
|
||||
* @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;
|
||||
|
||||
|
@ -81,32 +81,39 @@ class Actor extends Model
|
|||
'modified' => new DateTime(),
|
||||
];
|
||||
|
||||
$actor = new GSActor();
|
||||
$actor = $options['objects']['Actor'] ?? new GSActor();
|
||||
|
||||
foreach ($actor_map as $prop => $val) {
|
||||
$set = Formatting::snakeCaseToCamelCase("set_{$prop}");
|
||||
$actor->{$set}($val);
|
||||
}
|
||||
|
||||
DB::persist($actor);
|
||||
if (!isset($options['objects']['Actor'])) {
|
||||
DB::persist($actor);
|
||||
}
|
||||
|
||||
// ActivityPub Actor
|
||||
$aprofile = ActivitypubActor::create([
|
||||
$ap_actor = ActivitypubActor::create([
|
||||
'inbox_uri' => $person->get('inbox'),
|
||||
'inbox_shared_uri' => ($person->has('endpoints') && isset($person->get('endpoints')['sharedInbox'])) ? $person->get('endpoints')['sharedInbox'] : null,
|
||||
'uri' => $person->get('id'),
|
||||
'actor_id' => $actor->getId(),
|
||||
'url' => $person->get('url') ?? null,
|
||||
]);
|
||||
], $options['objects']['ActivitypubActor'] ?? null);
|
||||
|
||||
DB::persist($aprofile);
|
||||
if (!isset($options['objects']['ActivitypubActor'])) {
|
||||
DB::persist($ap_actor);
|
||||
}
|
||||
|
||||
// Public Key
|
||||
$apRSA = ActivitypubRsa::create([
|
||||
'actor_id' => $actor->getID(),
|
||||
'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
|
||||
//if (isset($res['icon']['url'])) {
|
||||
|
@ -118,8 +125,8 @@ class Actor extends Model
|
|||
// }
|
||||
//}
|
||||
|
||||
Event::handle('ActivityPubNewActor', [&$aprofile, &$actor, &$apRSA]);
|
||||
return $aprofile;
|
||||
Event::handle('ActivityPubNewActor', [&$ap_actor, &$actor, &$apRSA]);
|
||||
return $ap_actor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -58,11 +58,18 @@ abstract class Entity
|
|||
public static function create(array $args, $obj = null)
|
||||
{
|
||||
$class = static::class;
|
||||
$obj = $obj ?: new $class();
|
||||
|
||||
$date = new DateTime();
|
||||
foreach (['created', 'modified'] as $prop) {
|
||||
if (property_exists($class, $prop)) {
|
||||
$args[$prop] = $date;
|
||||
if (!is_null($obj)) { // Update modified
|
||||
if (property_exists($class, 'modified')) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user