gem 'rspec-rails', '~> 3.7'
end
+group :production, :test do
+ gem 'private_address_check', '~> 0.4.1'
+end
+
group :test do
gem 'capybara', '~> 2.15'
gem 'climate_control', '~> 0.2'
premailer-rails (1.10.1)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
+ private_address_check (0.4.1)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
pghero (~> 1.7)
pkg-config (~> 1.2)
premailer-rails
+ private_address_check (~> 0.4.1)
pry-rails (~> 0.3)
puma (~> 3.10)
pundit (~> 1.1)
class Error < StandardError; end
class NotPermittedError < Error; end
class ValidationError < Error; end
+ class HostValidationError < ValidationError; end
class RaceConditionError < Error; end
class UnexpectedResponseError < Error
# frozen_string_literal: true
+require 'ipaddr'
+require 'socket'
+
class Request
REQUEST_TARGET = '(request-target)'
def initialize(verb, url, **options)
@verb = verb
@url = Addressable::URI.parse(url).normalize
- @options = options
+ @options = options.merge(socket_class: Socket)
@headers = {}
set_common_headers!
def http_client
HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
end
+
+ class Socket < TCPSocket
+ class << self
+ def open(host, *args)
+ address = IPSocket.getaddress(host)
+ raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address)
+ super address, *args
+ end
+
+ alias new open
+ end
+ end
+
+ private_constant :Socket
end
--- /dev/null
+# frozen_string_literal: true
+
+class SidekiqErrorHandler
+ def call(*)
+ yield
+ rescue Mastodon::HostValidationError => e
+ Rails.logger.error "#{e.class}: #{e.message}"
+ Rails.logger.error e.backtrace.join("\n")
+ # Do not retry
+ end
+end
end
ActiveRecordQueryTrace.enabled = ENV.fetch('QUERY_TRACE_ENABLED') { false }
+
+module PrivateAddressCheck
+ def self.private_address?(*)
+ false
+ end
+end
Sidekiq.configure_server do |config|
config.redis = redis_params
+
+ config.server_middleware do |chain|
+ chain.add SidekiqErrorHandler
+ end
end
Sidekiq.configure_client do |config|
end
describe '#perform' do
- before do
- stub_request(:get, 'http://example.com')
- subject.perform
- end
+ context 'with valid host' do
+ before do
+ stub_request(:get, 'http://example.com')
+ subject.perform
+ end
+
+ it 'executes a HTTP request' do
+ expect(a_request(:get, 'http://example.com')).to have_been_made.once
+ end
- it 'executes a HTTP request' do
- expect(a_request(:get, 'http://example.com')).to have_been_made.once
+ it 'sets headers' do
+ expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
+ end
end
- it 'sets headers' do
- expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
+ context 'with private host' do
+ around do |example|
+ WebMock.disable!
+ example.run
+ WebMock.enable!
+ end
+
+ it 'raises Mastodon::ValidationError' do
+ allow(IPSocket).to receive(:getaddress).with('example.com').and_return('0.0.0.0')
+ expect{ subject.perform }.to raise_error Mastodon::ValidationError
+ end
end
end
end