forked from treehouse/mastodon
Raise Mastodon::HostValidationError when host for HTTP request is private (#6410)
parent
7cb49eaa3a
commit
2e8a492e88
4
Gemfile
4
Gemfile
|
@ -96,6 +96,10 @@ group :development, :test do
|
|||
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'
|
||||
|
|
|
@ -376,6 +376,7 @@ GEM
|
|||
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)
|
||||
|
@ -683,6 +684,7 @@ DEPENDENCIES
|
|||
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)
|
||||
|
|
|
@ -4,6 +4,7 @@ module Mastodon
|
|||
class Error < StandardError; end
|
||||
class NotPermittedError < Error; end
|
||||
class ValidationError < Error; end
|
||||
class HostValidationError < ValidationError; end
|
||||
class RaceConditionError < Error; end
|
||||
|
||||
class UnexpectedResponseError < Error
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'ipaddr'
|
||||
require 'socket'
|
||||
|
||||
class Request
|
||||
REQUEST_TARGET = '(request-target)'
|
||||
|
||||
|
@ -8,7 +11,7 @@ class Request
|
|||
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!
|
||||
|
@ -87,4 +90,18 @@ class Request
|
|||
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
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# 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
|
|
@ -85,3 +85,9 @@ Rails.application.configure do
|
|||
end
|
||||
|
||||
ActiveRecordQueryTrace.enabled = ENV.fetch('QUERY_TRACE_ENABLED') { false }
|
||||
|
||||
module PrivateAddressCheck
|
||||
def self.private_address?(*)
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,10 @@ 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|
|
||||
|
|
|
@ -38,17 +38,32 @@ describe Request do
|
|||
end
|
||||
|
||||
describe '#perform' do
|
||||
before do
|
||||
stub_request(:get, 'http://example.com')
|
||||
subject.perform
|
||||
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 'sets headers' do
|
||||
expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
|
||||
end
|
||||
end
|
||||
|
||||
it 'executes a HTTP request' do
|
||||
expect(a_request(:get, 'http://example.com')).to have_been_made.once
|
||||
end
|
||||
context 'with private host' do
|
||||
around do |example|
|
||||
WebMock.disable!
|
||||
example.run
|
||||
WebMock.enable!
|
||||
end
|
||||
|
||||
it 'sets headers' do
|
||||
expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue