Fix webfinger retries (#4275)

* Do not raise unretryable exceptions in ResolveRemoteAccountService

* Removed fatal exceptions from ResolveRemoteAccountService

Exceptions that cannot be retried should not be raised. New exception
class for those that can be retried (Mastodon::UnexpectedResponseError)
This commit is contained in:
Eugen Rochko 2017-07-20 01:59:07 +02:00 committed by GitHub
parent f24b81e27f
commit 1fcdaafa6f
14 changed files with 40 additions and 39 deletions

View file

@ -17,11 +17,7 @@ class Api::BaseController < ApplicationController
render json: { error: 'Record not found' }, status: 404 render json: { error: 'Record not found' }, status: 404
end end
rescue_from Goldfinger::Error do rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do
render json: { error: 'Remote account could not be resolved' }, status: 422
end
rescue_from HTTP::Error do
render json: { error: 'Remote data could not be fetched' }, status: 503 render json: { error: 'Remote data could not be fetched' }, status: 503
end end

View file

@ -5,4 +5,14 @@ module Mastodon
class NotPermittedError < Error; end class NotPermittedError < Error; end
class ValidationError < Error; end class ValidationError < Error; end
class RaceConditionError < Error; end class RaceConditionError < Error; end
class UnexpectedResponseError < Error
def initialize(response = nil)
@response = response
end
def to_s
"#{@response.uri} returned code #{@response.code}"
end
end
end end

View file

@ -32,8 +32,5 @@ class FetchRemoteAccountService < BaseService
rescue Nokogiri::XML::XPath::SyntaxError rescue Nokogiri::XML::XPath::SyntaxError
Rails.logger.debug 'Invalid XML or missing namespace' Rails.logger.debug 'Invalid XML or missing namespace'
nil nil
rescue Goldfinger::NotFoundError, Goldfinger::Error
Rails.logger.debug 'Exceptions related to Goldfinger occurs'
nil
end end
end end

View file

@ -33,9 +33,6 @@ class FetchRemoteStatusService < BaseService
rescue Nokogiri::XML::XPath::SyntaxError rescue Nokogiri::XML::XPath::SyntaxError
Rails.logger.debug 'Invalid XML or missing namespace' Rails.logger.debug 'Invalid XML or missing namespace'
nil nil
rescue Goldfinger::NotFoundError, Goldfinger::Error
Rails.logger.debug 'Exceptions related to Goldfinger occurs'
nil
end end
def confirmed_domain?(domain, account) def confirmed_domain?(domain, account)

View file

@ -47,7 +47,7 @@ class ProcessInteractionService < BaseService
reflect_unblock!(account, target_account) reflect_unblock!(account, target_account)
end end
end end
rescue Goldfinger::Error, HTTP::Error, OStatus2::BadSalmonError, Mastodon::NotPermittedError rescue HTTP::Error, OStatus2::BadSalmonError, Mastodon::NotPermittedError
nil nil
end end

View file

@ -23,18 +23,19 @@ class ResolveRemoteAccountService < BaseService
@webfinger = Goldfinger.finger("acct:#{uri}") @webfinger = Goldfinger.finger("acct:#{uri}")
raise Goldfinger::Error, 'Missing resource links' if links_missing?
confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@') confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@')
if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
@username = confirmed_username @username = confirmed_username
@domain = confirmed_domain @domain = confirmed_domain
elsif redirected.nil?
return call("#{confirmed_username}@#{confirmed_domain}", update_profile, true)
else else
return call("#{confirmed_username}@#{confirmed_domain}", update_profile, true) if redirected.nil? Rails.logger.debug 'Requested and returned acct URIs do not match'
raise Goldfinger::Error, 'Requested and returned acct URIs do not match' return
end end
return if links_missing?
return Account.find_local(@username) if TagManager.instance.local_domain?(@domain) return Account.find_local(@username) if TagManager.instance.local_domain?(@domain)
RedisLock.acquire(lock_options) do |lock| RedisLock.acquire(lock_options) do |lock|
@ -49,6 +50,9 @@ class ResolveRemoteAccountService < BaseService
end end
@account @account
rescue Goldfinger::Error => e
Rails.logger.debug "Webfinger query for #{uri} unsuccessful: #{e}"
nil
end end
private private
@ -57,7 +61,9 @@ class ResolveRemoteAccountService < BaseService
@webfinger.link('http://schemas.google.com/g/2010#updates-from').nil? || @webfinger.link('http://schemas.google.com/g/2010#updates-from').nil? ||
@webfinger.link('salmon').nil? || @webfinger.link('salmon').nil? ||
@webfinger.link('http://webfinger.net/rel/profile-page').nil? || @webfinger.link('http://webfinger.net/rel/profile-page').nil? ||
@webfinger.link('magic-public-key').nil? @webfinger.link('magic-public-key').nil? ||
canonical_uri.nil? ||
hub_url.nil?
end end
def webfinger_update_due? def webfinger_update_due?
@ -123,19 +129,14 @@ class ResolveRemoteAccountService < BaseService
author_uri = owner.at_xpath('./xmlns:uri') unless owner.nil? author_uri = owner.at_xpath('./xmlns:uri') unless owner.nil?
end end
raise Goldfinger::Error, 'Author URI could not be found' if author_uri.nil? @canonical_uri = author_uri.nil? ? nil : author_uri.content
@canonical_uri = author_uri.content
end end
def hub_url def hub_url
return @hub_url if defined?(@hub_url) return @hub_url if defined?(@hub_url)
hubs = atom.xpath('//xmlns:link[@rel="hub"]') hubs = atom.xpath('//xmlns:link[@rel="hub"]')
@hub_url = hubs.empty? || hubs.first['href'].nil? ? nil : hubs.first['href']
raise Goldfinger::Error, 'No PubSubHubbub hubs found' if hubs.empty? || hubs.first['href'].nil?
@hub_url = hubs.first['href']
end end
def atom_body def atom_body
@ -143,7 +144,7 @@ class ResolveRemoteAccountService < BaseService
response = Request.new(:get, atom_url).perform response = Request.new(:get, atom_url).perform
raise Goldfinger::Error, "Feed attempt failed for #{atom_url}: HTTP #{response.code}" unless response.code == 200 raise Mastodon::UnexpectedResponseError, response unless response.code == 200
@atom_body = response.to_s @atom_body = response.to_s
end end

