aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch')
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb51
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_view.rb50
-rw-r--r--actionpack/lib/action_dispatch/middleware/host_authorization.rb103
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb5
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb11
9 files changed, 187 insertions, 68 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 5b2ad36dd5..87fe19225b 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -24,10 +24,8 @@ module ActionDispatch
def call(env)
error = nil
result = run_callbacks :call do
- begin
- @app.call(env)
- rescue => error
- end
+ @app.call(env)
+ rescue => error
end
raise error if error
result
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 7669767ae3..61773d97a2 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -3,53 +3,14 @@
require "action_dispatch/http/request"
require "action_dispatch/middleware/exception_wrapper"
require "action_dispatch/routing/inspector"
+
require "action_view"
require "action_view/base"
-require "pp"
-
module ActionDispatch
# This middleware is responsible for logging exceptions and
# showing a debugging page in case the request is local.
class DebugExceptions
- RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
-
- class DebugView < ActionView::Base
- def debug_params(params)
- clean_params = params.clone
- clean_params.delete("action")
- clean_params.delete("controller")
-
- if clean_params.empty?
- "None"
- else
- PP.pp(clean_params, +"", 200)
- end
- end
-
- def debug_headers(headers)
- if headers.present?
- headers.inspect.gsub(",", ",\n")
- else
- "None"
- end
- end
-
- def debug_hash(object)
- object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
- end
-
- def render(*)
- logger = ActionView::Base.logger
-
- if logger && logger.respond_to?(:silence)
- logger.silence { super }
- else
- super
- end
- end
- end
-
cattr_reader :interceptors, instance_accessor: false, default: []
def self.register_interceptor(object = nil, &block)
@@ -87,11 +48,9 @@ module ActionDispatch
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
@interceptors.each do |interceptor|
- begin
- interceptor.call(request, exception)
- rescue Exception
- log_error(request, wrapper)
- end
+ interceptor.call(request, exception)
+ rescue Exception
+ log_error(request, wrapper)
end
end
@@ -152,7 +111,7 @@ module ActionDispatch
end
def create_template(request, wrapper)
- DebugView.new([RESCUES_TEMPLATE_PATH],
+ DebugView.new(
request: request,
exception_wrapper: wrapper,
exception: wrapper.exception,
diff --git a/actionpack/lib/action_dispatch/middleware/debug_view.rb b/actionpack/lib/action_dispatch/middleware/debug_view.rb
new file mode 100644
index 0000000000..ac12dc13a1
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/debug_view.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require "pp"
+
+require "action_view"
+require "action_view/base"
+
+module ActionDispatch
+ class DebugView < ActionView::Base # :nodoc:
+ RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
+
+ def initialize(assigns)
+ super([RESCUES_TEMPLATE_PATH], assigns)
+ end
+
+ def debug_params(params)
+ clean_params = params.clone
+ clean_params.delete("action")
+ clean_params.delete("controller")
+
+ if clean_params.empty?
+ "None"
+ else
+ PP.pp(clean_params, +"", 200)
+ end
+ end
+
+ def debug_headers(headers)
+ if headers.present?
+ headers.inspect.gsub(",", ",\n")
+ else
+ "None"
+ end
+ end
+
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ end
+
+ def render(*)
+ logger = ActionView::Base.logger
+
+ if logger && logger.respond_to?(:silence)
+ logger.silence { super }
+ else
+ super
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/host_authorization.rb b/actionpack/lib/action_dispatch/middleware/host_authorization.rb
new file mode 100644
index 0000000000..447b70112a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/host_authorization.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require "action_dispatch/http/request"
+
+module ActionDispatch
+ # This middleware guards from DNS rebinding attacks by white-listing the
+ # hosts a request can be sent to.
+ #
+ # When a request comes to an unauthorized host, the +response_app+
+ # application will be executed and rendered. If no +response_app+ is given, a
+ # default one will run, which responds with +403 Forbidden+.
+ class HostAuthorization
+ class Permissions # :nodoc:
+ def initialize(hosts)
+ @hosts = sanitize_hosts(hosts)
+ end
+
+ def empty?
+ @hosts.empty?
+ end
+
+ def allows?(host)
+ @hosts.any? do |allowed|
+ allowed === host
+ rescue
+ # IPAddr#=== raises an error if you give it a hostname instead of
+ # IP. Treat similar errors as blocked access.
+ false
+ end
+ end
+
+ private
+
+ def sanitize_hosts(hosts)
+ Array(hosts).map do |host|
+ case host
+ when Regexp then sanitize_regexp(host)
+ when String then sanitize_string(host)
+ else host
+ end
+ end
+ end
+
+ def sanitize_regexp(host)
+ /\A#{host}\z/
+ end
+
+ def sanitize_string(host)
+ if host.start_with?(".")
+ /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/
+ else
+ host
+ end
+ end
+ end
+
+ DEFAULT_RESPONSE_APP = -> env do
+ request = Request.new(env)
+
+ format = request.xhr? ? "text/plain" : "text/html"
+ template = DebugView.new(host: request.host)
+ body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
+
+ [403, {
+ "Content-Type" => "#{format}; charset=#{Response.default_charset}",
+ "Content-Length" => body.bytesize.to_s,
+ }, [body]]
+ end
+
+ def initialize(app, hosts, response_app = nil)
+ @app = app
+ @permissions = Permissions.new(hosts)
+ @response_app = response_app || DEFAULT_RESPONSE_APP
+ end
+
+ def call(env)
+ return @app.call(env) if @permissions.empty?
+
+ request = Request.new(env)
+
+ if authorized?(request)
+ mark_as_authorized(request)
+ @app.call(env)
+ else
+ @response_app.call(env)
+ end
+ end
+
+ private
+
+ def authorized?(request)
+ origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "")
+ forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "")
+
+ @permissions.allows?(origin_host) &&
+ (forwarded_host.blank? || @permissions.allows?(forwarded_host))
+ end
+
+ def mark_as_authorized(request)
+ request.set_header("action_dispatch.authorized_host", request.host)
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 35158f9062..a5667573f4 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -162,14 +162,12 @@ module ActionDispatch
# Split the comma-separated list into an array of strings.
ips = header.strip.split(/[,\s]+/)
ips.select do |ip|
- begin
- # Only return IPs that are valid according to the IPAddr#new method.
- range = IPAddr.new(ip).to_range
- # We want to make sure nobody is sneaking a netmask in.
- range.begin == range.end
- rescue ArgumentError
- nil
- end
+ # Only return IPs that are valid according to the IPAddr#new method.
+ range = IPAddr.new(ip).to_range
+ # We want to make sure nobody is sneaking a netmask in.
+ range.begin == range.end
+ rescue ArgumentError
+ nil
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb
new file mode 100644
index 0000000000..2fa78dd385
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb
@@ -0,0 +1,7 @@
+<header>
+ <h1>Blocked host: <%= @host %></h1>
+</header>
+<div id="container">
+ <h2>To allow requests to <%= @host %>, add the following configuration:</h2>
+ <pre>Rails.application.config.hosts &lt;&lt; "<%= @host %>"</pre>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb
new file mode 100644
index 0000000000..4e2d1d0b08
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb
@@ -0,0 +1,5 @@
+Blocked host: <%= @host %>
+
+To allow requests to <%= @host %>, add the following configuration:
+
+ Rails.application.config.hosts << "<%= @host %>"
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 2ae75b0da8..2966c969f6 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -90,11 +90,11 @@ module ActionDispatch
def clear!
@path_helpers.each do |helper|
- @path_helpers_module.send :remove_method, helper
+ @path_helpers_module.remove_method helper
end
@url_helpers.each do |helper|
- @url_helpers_module.send :remove_method, helper
+ @url_helpers_module.remove_method helper
end
@routes.clear
@@ -108,8 +108,8 @@ module ActionDispatch
url_name = :"#{name}_url"
if routes.key? key
- @path_helpers_module.send :undef_method, path_name
- @url_helpers_module.send :undef_method, url_name
+ @path_helpers_module.undef_method path_name
+ @url_helpers_module.undef_method url_name
end
routes[key] = route
define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 1e6b21f235..7c1202dc0e 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -14,11 +14,6 @@ module ActionDispatch
new response.status, response.headers, response.body
end
- def initialize(*) # :nodoc:
- super
- @response_parser = RequestEncoder.parser(content_type)
- end
-
# Was the response successful?
def success?
ActiveSupport::Deprecation.warn(<<-MSG.squish)
@@ -47,7 +42,11 @@ module ActionDispatch
end
def parsed_body
- @parsed_body ||= @response_parser.call(body)
+ @parsed_body ||= response_parser.call(body)
+ end
+
+ def response_parser
+ @response_parser ||= RequestEncoder.parser(content_type)
end
end
end