From 29961c4a806276020ecaaac393803ac6723e3db7 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 04:53:09 -0600 Subject: [PATCH 01/10] Update ap inbox --- .../ProfilePipeline/HandleUpdateActivity.php | 88 +++++++++++++ app/Util/ActivityPub/Inbox.php | 10 ++ .../Validator/UpdatePersonValidator.php | 119 ++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 app/Jobs/ProfilePipeline/HandleUpdateActivity.php create mode 100644 app/Util/ActivityPub/Validator/UpdatePersonValidator.php diff --git a/app/Jobs/ProfilePipeline/HandleUpdateActivity.php b/app/Jobs/ProfilePipeline/HandleUpdateActivity.php new file mode 100644 index 000000000..ffff068e5 --- /dev/null +++ b/app/Jobs/ProfilePipeline/HandleUpdateActivity.php @@ -0,0 +1,88 @@ +payload = $payload; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle(): void + { + $payload = $this->payload; + + if(empty($payload) || !isset($payload['actor'])) { + return; + } + + $profile = Profile::whereRemoteUrl($payload['actor'])->first(); + + if(!$profile || $profile->domain === null || $profile->private_key) { + return; + } + + if($profile->sharedInbox == null || $profile->sharedInbox != $payload['object']['endpoints']['sharedInbox']) { + $profile->sharedInbox = $payload['object']['endpoints']['sharedInbox']; + } + + if($profile->public_key !== $payload['object']['publicKey']['publicKeyPem']) { + $profile->public_key = $payload['object']['publicKey']['publicKeyPem']; + } + + if($profile->bio !== $payload['object']['summary']) { + $len = strlen(strip_tags($payload['object']['summary'])); + if($len) { + if($len > 500) { + $updated = strip_tags($payload['object']['summary']); + $updated = substr($updated, 0, config('pixelfed.max_bio_length')); + $profile->bio = Autolink::create()->autolink($updated); + } else { + $profile->bio = Purify::clean($payload['object']['summary']); + } + } else { + $profile->bio = null; + } + } + + if($profile->name !== $payload['object']['name']) { + $profile->name = Purify::clean(substr($payload['object']['name'], 0, config('pixelfed.max_name_length'))); + } + + if($profile->isDirty()) { + $profile->save(); + } + + RemoteAvatarFetch::dispatch($profile)->onQueue('low'); + + return; + } +} diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index 325d9b0c3..0caf8f25d 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -29,6 +29,7 @@ use App\Jobs\DeletePipeline\DeleteRemoteStatusPipeline; use App\Jobs\StoryPipeline\StoryExpire; use App\Jobs\StoryPipeline\StoryFetch; use App\Jobs\StatusPipeline\StatusRemoteUpdatePipeline; +use App\Jobs\ProfilePipeline\HandleUpdateActivity; use App\Util\ActivityPub\Validator\Accept as AcceptValidator; use App\Util\ActivityPub\Validator\Add as AddValidator; @@ -36,6 +37,7 @@ use App\Util\ActivityPub\Validator\Announce as AnnounceValidator; use App\Util\ActivityPub\Validator\Follow as FollowValidator; use App\Util\ActivityPub\Validator\Like as LikeValidator; use App\Util\ActivityPub\Validator\UndoFollow as UndoFollowValidator; +use App\Util\ActivityPub\Validator\UpdatePersonValidator; use App\Services\PollService; use App\Services\FollowerService; @@ -1217,10 +1219,18 @@ class Inbox return; } + if(!Helpers::validateUrl($activity['id'])) { + return; + } + if($activity['type'] === 'Note') { if(Status::whereObjectUrl($activity['id'])->exists()) { StatusRemoteUpdatePipeline::dispatch($activity); } + } else if ($activity['type'] === 'Person') { + if(UpdatePersonValidator::validate($this->payload)) { + HandleUpdateActivity::dispatch($this->payload)->onQueue('low'); + } } } } diff --git a/app/Util/ActivityPub/Validator/UpdatePersonValidator.php b/app/Util/ActivityPub/Validator/UpdatePersonValidator.php new file mode 100644 index 000000000..b8819d8fd --- /dev/null +++ b/app/Util/ActivityPub/Validator/UpdatePersonValidator.php @@ -0,0 +1,119 @@ + 'required', + 'id' => 'required|string|url', + 'type' => [ + 'required', + Rule::in(['Update']) + ], + 'actor' => 'required|url', + 'object' => 'required', + 'object.id' => [ + 'required', + 'url', + 'same:actor', + function (string $attribute, mixed $value, Closure $fail) use($payload) { + self::sameHost($attribute, $value, $fail, $payload['actor']); + }, + ], + 'object.type' => [ + 'required', + Rule::in(['Person']) + ], + 'object.publicKey' => 'required', + 'object.publicKey.id' => [ + 'required', + 'url', + function (string $attribute, mixed $value, Closure $fail) use($payload) { + self::sameHost($attribute, $value, $fail, $payload['actor']); + }, + ], + 'object.publicKey.owner' => [ + 'required', + 'url', + 'same:actor', + function (string $attribute, mixed $value, Closure $fail) use($payload) { + self::sameHost($attribute, $value, $fail, $payload['actor']); + }, + ], + 'object.publicKey.publicKeyPem' => 'required|string', + 'object.url' => [ + 'required', + 'url', + function (string $attribute, mixed $value, Closure $fail) use($payload) { + self::sameHost($attribute, $value, $fail, $payload['actor']); + }, + ], + 'object.summary' => 'required|string|nullable', + 'object.preferredUsername' => 'required|string', + 'object.name' => 'required|string|nullable', + 'object.inbox' => [ + 'required', + 'url', + function (string $attribute, mixed $value, Closure $fail) use($payload) { + self::sameHost($attribute, $value, $fail, $payload['actor']); + }, + ], + 'object.outbox' => [ + 'required', + 'url', + function (string $attribute, mixed $value, Closure $fail) use($payload) { + self::sameHost($attribute, $value, $fail, $payload['actor']); + }, + ], + 'object.following' => [ + 'required', + 'url', + function (string $attribute, mixed $value, Closure $fail) use($payload) { + self::sameHost($attribute, $value, $fail, $payload['actor']); + }, + ], + 'object.followers' => [ + 'required', + 'url', + function (string $attribute, mixed $value, Closure $fail) use($payload) { + self::sameHost($attribute, $value, $fail, $payload['actor']); + }, + ], + 'object.manuallyApprovesFollowers' => 'required', + 'object.icon' => 'sometimes|nullable', + 'object.icon.type' => 'sometimes|required_with:object.icon.url,object.icon.mediaType|in:Image', + 'object.icon.url' => 'sometimes|required_with:object.icon.type,object.icon.mediaType|url', + 'object.icon.mediaType' => 'sometimes|required_with:object.icon.url,object.icon.type|in:image/jpeg,image/png,image/jpg', + 'object.endpoints' => 'sometimes', + 'object.endpoints.sharedInbox' => [ + 'sometimes', + 'url', + function (string $attribute, mixed $value, Closure $fail) use($payload) { + self::sameHost($attribute, $value, $fail, $payload['actor']); + }, + ] + ])->passes(); + + return $valid; + } + + public static function sameHost(string $attribute, mixed $value, Closure $fail, string $comparedHost) + { + if(empty($value)) { + $fail('The ' . $attribute . ' is invalid or empty'); + } + $host = parse_url($value, PHP_URL_HOST); + $idHost = parse_url($comparedHost, PHP_URL_HOST); + if ($host !== $idHost) { + $fail('The ' . $attribute . ' is invalid'); + } + } +} From 0eb51ed74d930b2e5c9dc825a31247bc85c75ec1 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 05:07:14 -0600 Subject: [PATCH 02/10] Update RemoteAvatarFetch command --- app/Jobs/AvatarPipeline/RemoteAvatarFetch.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Jobs/AvatarPipeline/RemoteAvatarFetch.php b/app/Jobs/AvatarPipeline/RemoteAvatarFetch.php index bc90f9cf8..df972dd38 100644 --- a/app/Jobs/AvatarPipeline/RemoteAvatarFetch.php +++ b/app/Jobs/AvatarPipeline/RemoteAvatarFetch.php @@ -60,7 +60,7 @@ class RemoteAvatarFetch implements ShouldQueue { $profile = $this->profile; - if(config_cache('pixelfed.cloud_storage') == false && config_cache('federation.avatars.store_local') == false) { + if(boolval(config_cache('pixelfed.cloud_storage')) == false && boolval(config_cache('federation.avatars.store_local')) == false) { return 1; } @@ -108,7 +108,7 @@ class RemoteAvatarFetch implements ShouldQueue $avatar->remote_url = $icon['url']; $avatar->save(); - MediaStorageService::avatar($avatar, config_cache('pixelfed.cloud_storage') == false); + MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false); return 1; } From 31afaba3d0deeec671ccbbbcdd198992acb3637c Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 05:21:44 -0600 Subject: [PATCH 03/10] Update ProfilePipeline --- app/Jobs/ProfilePipeline/HandleUpdateActivity.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Jobs/ProfilePipeline/HandleUpdateActivity.php b/app/Jobs/ProfilePipeline/HandleUpdateActivity.php index ffff068e5..2c5a4456b 100644 --- a/app/Jobs/ProfilePipeline/HandleUpdateActivity.php +++ b/app/Jobs/ProfilePipeline/HandleUpdateActivity.php @@ -11,6 +11,7 @@ use Illuminate\Queue\SerializesModels; use App\Avatar; use App\Profile; use App\Util\ActivityPub\Helpers; +use Cache; use Purify; use App\Jobs\AvatarPipeline\RemoteAvatarFetch; use App\Util\Lexer\Autolink; @@ -81,7 +82,12 @@ class HandleUpdateActivity implements ShouldQueue $profile->save(); } - RemoteAvatarFetch::dispatch($profile)->onQueue('low'); + if(isset($payload['object']['icon'])) { + RemoteAvatarFetch::dispatch($profile)->onQueue('low'); + } else { + $profile->avatar->update(['remote_url' => null]); + Cache::forget('avatar:' . $profile->id); + } return; } From 1bf3ad7ed9bc2b49ffde8a862317f8263dc4e663 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 05:47:17 -0600 Subject: [PATCH 04/10] Update HandleUpdateActivity --- .../RemoteAvatarFetchFromUrl.php | 91 +++++++++++++++++++ .../ProfilePipeline/HandleUpdateActivity.php | 6 +- 2 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php diff --git a/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php new file mode 100644 index 000000000..cee52f6ea --- /dev/null +++ b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php @@ -0,0 +1,91 @@ +profile = $profile; + $this->url = $url; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $profile = $this->profile; + + if(boolval(config_cache('pixelfed.cloud_storage')) == false && boolval(config_cache('federation.avatars.store_local')) == false) { + return 1; + } + + if($profile->domain == null || $profile->private_key) { + return 1; + } + + $avatar = Avatar::whereProfileId($profile->id)->first(); + + if(!$avatar) { + $avatar = new Avatar; + $avatar->profile_id = $profile->id; + $avatar->is_remote = true; + $avatar->remote_url = $this->url; + $avatar->save(); + } else { + $avatar->remote_url = $this->url; + $avatar->is_remote = true; + $avatar->save(); + } + + MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false); + + return 1; + } +} diff --git a/app/Jobs/ProfilePipeline/HandleUpdateActivity.php b/app/Jobs/ProfilePipeline/HandleUpdateActivity.php index 2c5a4456b..3fe49b3d7 100644 --- a/app/Jobs/ProfilePipeline/HandleUpdateActivity.php +++ b/app/Jobs/ProfilePipeline/HandleUpdateActivity.php @@ -13,7 +13,7 @@ use App\Profile; use App\Util\ActivityPub\Helpers; use Cache; use Purify; -use App\Jobs\AvatarPipeline\RemoteAvatarFetch; +use App\Jobs\AvatarPipeline\RemoteAvatarFetchFromUrl; use App\Util\Lexer\Autolink; class HandleUpdateActivity implements ShouldQueue @@ -82,8 +82,8 @@ class HandleUpdateActivity implements ShouldQueue $profile->save(); } - if(isset($payload['object']['icon'])) { - RemoteAvatarFetch::dispatch($profile)->onQueue('low'); + if(isset($payload['object']['icon']) && isset($payload['object']['icon']['url'])) { + RemoteAvatarFetch::dispatch($profile, $payload['object']['icon']['url'])->onQueue('low'); } else { $profile->avatar->update(['remote_url' => null]); Cache::forget('avatar:' . $profile->id); From 5bea9034096a9645f919d018f0e4f1e96444e750 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 05:47:45 -0600 Subject: [PATCH 05/10] Update HandleUpdateActivity --- app/Jobs/ProfilePipeline/HandleUpdateActivity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Jobs/ProfilePipeline/HandleUpdateActivity.php b/app/Jobs/ProfilePipeline/HandleUpdateActivity.php index 3fe49b3d7..c8816e8a1 100644 --- a/app/Jobs/ProfilePipeline/HandleUpdateActivity.php +++ b/app/Jobs/ProfilePipeline/HandleUpdateActivity.php @@ -83,7 +83,7 @@ class HandleUpdateActivity implements ShouldQueue } if(isset($payload['object']['icon']) && isset($payload['object']['icon']['url'])) { - RemoteAvatarFetch::dispatch($profile, $payload['object']['icon']['url'])->onQueue('low'); + RemoteAvatarFetchFromUrl::dispatch($profile, $payload['object']['icon']['url'])->onQueue('low'); } else { $profile->avatar->update(['remote_url' => null]); Cache::forget('avatar:' . $profile->id); From ab9a8ba3143744ef3eea1d62c49fcab033091ace Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 05:50:08 -0600 Subject: [PATCH 06/10] Update Profile --- app/Profile.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/Profile.php b/app/Profile.php index 5c8d2ccbf..a7e4eff26 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -178,6 +178,14 @@ class Profile extends Model return url('/storage/avatars/default.jpg'); } + if( $avatar->is_remote && + $avatar->remote_url && + boolval(config_cache('pixelfed.cloud_storage')) == false && + boolval(config_cache('federation.avatars.store_local')) == true + ) { + return $avatar->remote_url; + } + if($path === 'public/avatars/default.jpg') { return url('/storage/avatars/default.jpg'); } From d6374cfe707246c2524c9d400ee3a9070a788b00 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 05:54:49 -0600 Subject: [PATCH 07/10] Update MediaStorageService --- app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php | 2 +- app/Services/MediaStorageService.php | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php index cee52f6ea..a3f88f009 100644 --- a/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php +++ b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php @@ -84,7 +84,7 @@ class RemoteAvatarFetchFromUrl implements ShouldQueue $avatar->save(); } - MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false); + MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true); return 1; } diff --git a/app/Services/MediaStorageService.php b/app/Services/MediaStorageService.php index a2a2c5058..60b194879 100644 --- a/app/Services/MediaStorageService.php +++ b/app/Services/MediaStorageService.php @@ -191,7 +191,7 @@ class MediaStorageService { unlink($tmpName); } - protected function fetchAvatar($avatar, $local = false) + protected function fetchAvatar($avatar, $local = false, $skipRecentCheck = false) { $url = $avatar->remote_url; $driver = $local ? 'local' : config('filesystems.cloud'); @@ -215,8 +215,10 @@ class MediaStorageService { $mime = $head['mime']; $max_size = (int) config('pixelfed.max_avatar_size') * 1000; - if($avatar->last_fetched_at && $avatar->last_fetched_at->gt(now()->subDay())) { - return; + if(!$skipRecentCheck) { + if($avatar->last_fetched_at && $avatar->last_fetched_at->gt(now()->subDay())) { + return; + } } // handle pleroma edge case From e8d4ce1888c9c7846209eb6feaab01175a174b2c Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 06:01:49 -0600 Subject: [PATCH 08/10] Update MediaStorageService --- app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php | 5 +++++ app/Services/MediaStorageService.php | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php index a3f88f009..932372a50 100644 --- a/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php +++ b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php @@ -13,9 +13,11 @@ use App\Util\ActivityPub\Helpers; use Illuminate\Support\Str; use Zttp\Zttp; use App\Http\Controllers\AvatarController; +use Cache; use Storage; use Log; use Illuminate\Http\File; +use App\Services\AccountService; use App\Services\MediaStorageService; use App\Services\ActivityPubFetchService; @@ -84,6 +86,9 @@ class RemoteAvatarFetchFromUrl implements ShouldQueue $avatar->save(); } + Cache::forget('avatar:' . $avatar->profile_id); + AccountService::del($avatar->profile_id); + MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true); return 1; diff --git a/app/Services/MediaStorageService.php b/app/Services/MediaStorageService.php index 60b194879..fd70e3a0f 100644 --- a/app/Services/MediaStorageService.php +++ b/app/Services/MediaStorageService.php @@ -221,6 +221,9 @@ class MediaStorageService { } } + Cache::forget('avatar:' . $avatar->profile_id); + AccountService::del($avatar->profile_id); + // handle pleroma edge case if(Str::endsWith($mime, '; charset=utf-8')) { $mime = str_replace('; charset=utf-8', '', $mime); @@ -268,7 +271,7 @@ class MediaStorageService { $avatar->save(); Cache::forget('avatar:' . $avatar->profile_id); - Cache::forget(AccountService::CACHE_KEY . $avatar->profile_id); + AccountService::del($avatar->profile_id); unlink($tmpName); } From e3be04db2b70de0042d40829ada677cfca716555 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 06:08:00 -0600 Subject: [PATCH 09/10] Update HandleUpdateActivity --- app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php index 932372a50..259058385 100644 --- a/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php +++ b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php @@ -64,6 +64,9 @@ class RemoteAvatarFetchFromUrl implements ShouldQueue { $profile = $this->profile; + Cache::forget('avatar:' . $profile->id); + AccountService::del($profile->id); + if(boolval(config_cache('pixelfed.cloud_storage')) == false && boolval(config_cache('federation.avatars.store_local')) == false) { return 1; } @@ -86,8 +89,6 @@ class RemoteAvatarFetchFromUrl implements ShouldQueue $avatar->save(); } - Cache::forget('avatar:' . $avatar->profile_id); - AccountService::del($avatar->profile_id); MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true); From 0f72b33c0e997d64c8da7f1d6c9cb1c51dd8fbb0 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 6 Jun 2023 06:12:24 -0600 Subject: [PATCH 10/10] Add tests --- .../Unit/ActivityPub/StoryValidationTest.php | 2 + .../UpdatePersonValidationTest.php | 161 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 tests/Unit/ActivityPub/UpdatePersonValidationTest.php diff --git a/tests/Unit/ActivityPub/StoryValidationTest.php b/tests/Unit/ActivityPub/StoryValidationTest.php index 29b5d2cd2..6368e9c07 100644 --- a/tests/Unit/ActivityPub/StoryValidationTest.php +++ b/tests/Unit/ActivityPub/StoryValidationTest.php @@ -7,6 +7,8 @@ use PHPUnit\Framework\TestCase; class StoryValidationTest extends TestCase { + public $activity; + public function setUp(): void { parent::setUp(); diff --git a/tests/Unit/ActivityPub/UpdatePersonValidationTest.php b/tests/Unit/ActivityPub/UpdatePersonValidationTest.php new file mode 100644 index 000000000..afb42bc46 --- /dev/null +++ b/tests/Unit/ActivityPub/UpdatePersonValidationTest.php @@ -0,0 +1,161 @@ +activity = json_decode('{"type":"Update","object":{"url":"http://mastodon.example.org/@gargron","type":"Person","summary":"

Some bio

","publicKey":{"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0gs3VnQf6am3R+CeBV4H\nlfI1HZTNRIBHgvFszRZkCERbRgEWMu+P+I6/7GJC5H5jhVQ60z4MmXcyHOGmYMK/\n5XyuHQz7V2Ssu1AxLfRN5Biq1ayb0+DT/E7QxNXDJPqSTnstZ6C7zKH/uAETqg3l\nBonjCQWyds+IYbQYxf5Sp3yhvQ80lMwHML3DaNCMlXWLoOnrOX5/yK5+dedesg2\n/HIvGk+HEt36vm6hoH7bwPuEkgA++ACqwjXRe5Mta7i3eilHxFaF8XIrJFARV0t\nqOu4GID/jG6oA+swIWndGrtR2QRJIt9QIBFfK3HG5M0koZbY1eTqwNFRHFL3xaD\nUQIDAQAB\n-----END PUBLIC KEY-----\n","owner":"http://mastodon.example.org/users/gargron","id":"http://mastodon.example.org/users/gargron#main-key"},"preferredUsername":"gargron","outbox":"http://mastodon.example.org/users/gargron/outbox","name":"gargle","manuallyApprovesFollowers":false,"inbox":"http://mastodon.example.org/users/gargron/inbox","id":"http://mastodon.example.org/users/gargron","following":"http://mastodon.example.org/users/gargron/following","followers":"http://mastodon.example.org/users/gargron/followers","endpoints":{"sharedInbox":"http://mastodon.example.org/inbox"},"attachment":[{"type":"PropertyValue","name":"foo","value":"updated"},{"type":"PropertyValue","name":"foo1","value":"updated"}],"icon":{"type":"Image","mediaType":"image/jpeg","url":"https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"},"image":{"type":"Image","mediaType":"image/png","url":"https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}},"id":"http://mastodon.example.org/users/gargron#updates/1519563538","actor":"http://mastodon.example.org/users/gargron","@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"toot":"http://joinmastodon.org/ns#","sensitive":"as:sensitive","ostatus":"http://ostatus.org#","movedTo":"as:movedTo","manuallyApprovesFollowers":"as:manuallyApprovesFollowers","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","atomUri":"ostatus:atomUri","Hashtag":"as:Hashtag","Emoji":"toot:Emoji"}]}', true); + } + + /** @test */ + public function schemaTest() + { + $this->assertTrue(UpdatePersonValidator::validate($this->activity)); + } + + /** @test */ + public function invalidContext() + { + $activity = $this->activity; + unset($activity['@context']); + $activity['@@context'] = 'https://www.w3.org/ns/activitystreams'; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function missingContext() + { + $activity = $this->activity; + unset($activity['@context']); + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function missingId() + { + $activity = $this->activity; + unset($activity['id']); + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function missingType() + { + $activity = $this->activity; + unset($activity['type']); + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function invalidType() + { + $activity = $this->activity; + $activity['type'] = 'Create'; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function invalidObjectType() + { + $activity = $this->activity; + $activity['object']['type'] = 'Note'; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function invalidActorMatchingObjectId() + { + $activity = $this->activity; + $activity['object']['id'] = 'https://example.org/@user'; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function invalidActorUrlMatchingObjectId() + { + $activity = $this->activity; + $activity['object']['id'] = $activity['object']['id'] . 'test'; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function missingActorPublicKey() + { + $activity = $this->activity; + unset($activity['object']['publicKey']); + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function invalidActorPublicKey() + { + $activity = $this->activity; + $activity['object']['publicKey'] = null; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function invalidActorPublicKeyId() + { + $activity = $this->activity; + $activity['object']['publicKey']['id'] = null; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function invalidActorPublicKeyIdHost() + { + $activity = $this->activity; + $activity['object']['publicKey']['id'] = 'https://example.org/test'; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function invalidActorAvatar() + { + $activity = $this->activity; + $activity['object']['icon']['type'] = 'TikTok'; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function invalidActorAvatarMediaType() + { + $activity = $this->activity; + $activity['object']['icon']['mediaType'] = 'video/mp4'; + $this->assertFalse(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function validActorAvatarMediaTypePng() + { + $activity = $this->activity; + $activity['object']['icon']['mediaType'] = 'image/png'; + $this->assertTrue(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function validActorAvatarMediaTypeJpeg() + { + $activity = $this->activity; + $activity['object']['icon']['mediaType'] = 'image/jpeg'; + $this->assertTrue(UpdatePersonValidator::validate($activity)); + } + + /** @test */ + public function validActorAvatarMediaUrl() + { + $activity = $this->activity; + $activity['object']['icon']['url'] = 'http://example.org/avatar.png'; + $this->assertTrue(UpdatePersonValidator::validate($activity)); + } +}