diff --git a/.env.test b/.env.test index 3ffdf339a..1d1169dfe 100644 --- a/.env.test +++ b/.env.test @@ -18,7 +18,7 @@ KBIN_DEFAULT_LANG=en KBIN_DOMAIN=kbin.test ELASTICSEARCH_ENABLED=false KBIN_API_ITEMS_PER_PAGE=2 -KBIN_FEDERATION_ENABLED=false +KBIN_FEDERATION_ENABLED=true ###> league/oauth2-server-bundle ### OAUTH_PRIVATE_KEY=%kernel.project_dir%/config/oauth2/tests/private.pem diff --git a/src/Command/ApImportObject.php b/src/Command/ApImportObject.php index 924d8186e..9ad97d1ac 100644 --- a/src/Command/ApImportObject.php +++ b/src/Command/ApImportObject.php @@ -5,7 +5,7 @@ namespace App\Command; use App\Message\ActivityPub\Inbox\ActivityMessage; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -21,7 +21,7 @@ class ApImportObject extends Command { public function __construct( private readonly MessageBusInterface $bus, - private readonly ApHttpClient $client, + private readonly ApHttpClientInterface $client, ) { parent::__construct(); } diff --git a/src/Controller/ActivityPub/WebFingerController.php b/src/Controller/ActivityPub/WebFingerController.php index 37a3c80d1..87af69bef 100644 --- a/src/Controller/ActivityPub/WebFingerController.php +++ b/src/Controller/ActivityPub/WebFingerController.php @@ -6,19 +6,26 @@ use App\ActivityPub\JsonRd; use App\Event\ActivityPub\WebfingerResponseEvent; +use App\Service\ActivityPub\Webfinger\WebFingerParameters; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; class WebFingerController { - public function __construct(private readonly EventDispatcherInterface $eventDispatcher) - { + public function __construct( + private readonly EventDispatcherInterface $eventDispatcher, + private readonly WebFingerParameters $webFingerParameters, + ) { } public function __invoke(Request $request): JsonResponse { - $event = new WebfingerResponseEvent(new JsonRd(), $request); + $event = new WebfingerResponseEvent( + new JsonRd(), + $request->query->get('resource') ?: '', + $this->webFingerParameters->getParams($request), + ); $this->eventDispatcher->dispatch($event); if (!empty($event->jsonRd->getLinks())) { diff --git a/src/Controller/SearchController.php b/src/Controller/SearchController.php index 9a6f42248..a8426b855 100644 --- a/src/Controller/SearchController.php +++ b/src/Controller/SearchController.php @@ -9,7 +9,7 @@ use App\Entity\User; use App\Message\ActivityPub\Inbox\CreateMessage; use App\MessageHandler\ActivityPub\Inbox\CreateHandler; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPubManager; use App\Service\SearchManager; use App\Service\SettingsManager; @@ -23,7 +23,7 @@ class SearchController extends AbstractController public function __construct( private readonly SearchManager $manager, private readonly ActivityPubManager $activityPubManager, - private readonly ApHttpClient $apHttpClient, + private readonly ApHttpClientInterface $apHttpClient, private readonly SubjectOverviewManager $overviewManager, private readonly SettingsManager $settingsManager, private readonly LoggerInterface $logger, diff --git a/src/Entity/User.php b/src/Entity/User.php index d9e786581..6e081f528 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -11,7 +11,7 @@ use App\Entity\Traits\CreatedAtTrait; use App\Entity\Traits\VisibilityTrait; use App\Repository\UserRepository; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; @@ -869,7 +869,7 @@ public function hasMagazineOwnershipRequest(Magazine $magazine): bool return $this->magazineOwnershipRequests->matching($criteria)->count() > 0; } - public function getFollowerUrl(ApHttpClient $client, UrlGeneratorInterface $urlGenerator, bool $isRemote): ?string + public function getFollowerUrl(ApHttpClientInterface $client, UrlGeneratorInterface $urlGenerator, bool $isRemote): ?string { if ($isRemote) { $actorObject = $client->getActorObject($this->apProfileId); diff --git a/src/Event/ActivityPub/WebfingerResponseEvent.php b/src/Event/ActivityPub/WebfingerResponseEvent.php index 7af331408..75761e770 100644 --- a/src/Event/ActivityPub/WebfingerResponseEvent.php +++ b/src/Event/ActivityPub/WebfingerResponseEvent.php @@ -5,13 +5,13 @@ namespace App\Event\ActivityPub; use App\ActivityPub\JsonRd; -use Symfony\Component\HttpFoundation\Request; class WebfingerResponseEvent { public function __construct( public JsonRd $jsonRd, - public Request $request, + public string $subject, + public array $params, ) { } } diff --git a/src/EventSubscriber/ActivityPub/GroupWebFingerProfileSubscriber.php b/src/EventSubscriber/ActivityPub/GroupWebFingerProfileSubscriber.php index e0f81f692..14a8ee15e 100644 --- a/src/EventSubscriber/ActivityPub/GroupWebFingerProfileSubscriber.php +++ b/src/EventSubscriber/ActivityPub/GroupWebFingerProfileSubscriber.php @@ -16,7 +16,6 @@ class GroupWebFingerProfileSubscriber implements EventSubscriberInterface { public function __construct( - private readonly WebFingerParameters $webfingerParameters, private readonly MagazineRepository $magazineRepository, private readonly UrlGeneratorInterface $urlGenerator, ) { @@ -32,7 +31,7 @@ public static function getSubscribedEvents(): array public function buildResponse(WebfingerResponseEvent $event): void { - $params = $this->webfingerParameters->getParams($event->request); + $params = $event->params; $jsonRd = $event->jsonRd; if ( diff --git a/src/EventSubscriber/ActivityPub/GroupWebFingerSubscriber.php b/src/EventSubscriber/ActivityPub/GroupWebFingerSubscriber.php index fa6cb9f9e..7913ba7cf 100644 --- a/src/EventSubscriber/ActivityPub/GroupWebFingerSubscriber.php +++ b/src/EventSubscriber/ActivityPub/GroupWebFingerSubscriber.php @@ -16,7 +16,6 @@ class GroupWebFingerSubscriber implements EventSubscriberInterface { public function __construct( - private readonly WebFingerParameters $webfingerParameters, private readonly MagazineRepository $magazineRepository, private readonly UrlGeneratorInterface $urlGenerator, ) { @@ -32,11 +31,10 @@ public static function getSubscribedEvents(): array public function buildResponse(WebfingerResponseEvent $event): void { - $request = $event->request; - $params = $this->webfingerParameters->getParams($request); + $params = $event->params; $jsonRd = $event->jsonRd; - $subject = $request->query->get('resource') ?: ''; + $subject = $event->subject; if (!empty($subject)) { $jsonRd->setSubject($subject); } diff --git a/src/EventSubscriber/ActivityPub/UserWebFingerProfileSubscriber.php b/src/EventSubscriber/ActivityPub/UserWebFingerProfileSubscriber.php index 078788371..c6e1f2b59 100644 --- a/src/EventSubscriber/ActivityPub/UserWebFingerProfileSubscriber.php +++ b/src/EventSubscriber/ActivityPub/UserWebFingerProfileSubscriber.php @@ -19,7 +19,6 @@ class UserWebFingerProfileSubscriber implements EventSubscriberInterface { public function __construct( - private readonly WebFingerParameters $webfingerParameters, private readonly UserRepository $userRepository, private readonly UrlGeneratorInterface $urlGenerator, private readonly SettingsManager $settingsManager, @@ -38,7 +37,7 @@ public static function getSubscribedEvents(): array public function buildResponse(WebfingerResponseEvent $event): void { - $params = $this->webfingerParameters->getParams($event->request); + $params = $event->params; $jsonRd = $event->jsonRd; if (isset($params[WebFingerParameters::ACCOUNT_KEY_NAME])) { diff --git a/src/EventSubscriber/ActivityPub/UserWebFingerSubscriber.php b/src/EventSubscriber/ActivityPub/UserWebFingerSubscriber.php index 19eb7faba..6cd9d7ac4 100644 --- a/src/EventSubscriber/ActivityPub/UserWebFingerSubscriber.php +++ b/src/EventSubscriber/ActivityPub/UserWebFingerSubscriber.php @@ -16,7 +16,6 @@ class UserWebFingerSubscriber implements EventSubscriberInterface { public function __construct( - private readonly WebFingerParameters $webfingerParameters, private readonly UserRepository $userRepository, private readonly UrlGeneratorInterface $urlGenerator, ) { @@ -32,11 +31,10 @@ public static function getSubscribedEvents(): array public function buildResponse(WebfingerResponseEvent $event): void { - $request = $event->request; - $params = $this->webfingerParameters->getParams($request); + $params = $event->params; $jsonRd = $event->jsonRd; - $subject = $request->query->get('resource') ?: ''; + $subject = $event->subject; if (!empty($subject)) { $jsonRd->setSubject($subject); } diff --git a/src/Factory/ActivityPub/EntryCommentNoteFactory.php b/src/Factory/ActivityPub/EntryCommentNoteFactory.php index 4dbec1556..727e3d9a3 100644 --- a/src/Factory/ActivityPub/EntryCommentNoteFactory.php +++ b/src/Factory/ActivityPub/EntryCommentNoteFactory.php @@ -8,7 +8,7 @@ use App\Entity\EntryComment; use App\Markdown\MarkdownConverter; use App\Markdown\RenderTarget; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\ContextsProvider; use App\Service\ActivityPub\Wrapper\ImageWrapper; use App\Service\ActivityPub\Wrapper\MentionsWrapper; @@ -28,7 +28,7 @@ public function __construct( private readonly MentionsWrapper $mentionsWrapper, private readonly MentionManager $mentionManager, private readonly EntryPageFactory $pageFactory, - private readonly ApHttpClient $client, + private readonly ApHttpClientInterface $client, private readonly ActivityPubManager $activityPubManager, private readonly MarkdownConverter $markdownConverter, ) { diff --git a/src/Factory/ActivityPub/EntryPageFactory.php b/src/Factory/ActivityPub/EntryPageFactory.php index 77f4b2aad..e0a74c2f4 100644 --- a/src/Factory/ActivityPub/EntryPageFactory.php +++ b/src/Factory/ActivityPub/EntryPageFactory.php @@ -8,7 +8,7 @@ use App\Entity\Entry; use App\Markdown\MarkdownConverter; use App\Markdown\RenderTarget; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\ContextsProvider; use App\Service\ActivityPub\Wrapper\ImageWrapper; use App\Service\ActivityPub\Wrapper\MentionsWrapper; @@ -27,7 +27,7 @@ public function __construct( private readonly ImageWrapper $imageWrapper, private readonly TagsWrapper $tagsWrapper, private readonly MentionsWrapper $mentionsWrapper, - private readonly ApHttpClient $client, + private readonly ApHttpClientInterface $client, private readonly ActivityPubManager $activityPubManager, private readonly MarkdownConverter $markdownConverter, ) { diff --git a/src/Factory/ActivityPub/InstanceFactory.php b/src/Factory/ActivityPub/InstanceFactory.php index 27528360f..cd0cb5152 100644 --- a/src/Factory/ActivityPub/InstanceFactory.php +++ b/src/Factory/ActivityPub/InstanceFactory.php @@ -4,7 +4,7 @@ namespace App\Factory\ActivityPub; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\ContextsProvider; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -12,7 +12,7 @@ class InstanceFactory { public function __construct( private string $kbinDomain, - private readonly ApHttpClient $client, + private readonly ApHttpClientInterface $client, private readonly UrlGeneratorInterface $urlGenerator, private readonly ContextsProvider $contextProvider, ) { diff --git a/src/Factory/ActivityPub/PostCommentNoteFactory.php b/src/Factory/ActivityPub/PostCommentNoteFactory.php index 62d9b43d2..52e932019 100644 --- a/src/Factory/ActivityPub/PostCommentNoteFactory.php +++ b/src/Factory/ActivityPub/PostCommentNoteFactory.php @@ -8,7 +8,7 @@ use App\Entity\PostComment; use App\Markdown\MarkdownConverter; use App\Markdown\RenderTarget; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\ContextsProvider; use App\Service\ActivityPub\Wrapper\ImageWrapper; use App\Service\ActivityPub\Wrapper\MentionsWrapper; @@ -28,7 +28,7 @@ public function __construct( private readonly TagsWrapper $tagsWrapper, private readonly MentionsWrapper $mentionsWrapper, private readonly MentionManager $mentionManager, - private readonly ApHttpClient $client, + private readonly ApHttpClientInterface $client, private readonly ActivityPubManager $activityPubManager, private readonly MarkdownConverter $markdownConverter, ) { diff --git a/src/Factory/ActivityPub/PostNoteFactory.php b/src/Factory/ActivityPub/PostNoteFactory.php index 4edd13819..73f6ba73e 100644 --- a/src/Factory/ActivityPub/PostNoteFactory.php +++ b/src/Factory/ActivityPub/PostNoteFactory.php @@ -8,7 +8,7 @@ use App\Entity\Post; use App\Markdown\MarkdownConverter; use App\Markdown\RenderTarget; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\ContextsProvider; use App\Service\ActivityPub\Wrapper\ImageWrapper; use App\Service\ActivityPub\Wrapper\MentionsWrapper; @@ -27,7 +27,7 @@ public function __construct( private readonly ImageWrapper $imageWrapper, private readonly TagsWrapper $tagsWrapper, private readonly MentionsWrapper $mentionsWrapper, - private readonly ApHttpClient $client, + private readonly ApHttpClientInterface $client, private readonly ActivityPubManager $activityPubManager, private readonly MentionManager $mentionManager, private readonly TagExtractor $tagExtractor, diff --git a/src/MessageHandler/ActivityPub/Inbox/ActivityHandler.php b/src/MessageHandler/ActivityPub/Inbox/ActivityHandler.php index 473a819a4..3ca978e38 100644 --- a/src/MessageHandler/ActivityPub/Inbox/ActivityHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/ActivityHandler.php @@ -23,7 +23,7 @@ use App\Message\Contracts\MessageInterface; use App\MessageHandler\MbinMessageHandler; use App\Repository\InstanceRepository; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\SignatureValidator; use App\Service\ActivityPubManager; use App\Service\RemoteInstanceManager; @@ -45,7 +45,7 @@ public function __construct( private readonly SettingsManager $settingsManager, private readonly MessageBusInterface $bus, private readonly ActivityPubManager $activityPubManager, - private readonly ApHttpClient $apHttpClient, + private readonly ApHttpClientInterface $apHttpClient, private readonly InstanceRepository $instanceRepository, private readonly RemoteInstanceManager $remoteInstanceManager, private readonly LoggerInterface $logger, diff --git a/src/MessageHandler/ActivityPub/Inbox/AddHandler.php b/src/MessageHandler/ActivityPub/Inbox/AddHandler.php index 25164012b..40a62ab6c 100644 --- a/src/MessageHandler/ActivityPub/Inbox/AddHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/AddHandler.php @@ -15,7 +15,7 @@ use App\Repository\ApActivityRepository; use App\Repository\EntryRepository; use App\Repository\MagazineRepository; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPubManager; use App\Service\EntryManager; use App\Service\MagazineManager; @@ -33,7 +33,7 @@ public function __construct( private readonly EntityManagerInterface $entityManager, private readonly KernelInterface $kernel, private readonly ActivityPubManager $activityPubManager, - private readonly ApHttpClient $apHttpClient, + private readonly ApHttpClientInterface $apHttpClient, private readonly ApActivityRepository $apActivityRepository, private readonly MagazineRepository $magazineRepository, private readonly MagazineManager $magazineManager, diff --git a/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php b/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php index d0970bff8..6334a9ce2 100644 --- a/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php @@ -20,7 +20,7 @@ use App\Message\Contracts\MessageInterface; use App\MessageHandler\MbinMessageHandler; use App\Repository\ApActivityRepository; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\Note; use App\Service\ActivityPub\Page; use App\Service\SettingsManager; @@ -37,7 +37,7 @@ public function __construct( private readonly EntityManagerInterface $entityManager, private readonly KernelInterface $kernel, private readonly LoggerInterface $logger, - private readonly ApHttpClient $client, + private readonly ApHttpClientInterface $client, private readonly MessageBusInterface $bus, private readonly ApActivityRepository $repository, private readonly Note $note, diff --git a/src/MessageHandler/ActivityPub/Inbox/FollowHandler.php b/src/MessageHandler/ActivityPub/Inbox/FollowHandler.php index a31f0651c..6cb0a38de 100644 --- a/src/MessageHandler/ActivityPub/Inbox/FollowHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/FollowHandler.php @@ -9,7 +9,7 @@ use App\Message\ActivityPub\Inbox\FollowMessage; use App\Message\Contracts\MessageInterface; use App\MessageHandler\MbinMessageHandler; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\Wrapper\FollowResponseWrapper; use App\Service\ActivityPubManager; use App\Service\MagazineManager; @@ -28,7 +28,7 @@ public function __construct( private readonly ActivityPubManager $activityPubManager, private readonly UserManager $userManager, private readonly MagazineManager $magazineManager, - private readonly ApHttpClient $client, + private readonly ApHttpClientInterface $client, private readonly LoggerInterface $logger, private readonly FollowResponseWrapper $followResponseWrapper, ) { diff --git a/src/MessageHandler/ActivityPub/Outbox/DeliverHandler.php b/src/MessageHandler/ActivityPub/Outbox/DeliverHandler.php index 16b0841c1..e24cd00fe 100644 --- a/src/MessageHandler/ActivityPub/Outbox/DeliverHandler.php +++ b/src/MessageHandler/ActivityPub/Outbox/DeliverHandler.php @@ -12,7 +12,7 @@ use App\Message\Contracts\MessageInterface; use App\MessageHandler\MbinMessageHandler; use App\Repository\InstanceRepository; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPubManager; use App\Service\SettingsManager; use Doctrine\ORM\EntityManagerInterface; @@ -32,7 +32,7 @@ class DeliverHandler extends MbinMessageHandler public function __construct( private readonly EntityManagerInterface $entityManager, private readonly KernelInterface $kernel, - private readonly ApHttpClient $client, + private readonly ApHttpClientInterface $client, private readonly ActivityPubManager $activityPubManager, private readonly SettingsManager $settingsManager, private readonly LoggerInterface $logger, diff --git a/src/MessageHandler/ActivityPub/Outbox/FollowHandler.php b/src/MessageHandler/ActivityPub/Outbox/FollowHandler.php index 4280c1b1f..6bd127247 100644 --- a/src/MessageHandler/ActivityPub/Outbox/FollowHandler.php +++ b/src/MessageHandler/ActivityPub/Outbox/FollowHandler.php @@ -9,7 +9,7 @@ use App\MessageHandler\MbinMessageHandler; use App\Repository\MagazineRepository; use App\Repository\UserRepository; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\Wrapper\FollowWrapper; use App\Service\ActivityPub\Wrapper\UndoWrapper; use App\Service\ActivityPubManager; @@ -30,7 +30,7 @@ public function __construct( private readonly ActivityPubManager $activityPubManager, private readonly FollowWrapper $followWrapper, private readonly UndoWrapper $undoWrapper, - private readonly ApHttpClient $apHttpClient, + private readonly ApHttpClientInterface $apHttpClient, private readonly SettingsManager $settingsManager, private readonly DeliverManager $deliverManager, ) { diff --git a/src/MessageHandler/ActivityPub/UpdateActorHandler.php b/src/MessageHandler/ActivityPub/UpdateActorHandler.php index 365121d98..37a20d1df 100644 --- a/src/MessageHandler/ActivityPub/UpdateActorHandler.php +++ b/src/MessageHandler/ActivityPub/UpdateActorHandler.php @@ -9,7 +9,7 @@ use App\MessageHandler\MbinMessageHandler; use App\Repository\MagazineRepository; use App\Repository\UserRepository; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPubManager; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -24,7 +24,7 @@ public function __construct( private readonly EntityManagerInterface $entityManager, private readonly KernelInterface $kernel, private readonly ActivityPubManager $activityPubManager, - private readonly ApHttpClient $apHttpClient, + private readonly ApHttpClientInterface $apHttpClient, private readonly LockFactory $lockFactory, private readonly UserRepository $userRepository, private readonly MagazineRepository $magazineRepository, diff --git a/src/Service/ActivityPub/ApHttpClient.php b/src/Service/ActivityPub/ApHttpClient.php index 4e097c1c0..4975da3b8 100644 --- a/src/Service/ActivityPub/ApHttpClient.php +++ b/src/Service/ActivityPub/ApHttpClient.php @@ -41,7 +41,7 @@ enum ApRequestType case NodeInfo; } -class ApHttpClient +class ApHttpClient implements ApHttpClientInterface { public const TIMEOUT = 8; public const MAX_DURATION = 15; diff --git a/src/Service/ActivityPub/ApHttpClientInterface.php b/src/Service/ActivityPub/ApHttpClientInterface.php new file mode 100644 index 000000000..643900410 --- /dev/null +++ b/src/Service/ActivityPub/ApHttpClientInterface.php @@ -0,0 +1,97 @@ +entityManager->persist($newMod); $magazine = $this->magazineManager->create($dto, $newMod); + $this->entityManager->persist($magazine); $this->magazines->add($magazine); diff --git a/tests/Functional/Controller/Api/Search/SearchApiTest.php b/tests/Functional/Controller/Api/Search/SearchApiTest.php index d33bc6c91..3be3afa13 100644 --- a/tests/Functional/Controller/Api/Search/SearchApiTest.php +++ b/tests/Functional/Controller/Api/Search/SearchApiTest.php @@ -5,6 +5,7 @@ namespace App\Tests\Functional\Controller\Api\Search; use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\SettingsManager; use App\Tests\WebTestCase; use phpseclib3\Crypt\RSA; @@ -395,6 +396,6 @@ private function setCacheKeysForApHttpClient(string $domain, ?LoggerInterface $l $this->siteRepository, $this->projectInfoService, ); - self::getContainer()->set(ApHttpClient::class, $apHttpClient); + self::getContainer()->set(ApHttpClientInterface::class, $apHttpClient); } } diff --git a/tests/TestingApHttpClient.php b/tests/TestingApHttpClient.php new file mode 100644 index 000000000..1c0c9fe5b --- /dev/null +++ b/tests/TestingApHttpClient.php @@ -0,0 +1,128 @@ + $activityObjects + */ + public array $activityObjects = []; + + /** + * @phpstan-var array $collectionObjects + */ + public array $collectionObjects = []; + + /** + * @phpstan-var array $webfingerObjects + */ + public array $webfingerObjects = []; + + /** + * @phpstan-var array $actorObjects + */ + public array $actorObjects = []; + + /** + * @var array + */ + private array $postedObjects = []; + + public function getActivityObject(string $url, bool $decoded = true): array|string|null + { + if (\array_key_exists($url, $this->activityObjects)) { + return $this->activityObjects[$url]; + } + + return null; + } + + public function getCollectionObject(string $apAddress): ?array + { + if (\array_key_exists($apAddress, $this->collectionObjects)) { + return $this->collectionObjects[$apAddress]; + } + + return null; + } + + public function getActorObject(string $apProfileId): ?array + { + if (\array_key_exists($apProfileId, $this->actorObjects)) { + return $this->actorObjects[$apProfileId]; + } + + return null; + } + + public function getWebfingerObject(string $url): ?array + { + if (\array_key_exists($url, $this->webfingerObjects)) { + return $this->webfingerObjects[$url]; + } + + return null; + } + + public function fetchInstanceNodeInfoEndpoints(string $domain, bool $decoded = true): array|string|null + { + return null; + } + + public function fetchInstanceNodeInfo(string $url, bool $decoded = true): array|string|null + { + return null; + } + + public function post(string $url, Magazine|User $actor, ?array $body = null): void + { + $this->postedObjects[] = [ + 'inboxUrl' => $url, + 'actor' => $actor, + 'payload' => $body, + ]; + } + + /** + * @return array + */ + public function getPostedObjects(): array + { + return $this->postedObjects; + } + + public function getActivityObjectCacheKey(string $url): string + { + return 'SOME_TESTING_CACHE_KEY'; + } + + public function getInboxUrl(string $apProfileId): string + { + $actor = $this->getActorObject($apProfileId); + if (!empty($actor)) { + return $actor['endpoints']['sharedInbox'] ?? $actor['inbox']; + } else { + throw new \LogicException("Unable to find AP actor (user or magazine) with URL: $apProfileId"); + } + } + + public function invalidateActorObjectCache(string $apProfileId): void + { + } + + public function invalidateCollectionObjectCache(string $apAddress): void + { + } + + public function getInstancePublicKey(): string + { + return 'TESTING PUBLIC KEY'; + } +} diff --git a/tests/Unit/ActivityPub/TagMatchTest.php b/tests/Unit/ActivityPub/TagMatchTest.php new file mode 100644 index 000000000..b4e764cfb --- /dev/null +++ b/tests/Unit/ActivityPub/TagMatchTest.php @@ -0,0 +1,248 @@ +settingsManager->get('KBIN_DOMAIN'); + + foreach ($this->domains as $domain) { + $this->settingsManager->set('KBIN_DOMAIN', $domain); + $context = $this->router->getContext(); + $context->setHost($domain); + + $username = 'user'; + $user = $this->getUserByUsername($username); + $json = $this->personFactory->create($user); + $this->testingApHttpClient->actorObjects[$json['id']] = $json; + + $userEvent = new WebfingerResponseEvent(new JsonRd(), "$username@$domain", ['account' => $username]); + $this->eventDispatcher->dispatch($userEvent); + $realDomain = \sprintf(WebFingerFactory::WEBFINGER_URL, 'https', $domain, '', "$username@$domain"); + $this->testingApHttpClient->webfingerObjects[$realDomain] = $userEvent->jsonRd->toArray(); + + $magazineName = 'mbin'; + $magazine = $this->getMagazineByName($magazineName, user: $user); + $json = $this->groupFactory->create($magazine); + $this->testingApHttpClient->actorObjects[$json['id']] = $json; + + $magazineEvent = new WebfingerResponseEvent(new JsonRd(), "$magazineName@$domain", ['account' => $magazineName]); + $this->eventDispatcher->dispatch($magazineEvent); + $realDomain = \sprintf(WebFingerFactory::WEBFINGER_URL, 'https', $domain, '', "$magazineName@$domain"); + $this->testingApHttpClient->webfingerObjects[$realDomain] = $magazineEvent->jsonRd->toArray(); + + $entry = $this->getEntryByTitle("test from $domain", magazine: $magazine, user: $user); + $json = $this->pageFactory->create($entry, $this->tagLinkRepository->getTagsOfEntry($entry)); + $this->testingApHttpClient->activityObjects[$json['id']] = $json; + + $create = $this->createWrapper->build($entry); + $this->testingApHttpClient->activityObjects[$create['id']] = $create; + + $this->entryManager->purge($user, $entry); + $this->magazineManager->purge($magazine); + $this->entityManager->remove($user); + + $this->entries = new ArrayCollection(); + $this->magazines = new ArrayCollection(); + $this->users = new ArrayCollection(); + } + + $this->settingsManager->set('KBIN_DOMAIN', $prevDomain); + $context = $this->router->getContext(); + $context->setHost($prevDomain); + + $this->testingApHttpClient->actorObjects[$this->mastodonUser['id']] = $this->mastodonUser; + $this->testingApHttpClient->activityObjects[$this->mastodonPost['id']] = $this->mastodonPost; + $this->testingApHttpClient->webfingerObjects[\sprintf(WebFingerFactory::WEBFINGER_URL, 'https', 'masto.don', '', 'User@masto.don')] = $this->mastodonWebfinger; + } + + public function setUp(): void + { + sort($this->domains); + parent::setUp(); + + $admin = $this->getUserByUsername('admin', isAdmin: true); + $this->getMagazineByName('random', user: $admin); + + $this->createMockedRemoteObjects(); + $user = $this->getUserByUsername('user'); + $magazine = $this->getMagazineByName('matching_mbin', user: $user); + $magazine->title = 'Matching Mbin'; + $magazine->tags = ['mbin']; + $this->entityManager->persist($magazine); + $this->entityManager->flush(); + + foreach ($this->domains as $domain) { + $this->remoteUsers[] = $this->activityPubManager->findActorOrCreate("user@$domain"); + $this->remoteMagazines[] = $this->activityPubManager->findActorOrCreate("mbin@$domain"); + } + + foreach ($this->remoteUsers as $remoteUser) { + $this->magazineManager->subscribe($magazine, $remoteUser); + } + + foreach ($this->remoteMagazines as $remoteMagazine) { + $this->magazineManager->subscribe($remoteMagazine, $user); + } + } + + public function testMatching(): void + { + self::assertEquals(\sizeof($this->domains), \sizeof(array_filter($this->remoteMagazines))); + self::assertEquals(\sizeof($this->domains), \sizeof(array_filter($this->remoteUsers))); + + // pull in the 10 prepared remote entries + foreach (array_filter($this->testingApHttpClient->activityObjects, fn ($item) => 'Page' === $item['type']) as $apObject) { + $this->bus->dispatch(new CreateMessage($apObject)); + $entry = $this->entryRepository->findOneBy(['apId' => $apObject['id']]); + self::assertNotNull($entry); + } + + $this->bus->dispatch(new CreateMessage($this->mastodonPost)); + $postedObjects = $this->testingApHttpClient->getPostedObjects(); + $postedAnnounces = array_filter($postedObjects, fn ($item) => 'Announce' === $item['payload']['type']); + $targetInboxes = array_map(fn ($item) => parse_url($item['inboxUrl'], PHP_URL_HOST), $postedAnnounces); + sort($targetInboxes); + self::assertArrayIsEqualToArrayIgnoringListOfKeys($this->domains, $targetInboxes, []); + } + + private array $mastodonUser = [ + 'id' => 'https://masto.don/users/User', + 'type' => 'Person', + 'following' => 'https://masto.don/users/User/following', + 'followers' => 'https://masto.don/users/User/followers', + 'inbox' => 'https://masto.don/users/User/inbox', + 'outbox' => 'https://masto.don/users/User/outbox', + 'featured' => 'https://masto.don/users/User/collections/featured', + 'featuredTags' => 'https://masto.don/users/User/collections/tags', + 'preferredUsername' => 'User', + 'name' => 'User', + 'summary' => '

