diff --git a/app/helpers/json_ld_helper.rb b/app/helpers/json_ld_helper.rb index f3361ee206a..0858d168c03 100644 --- a/app/helpers/json_ld_helper.rb +++ b/app/helpers/json_ld_helper.rb @@ -233,7 +233,7 @@ module JsonLdHelper # If an error is raised, it contains the response and can be captured for handling like # # begin - # fetch_resource_without_id_validation(uri, nil, true) + # fetch_resource_without_id_validation(uri, raise_on_error: :all) # rescue Mastodon::UnexpectedResponseError => e # e.response # end diff --git a/app/services/activitypub/fetch_remote_actor_service.rb b/app/services/activitypub/fetch_remote_actor_service.rb index efb9b9b64b8..2c7381dd97c 100644 --- a/app/services/activitypub/fetch_remote_actor_service.rb +++ b/app/services/activitypub/fetch_remote_actor_service.rb @@ -15,10 +15,14 @@ class ActivityPub::FetchRemoteActorService < BaseService @json = begin if prefetched_body.nil? - fetch_resource(uri, true) + fetch_resource(uri, true, raise_on_error: :all) else body_to_json(prefetched_body, compare_id: uri) end + rescue Mastodon::UnexpectedResponseError => e + queue_deletion!(uri) if e.response.code == 410 + + raise Error, "Error fetching actor JSON at #{uri} (HTTP #{e.response.code})" rescue JSON::ParserError raise Error, "Error parsing JSON-LD document #{uri}" end @@ -39,6 +43,16 @@ class ActivityPub::FetchRemoteActorService < BaseService private + def queue_deletion!(uri) + account = Account.find_by(uri:) + return unless account&.remote? + + Rails.logger.debug { "Deleting actor #{uri} because of HTTP 410 response" } + + account.suspend!(origin: :remote) + AccountDeletionWorker.perform_async(account.id, { 'reserve_username' => false, 'skip_activitypub' => true }) + end + def supported_context? super(@json) end diff --git a/spec/services/activitypub/fetch_remote_actor_service_spec.rb b/spec/services/activitypub/fetch_remote_actor_service_spec.rb index f1dc327fab7..49eea0ede44 100644 --- a/spec/services/activitypub/fetch_remote_actor_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_actor_service_spec.rb @@ -32,6 +32,51 @@ RSpec.describe ActivityPub::FetchRemoteActorService do end end + context 'when the account is already known and the actor document returns a temporary error' do + let!(:known_account) { Fabricate(:remote_account, uri: 'https://example.com/alice') } + + before do + stub_request(:get, 'https://example.com/alice').to_return(status: 500) + end + + it 'returns nil but keeps the existing account intact' do + expect(account).to be_nil + expect(known_account.reload.suspended?).to be false + expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once + expect(AccountDeletionWorker).to_not have_enqueued_sidekiq_job + end + end + + context 'when the account is already known and the actor document returns a 404 not found error' do + let!(:known_account) { Fabricate(:remote_account, uri: 'https://example.com/alice') } + + before do + stub_request(:get, 'https://example.com/alice').to_return(status: 404) + end + + it 'returns nil but keeps the existing account intact' do + expect(account).to be_nil + expect(known_account.reload.suspended?).to be false + expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once + expect(AccountDeletionWorker).to_not have_enqueued_sidekiq_job + end + end + + context 'when the account is already known and the actor document returns a 410 gone error' do + let!(:known_account) { Fabricate(:remote_account, uri: 'https://example.com/alice') } + + before do + stub_request(:get, 'https://example.com/alice').to_return(status: 410) + end + + it 'returns nil and marks the known account for deletion' do + expect(account).to be_nil + expect(known_account.reload.suspended?).to be true + expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once + expect(AccountDeletionWorker).to have_enqueued_sidekiq_job(known_account.id, { 'reserve_username' => false, 'skip_activitypub' => true }) + end + end + context 'when the account does not have a inbox' do before do actor[:inbox] = nil