View file

@ -14,7 +14,7 @@ class SendInteractionService < BaseService
delivery = build_request.perform delivery = build_request.perform
raise "Delivery failed for #{target_account.salmon_url}: HTTP #{delivery.code}" unless delivery.code > 199 && delivery.code < 300 raise Mastodon::UnexpectedResponseError, delivery unless delivery.code > 199 && delivery.code < 300
end end
private private

View file

@ -18,7 +18,7 @@ class SubscribeService < BaseService
else else
# The response was either a 429 rate limit, or a 5xx error. # The response was either a 429 rate limit, or a 5xx error.
# We need to retry at a later time. Fail loudly! # We need to retry at a later time. Fail loudly!
raise "Subscription attempt failed for #{@account.acct} (#{@account.hub_url}): HTTP #{@response.code}" raise Mastodon::UnexpectedResponseError, @response
end end
end end

View file

@ -44,7 +44,7 @@ class ImportWorker
target_account = ResolveRemoteAccountService.new.call(row.first) target_account = ResolveRemoteAccountService.new.call(row.first)
next if target_account.nil? next if target_account.nil?
MuteService.new.call(from_account, target_account) MuteService.new.call(from_account, target_account)
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError rescue Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError
next next
end end
end end
@ -56,7 +56,7 @@ class ImportWorker
target_account = ResolveRemoteAccountService.new.call(row.first) target_account = ResolveRemoteAccountService.new.call(row.first)
next if target_account.nil? next if target_account.nil?
BlockService.new.call(from_account, target_account) BlockService.new.call(from_account, target_account)
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError rescue Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError
next next
end end
end end
@ -66,7 +66,7 @@ class ImportWorker
import_rows.each do |row| import_rows.each do |row|
begin begin
FollowService.new.call(from_account, row.first) FollowService.new.call(from_account, row.first)
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError
next next
end end
end end

