diff --git a/app/services/activitypub/fetch_remote_featured_collection_service.rb b/app/services/activitypub/fetch_remote_featured_collection_service.rb index 968dc6811ec..3afb4093b8f 100644 --- a/app/services/activitypub/fetch_remote_featured_collection_service.rb +++ b/app/services/activitypub/fetch_remote_featured_collection_service.rb @@ -13,9 +13,9 @@ class ActivityPub::FetchRemoteFeaturedCollectionService < BaseService return unless supported_context?(json) return unless json['type'] == 'FeaturedCollection' - # Fetching an unknown account should eventually also fetch its - # collections, so it should be OK to only handle known accounts here - account = Account.find_by(uri: json['attributedTo']) + # A collection can be resolved on its own (e.g. through authorize_interaction) + # before its account is known, so the account is fetched if necessary + account = account_from_uri(value_or_id(first_of_value(json['attributedTo'])), request_id) return unless account existing_collection = account.collections.find_by(uri:) @@ -23,4 +23,12 @@ class ActivityPub::FetchRemoteFeaturedCollectionService < BaseService ActivityPub::ProcessFeaturedCollectionService.new.call(account, json, request_id:) end + + private + + def account_from_uri(uri, request_id) + account = ActivityPub::TagManager.instance.uri_to_resource(uri, Account) + account ||= ActivityPub::FetchRemoteAccountService.new.call(uri, request_id:) + account + end end diff --git a/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb b/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb index f5cb9194b2e..f623f5648e2 100644 --- a/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb @@ -61,4 +61,47 @@ RSpec.describe ActivityPub::FetchRemoteFeaturedCollectionService do expect(subject.call(uri)).to be_nil end end + + context 'when the attributed account is not known yet' do + let(:actor_uri) { 'https://example.com/alice' } + let(:response) do + { + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => uri, + 'type' => 'FeaturedCollection', + 'name' => 'Incredible people', + 'attributedTo' => actor_uri, + 'sensitive' => false, + 'discoverable' => true, + 'totalItems' => 0, + } + end + let(:actor) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: actor_uri, + type: 'Person', + preferredUsername: 'alice', + inbox: 'https://example.com/alice/inbox', + } + end + let(:webfinger) do + { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: actor_uri, type: 'application/activity+json' }] } + end + + before do + stub_request(:get, actor_uri) + .to_return(body: actor.to_json, headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com') + .to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' }) + end + + it 'fetches the account and creates the collection' do + collection = nil + expect { collection = subject.call(uri) }.to change(Collection, :count).by(1) + + expect(collection.uri).to eq uri + expect(collection.account.uri).to eq actor_uri + end + end end