diff --git a/plugins/ActivityPub/Controller/Inbox.php b/plugins/ActivityPub/Controller/Inbox.php index 16701c72e8..a866e3f1e7 100644 --- a/plugins/ActivityPub/Controller/Inbox.php +++ b/plugins/ActivityPub/Controller/Inbox.php @@ -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 diff --git a/plugins/ActivityPub/Entity/ActivitypubActor.php b/plugins/ActivityPub/Entity/ActivitypubActor.php index eb99f449a7..e30dec0285 100644 --- a/plugins/ActivityPub/Entity/ActivitypubActor.php +++ b/plugins/ActivityPub/Entity/ActivitypubActor.php @@ -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 [ diff --git a/plugins/ActivityPub/Util/Explorer.php b/plugins/ActivityPub/Util/Explorer.php index b9e235fde1..8b7bab20e0 100644 --- a/plugins/ActivityPub/Util/Explorer.php +++ b/plugins/ActivityPub/Util/Explorer.php @@ -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(); } } diff --git a/plugins/ActivityPub/Util/Model/Activity.php b/plugins/ActivityPub/Util/Model/Activity.php index d2272cd84e..1543fe3e4c 100644 --- a/plugins/ActivityPub/Util/Model/Activity.php +++ b/plugins/ActivityPub/Util/Model/Activity.php @@ -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); diff --git a/plugins/ActivityPub/Util/Model/Actor.php b/plugins/ActivityPub/Util/Model/Actor.php index 01a99c9a8b..c3c8832c5a 100644 --- a/plugins/ActivityPub/Util/Model/Actor.php +++ b/plugins/ActivityPub/Util/Model/Actor.php @@ -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; } /** diff --git a/src/Core/Entity.php b/src/Core/Entity.php index b380982516..8464d4c764 100644 --- a/src/Core/Entity.php +++ b/src/Core/Entity.php @@ -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; }