View file

@ -23,7 +23,7 @@ class Pubsubhubbub::DeliveryWorker
def process_delivery def process_delivery
payload_delivery payload_delivery
raise "Delivery failed for #{subscription.callback_url}: HTTP #{payload_delivery.code}" unless response_successful? raise Mastodon::UnexpectedResponseError, payload_delivery unless response_successful?
subscription.touch(:last_successful_delivery_at) subscription.touch(:last_successful_delivery_at)
end end

View file

@ -32,7 +32,7 @@ describe Api::BaseController do
ActiveRecord::RecordInvalid => 422, ActiveRecord::RecordInvalid => 422,
Mastodon::ValidationError => 422, Mastodon::ValidationError => 422,
ActiveRecord::RecordNotFound => 404, ActiveRecord::RecordNotFound => 404,
Goldfinger::Error => 422, Mastodon::UnexpectedResponseError => 503,
HTTP::Error => 503, HTTP::Error => 503,
OpenSSL::SSL::SSLError => 503, OpenSSL::SSL::SSLError => 503,
Mastodon::NotPermittedError => 403, Mastodon::NotPermittedError => 403,

View file

@ -22,11 +22,11 @@ RSpec.describe ResolveRemoteAccountService do
end end
it 'raises error if no such user can be resolved via webfinger' do it 'raises error if no such user can be resolved via webfinger' do
expect { subject.call('catsrgr8@quitter.no') }.to raise_error Goldfinger::Error expect(subject.call('catsrgr8@quitter.no')).to be_nil
end end
it 'raises error if the domain does not have webfinger' do it 'raises error if the domain does not have webfinger' do
expect { subject.call('catsrgr8@example.com') }.to raise_error Goldfinger::Error expect(subject.call('catsrgr8@example.com')).to be_nil
end end
it 'returns an already existing remote account' do it 'returns an already existing remote account' do
@ -58,7 +58,7 @@ RSpec.describe ResolveRemoteAccountService do
end end
it 'prevents hijacking inexisting accounts' do it 'prevents hijacking inexisting accounts' do
expect { subject.call('hacker2@redirected.com') }.to raise_error Goldfinger::Error expect(subject.call('hacker2@redirected.com')).to be_nil
end end
it 'returns a new remote account' do it 'returns a new remote account' do

View file

@ -33,11 +33,11 @@ RSpec.describe SubscribeService do
it 'fails loudly if PuSH hub is unavailable' do it 'fails loudly if PuSH hub is unavailable' do
stub_request(:post, 'http://hub.example.com/').to_return(status: 503) stub_request(:post, 'http://hub.example.com/').to_return(status: 503)
expect { subject.call(account) }.to raise_error(/Subscription attempt failed/) expect { subject.call(account) }.to raise_error Mastodon::UnexpectedResponseError
end end
it 'fails loudly if rate limited' do it 'fails loudly if rate limited' do
stub_request(:post, 'http://hub.example.com/').to_return(status: 429) stub_request(:post, 'http://hub.example.com/').to_return(status: 429)
expect { subject.call(account) }.to raise_error(/Subscription attempt failed/) expect { subject.call(account) }.to raise_error Mastodon::UnexpectedResponseError
end end
end end

View file

@ -26,7 +26,7 @@ describe Pubsubhubbub::DeliveryWorker do
subscription = Fabricate(:subscription) subscription = Fabricate(:subscription)
stub_request_to_respond_with(subscription, 500) stub_request_to_respond_with(subscription, 500)
expect { subject.perform(subscription.id, payload) }.to raise_error(/Delivery failed/) expect { subject.perform(subscription.id, payload) }.to raise_error Mastodon::UnexpectedResponseError
end end
it 'updates subscriptions when delivery succeeds' do it 'updates subscriptions when delivery succeeds' do