Some summary

', + 'url' => 'https://masto.don/@User', + 'manuallyApprovesFollowers' => false, + 'discoverable' => true, + 'indexable' => true, + 'published' => '2025-01-01T00:00:00Z', + 'memorial' => false, + 'publicKey' => [ + 'id' => 'https://masto.don/users/User#main-key', + 'owner' => 'https://masto.don/users/User', + 'publicKeyPem' => "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAujdiYalTtr7R1CJIVBIy\nP50V+/JX+P15o0Cz0LUOhKvJIVyeV6szQGHj6Idu74x9e3+xf9jzQRCH6eq8ASAH\nHAKwdnHfhSmKbCQaTEI5V8497/4yU9z9Zn7uJ+C1rrKVIEoGGkpt8bK8fynfR/hb\n17FctW6EnrVrvNHyW+WwbyEbyqAxwbcOYd78PhdftWEdP6D+t4+XUoF9N1XGpsGO\nrixJDzMwNqkg9Gg9l/mnCmxV367xgh8qHC0SNmwaMbWv6AV/07dHWlr0N1pXmHqo\n9YkOEy7XuH1hovBzHWEf++P1Ew4bstwdfyS/m5bcakmSe+dR3WDylW336nO88vAF\nCQIDAQAB\n-----END PUBLIC KEY-----\n", + ], + 'tag' => [], + 'attachment' => [], + 'endpoints' => [ + 'sharedInbox' => 'https://masto.don/inbox', + ], + ]; + + private array $mastodonPost = [ + 'id' => 'https://masto.don/users/User/statuses/110226274955756643', + 'type' => 'Note', + 'summary' => null, + 'inReplyTo' => null, + 'published' => '2025-01-01T15:51:18Z', + 'url' => 'https://masto.don/@User/110226274955756643', + 'attributedTo' => 'https://masto.don/users/User', + 'to' => [ + 'https://www.w3.org/ns/activitystreams#Public', + ], + 'cc' => [ + 'https://masto.don/users/User/followers', + ], + 'sensitive' => false, + 'atomUri' => 'https://masto.don/users/User/statuses/110226274955756643', + 'inReplyToAtomUri' => null, + 'conversation' => 'tag:masto.don,2025-01-01:objectId=399588:objectType=Conversation', + 'content' => '

I am very excited about

', + 'contentMap' => [ + 'de' => '

I am very excited about

', + ], + 'attachment' => [], + 'tag' => [ + [ + 'type' => 'Hashtag', + 'href' => 'https://masto.don/tags/mbin', + 'name' => '#mbin', + ], + ], + 'replies' => [ + 'id' => 'https://masto.don/users/User/statuses/110226274955756643/replies', + 'type' => 'Collection', + 'first' => [ + 'type' => 'CollectionPage', + 'next' => 'https://masto.don/users/User/statuses/110226274955756643/replies?min_id=110226283102047096&page=true', + 'partOf' => 'https://masto.don/users/User/statuses/110226274955756643/replies', + 'items' => [ + 'https://masto.don/users/User/statuses/110226283102047096', + ], + ], + ], + 'likes' => [ + 'id' => 'https://masto.don/users/User/statuses/110226274955756643/likes', + 'type' => 'Collection', + 'totalItems' => 0, + ], + 'shares' => [ + 'id' => 'https://masto.don/users/User/statuses/110226274955756643/shares', + 'type' => 'Collection', + 'totalItems' => 0, + ], + ]; + + private array $mastodonWebfinger = [ + 'subject' => 'acct:User@masto.don', + 'aliases' => [ + 'https://masto.don/@User', + 'https://masto.don/users/User', + ], + 'links' => [ + [ + 'rel' => 'http://webfinger.net/rel/profile-page', + 'type' => 'text/html', + 'href' => 'https://masto.don/@User', + ], + [ + 'rel' => 'self', + 'type' => 'application/activity+json', + 'href' => 'https://masto.don/users/User', + ], + [ + 'rel' => 'http://ostatus.org/schema/1.0/subscribe', + 'template' => 'https://masto.don/authorize_interaction?uri=[uri]', + ], + ], + ]; +} diff --git a/tests/Unit/Service/ActivityPub/SignatureValidatorTest.php b/tests/Unit/Service/ActivityPub/SignatureValidatorTest.php index 392672910..158a027e3 100644 --- a/tests/Unit/Service/ActivityPub/SignatureValidatorTest.php +++ b/tests/Unit/Service/ActivityPub/SignatureValidatorTest.php @@ -6,7 +6,7 @@ use App\Entity\Magazine; use App\Exception\InvalidApSignatureException; -use App\Service\ActivityPub\ApHttpClient; +use App\Service\ActivityPub\ApHttpClientInterface; use App\Service\ActivityPub\SignatureValidator; use App\Service\ActivityPubManager; use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; @@ -97,7 +97,7 @@ public function testItValidatesACorrectlySignedRequest(): void $apManager->method('findActorOrCreate') ->willReturn($stubMagazine); - $apHttpClient = $this->createStub(ApHttpClient::class); + $apHttpClient = $this->createStub(ApHttpClientInterface::class); $apHttpClient->method('getActorObject') ->willReturn( [ @@ -128,7 +128,7 @@ public function testItValidatesACorrectlySignedRequestToAPersonalInbox(): void $apManager->method('findActorOrCreate') ->willReturn($stubMagazine); - $apHttpClient = $this->createStub(ApHttpClient::class); + $apHttpClient = $this->createStub(ApHttpClientInterface::class); $apHttpClient->method('getActorObject') ->willReturn( [ @@ -158,7 +158,7 @@ public function testItDoesNotValidateARequestWithDifferentBody(): void $apManager->method('findActorOrCreate') ->willReturn($stubMagazine); - $apHttpClient = $this->createStub(ApHttpClient::class); + $apHttpClient = $this->createStub(ApHttpClientInterface::class); $apHttpClient->method('getActorObject') ->willReturn( [ @@ -192,7 +192,7 @@ public function testItDoesNotValidateARequestWhenDomainsDoNotMatch(): void $this->headers['signature'][0] = \sprintf($this->headers['signature'][0], 'https://kbin.localhost/m/group'); $apManager = $this->createStub(ActivityPubManager::class); - $apHttpClient = $this->createStub(ApHttpClient::class); + $apHttpClient = $this->createStub(ApHttpClientInterface::class); $logger = $this->createStub(LoggerInterface::class); @@ -218,7 +218,7 @@ public function testItDoesNotValidateARequestWhenUrlsAreNotHTTPS(): void $this->headers['signature'][0] = \sprintf($this->headers['signature'][0], 'http://kbin.localhost/m/group'); $apManager = $this->createStub(ActivityPubManager::class); - $apHttpClient = $this->createStub(ApHttpClient::class); + $apHttpClient = $this->createStub(ApHttpClientInterface::class); $logger = $this->createStub(LoggerInterface::class); diff --git a/tests/WebTestCase.php b/tests/WebTestCase.php index b0f0b244f..958b0da51 100644 --- a/tests/WebTestCase.php +++ b/tests/WebTestCase.php @@ -4,11 +4,13 @@ namespace App\Tests; +use App\Factory\ActivityPub\EntryPageFactory; use App\Factory\ActivityPub\GroupFactory; use App\Factory\ActivityPub\PersonFactory; use App\Factory\ActivityPub\TombstoneFactory; use App\Factory\ImageFactory; use App\Factory\MagazineFactory; +use App\MessageHandler\ActivityPub\Outbox\DeliverHandler; use App\Repository\EntryCommentRepository; use App\Repository\EntryRepository; use App\Repository\ImageRepository; @@ -20,7 +22,11 @@ use App\Repository\ReportRepository; use App\Repository\SettingsRepository; use App\Repository\SiteRepository; +use App\Repository\TagLinkRepository; use App\Repository\UserRepository; +use App\Service\ActivityPub\ApHttpClientInterface; +use App\Service\ActivityPub\Wrapper\CreateWrapper; +use App\Service\ActivityPubManager; use App\Service\BadgeManager; use App\Service\DomainManager; use App\Service\EntryCommentManager; @@ -47,7 +53,9 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouterInterface; use Symfony\Contracts\Translation\TranslatorInterface; abstract class WebTestCase extends BaseWebTestCase @@ -97,6 +105,7 @@ abstract class WebTestCase extends BaseWebTestCase protected BadgeManager $badgeManager; protected NotificationManager $notificationManager; protected MentionManager $mentionManager; + protected ActivityPubManager $activityPubManager; protected MagazineRepository $magazineRepository; protected EntryRepository $entryRepository; @@ -110,12 +119,17 @@ abstract class WebTestCase extends BaseWebTestCase protected ReportRepository $reportRepository; protected SettingsRepository $settingsRepository; protected UserRepository $userRepository; + protected TagLinkRepository $tagLinkRepository; protected ImageFactory $imageFactory; protected MagazineFactory $magazineFactory; protected TombstoneFactory $tombstoneFactory; protected PersonFactory $personFactory; protected GroupFactory $groupFactory; + protected EntryPageFactory $pageFactory; + protected TestingApHttpClient $testingApHttpClient; + + protected CreateWrapper $createWrapper; protected UrlGeneratorInterface $urlGenerator; protected TranslatorInterface $translator; @@ -123,6 +137,10 @@ abstract class WebTestCase extends BaseWebTestCase protected RequestStack $requestStack; protected LoggerInterface $logger; protected ProjectInfoService $projectInfoService; + protected RouterInterface $router; + protected MessageBusInterface $bus; + + protected DeliverHandler $deliverHandler; protected string $kibbyPath; @@ -134,6 +152,9 @@ public function setUp(): void $this->kibbyPath = \dirname(__FILE__).'/assets/kibby_emoji.png'; $this->client = static::createClient(); + $this->testingApHttpClient = new TestingApHttpClient(); + self::getContainer()->set(ApHttpClientInterface::class, $this->testingApHttpClient); + $this->entityManager = $this->getService(EntityManagerInterface::class); $this->magazineManager = $this->getService(MagazineManager::class); $this->userManager = $this->getService(UserManager::class); @@ -150,6 +171,7 @@ public function setUp(): void $this->reportManager = $this->getService(ReportManager::class); $this->badgeManager = $this->getService(BadgeManager::class); $this->notificationManager = $this->getService(NotificationManager::class); + $this->activityPubManager = $this->getService(ActivityPubManager::class); $this->magazineRepository = $this->getService(MagazineRepository::class); $this->entryRepository = $this->getService(EntryRepository::class); @@ -163,14 +185,22 @@ public function setUp(): void $this->reportRepository = $this->getService(ReportRepository::class); $this->settingsRepository = $this->getService(SettingsRepository::class); $this->userRepository = $this->getService(UserRepository::class); + $this->tagLinkRepository = $this->getService(TagLinkRepository::class); $this->imageFactory = $this->getService(ImageFactory::class); + $this->personFactory = $this->getService(PersonFactory::class); $this->magazineFactory = $this->getService(MagazineFactory::class); + $this->groupFactory = $this->getService(GroupFactory::class); + $this->pageFactory = $this->getService(EntryPageFactory::class); + + $this->createWrapper = $this->getService(CreateWrapper::class); $this->urlGenerator = $this->getService(UrlGeneratorInterface::class); $this->translator = $this->getService(TranslatorInterface::class); $this->eventDispatcher = $this->getService(EventDispatcherInterface::class); $this->requestStack = $this->getService(RequestStack::class); + $this->router = $this->getService(RouterInterface::class); + $this->bus = $this->getService(MessageBusInterface::class); // clear all cache before every test $app = new Application($this->client->getKernel());