mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Introduce a guard against DNS rebinding attacks
The ActionDispatch::HostAuthorization is a new middleware that prevent against DNS rebinding and other Host header attacks. By default it is included only in the development environment with the following configuration: Rails.application.config.hosts = [ IPAddr.new("0.0.0.0/0"), # All IPv4 addresses. IPAddr.new("::/0"), # All IPv6 addresses. "localhost" # The localhost reserved domain. ] In other environments, `Rails.application.config.hosts` is empty and no Host header checks will be done. If you want to guard against header attacks on production, you have to manually permit the allowed hosts with: Rails.application.config.hosts << "product.com" The host of a request is checked against the hosts entries with the case operator (#===), which lets hosts support entries of type RegExp, Proc and IPAddr to name a few. Here is an example with a regexp. # Allow requests from subdomains like `www.product.com` and # `beta1.product.com`. Rails.application.config.hosts << /.*\.product\.com/ A special case is supported that allows you to permit all sub-domains: # Allow requests from subdomains like `www.product.com` and # `beta1.product.com`. Rails.application.config.hosts << ".product.com"
This commit is contained in:
parent
ce48b5a366
commit
07ec8062e6
15 changed files with 393 additions and 50 deletions
|
@ -1,3 +1,13 @@
|
||||||
|
* Introduce ActionDispatch::HostAuthorization
|
||||||
|
|
||||||
|
This is a new middleware that guards against DNS rebinding attacks by
|
||||||
|
white-listing the allowed hosts a request can be made to.
|
||||||
|
|
||||||
|
Each host is checked with the case operator (`#===`) to support `RegExp`,
|
||||||
|
`Proc`, `IPAddr` and custom objects as host allowances.
|
||||||
|
|
||||||
|
*Genadi Samokovarov*
|
||||||
|
|
||||||
* Raise an error on root route naming conflicts.
|
* Raise an error on root route naming conflicts.
|
||||||
|
|
||||||
Raises an ArgumentError when multiple root routes are defined in the
|
Raises an ArgumentError when multiple root routes are defined in the
|
||||||
|
|
|
@ -49,11 +49,13 @@ module ActionDispatch
|
||||||
end
|
end
|
||||||
|
|
||||||
autoload_under "middleware" do
|
autoload_under "middleware" do
|
||||||
|
autoload :HostAuthorization
|
||||||
autoload :RequestId
|
autoload :RequestId
|
||||||
autoload :Callbacks
|
autoload :Callbacks
|
||||||
autoload :Cookies
|
autoload :Cookies
|
||||||
autoload :DebugExceptions
|
autoload :DebugExceptions
|
||||||
autoload :DebugLocks
|
autoload :DebugLocks
|
||||||
|
autoload :DebugView
|
||||||
autoload :ExceptionWrapper
|
autoload :ExceptionWrapper
|
||||||
autoload :Executor
|
autoload :Executor
|
||||||
autoload :Flash
|
autoload :Flash
|
||||||
|
|
|
@ -3,53 +3,14 @@
|
||||||
require "action_dispatch/http/request"
|
require "action_dispatch/http/request"
|
||||||
require "action_dispatch/middleware/exception_wrapper"
|
require "action_dispatch/middleware/exception_wrapper"
|
||||||
require "action_dispatch/routing/inspector"
|
require "action_dispatch/routing/inspector"
|
||||||
|
|
||||||
require "action_view"
|
require "action_view"
|
||||||
require "action_view/base"
|
require "action_view/base"
|
||||||
|
|
||||||
require "pp"
|
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
# This middleware is responsible for logging exceptions and
|
# This middleware is responsible for logging exceptions and
|
||||||
# showing a debugging page in case the request is local.
|
# showing a debugging page in case the request is local.
|
||||||
class DebugExceptions
|
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: []
|
cattr_reader :interceptors, instance_accessor: false, default: []
|
||||||
|
|
||||||
def self.register_interceptor(object = nil, &block)
|
def self.register_interceptor(object = nil, &block)
|
||||||
|
@ -152,7 +113,7 @@ module ActionDispatch
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_template(request, wrapper)
|
def create_template(request, wrapper)
|
||||||
DebugView.new([RESCUES_TEMPLATE_PATH],
|
DebugView.new(
|
||||||
request: request,
|
request: request,
|
||||||
exception_wrapper: wrapper,
|
exception_wrapper: wrapper,
|
||||||
exception: wrapper.exception,
|
exception: wrapper.exception,
|
||||||
|
|
50
actionpack/lib/action_dispatch/middleware/debug_view.rb
Normal file
50
actionpack/lib/action_dispatch/middleware/debug_view.rb
Normal file
|
@ -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
|
105
actionpack/lib/action_dispatch/middleware/host_authorization.rb
Normal file
105
actionpack/lib/action_dispatch/middleware/host_authorization.rb
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
# 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|
|
||||||
|
begin
|
||||||
|
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
|
||||||
|
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
|
|
@ -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 << "<%= @host %>"</pre>
|
||||||
|
</div>
|
|
@ -0,0 +1,5 @@
|
||||||
|
Blocked host: <%= @host %>
|
||||||
|
|
||||||
|
To allow requests to <%= @host %>, add the following configuration:
|
||||||
|
|
||||||
|
Rails.application.config.hosts << "<%= @host %>"
|
160
actionpack/test/dispatch/host_authorization_test.rb
Normal file
160
actionpack/test/dispatch/host_authorization_test.rb
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_unit"
|
||||||
|
|
||||||
|
class HostAuthorizationTest < ActionDispatch::IntegrationTest
|
||||||
|
App = -> env { [200, {}, %w(Success)] }
|
||||||
|
|
||||||
|
test "blocks requests to unallowed host" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, %w(only.com))
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
|
||||||
|
assert_response :forbidden
|
||||||
|
assert_match "Blocked host: www.example.com", response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "passes all requests to if the whitelist is empty" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, nil)
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
|
||||||
|
assert_response :ok
|
||||||
|
assert_equal "Success", body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "passes requests to allowed host" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, %w(www.example.com))
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
|
||||||
|
assert_response :ok
|
||||||
|
assert_equal "Success", body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the whitelist could be a single element" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, "www.example.com")
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
|
||||||
|
assert_response :ok
|
||||||
|
assert_equal "Success", body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "passes requests to allowed hosts with domain name notation" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, ".example.com")
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
|
||||||
|
assert_response :ok
|
||||||
|
assert_equal "Success", body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not allow domain name notation in the HOST header itself" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, ".example.com")
|
||||||
|
|
||||||
|
get "/", env: {
|
||||||
|
"HOST" => ".example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_response :forbidden
|
||||||
|
assert_match "Blocked host: .example.com", response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "checks for requests with #=== to support wider range of host checks" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, [-> input { input == "www.example.com" }])
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
|
||||||
|
assert_response :ok
|
||||||
|
assert_equal "Success", body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "mark the host when authorized" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, ".example.com")
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
|
||||||
|
assert_equal "www.example.com", request.get_header("action_dispatch.authorized_host")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sanitizes regular expressions to prevent accidental matches" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, [/w.example.co/])
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
|
||||||
|
assert_response :forbidden
|
||||||
|
assert_match "Blocked host: www.example.com", response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "blocks requests to unallowed host supporting custom responses" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, ["w.example.co"], -> env do
|
||||||
|
[401, {}, %w(Custom)]
|
||||||
|
end)
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
|
||||||
|
assert_response :unauthorized
|
||||||
|
assert_equal "Custom", body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "blocks requests with spoofed X-FORWARDED-HOST" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, [IPAddr.new("127.0.0.1")])
|
||||||
|
|
||||||
|
get "/", env: {
|
||||||
|
"HTTP_X_FORWARDED_HOST" => "127.0.0.1",
|
||||||
|
"HOST" => "www.example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_response :forbidden
|
||||||
|
assert_match "Blocked host: 127.0.0.1", response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not consider IP addresses in X-FORWARDED-HOST spoofed when disabled" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, nil)
|
||||||
|
|
||||||
|
get "/", env: {
|
||||||
|
"HTTP_X_FORWARDED_HOST" => "127.0.0.1",
|
||||||
|
"HOST" => "www.example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_response :ok
|
||||||
|
assert_equal "Success", body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "detects localhost domain spoofing" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, "localhost")
|
||||||
|
|
||||||
|
get "/", env: {
|
||||||
|
"HTTP_X_FORWARDED_HOST" => "localhost",
|
||||||
|
"HOST" => "www.example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_response :forbidden
|
||||||
|
assert_match "Blocked host: localhost", response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "forwarded hosts should be permitted" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, "domain.com")
|
||||||
|
|
||||||
|
get "/", env: {
|
||||||
|
"HTTP_X_FORWARDED_HOST" => "sub.domain.com",
|
||||||
|
"HOST" => "domain.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_response :forbidden
|
||||||
|
assert_match "Blocked host: sub.domain.com", response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "forwarded hosts are allowed when permitted" do
|
||||||
|
@app = ActionDispatch::HostAuthorization.new(App, ".domain.com")
|
||||||
|
|
||||||
|
get "/", env: {
|
||||||
|
"HTTP_X_FORWARDED_HOST" => "sub.domain.com",
|
||||||
|
"HOST" => "domain.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_response :ok
|
||||||
|
assert_equal "Success", body
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,3 +1,38 @@
|
||||||
|
* Introduce guard against DNS rebinding attacks
|
||||||
|
|
||||||
|
The `ActionDispatch::HostAuthorization` is a new middleware that prevent
|
||||||
|
against DNS rebinding and other `Host` header attacks. It is included in
|
||||||
|
the development environment by default with the following configuration:
|
||||||
|
|
||||||
|
Rails.application.config.hosts = [
|
||||||
|
IPAddr.new("0.0.0.0/0"), # All IPv4 addresses.
|
||||||
|
IPAddr.new("::/0"), # All IPv6 addresses.
|
||||||
|
"localhost" # The localhost reserved domain.
|
||||||
|
]
|
||||||
|
|
||||||
|
In other environments `Rails.application.config.hosts` is empty and no
|
||||||
|
`Host` header checks will be done. If you want to guard against header
|
||||||
|
attacks on production, you have to manually whitelist the allowed hosts
|
||||||
|
with:
|
||||||
|
|
||||||
|
Rails.application.config.hosts << "product.com"
|
||||||
|
|
||||||
|
The host of a request is checked against the `hosts` entries with the case
|
||||||
|
operator (`#===`), which lets `hosts` support entries of type `RegExp`,
|
||||||
|
`Proc` and `IPAddr` to name a few. Here is an example with a regexp.
|
||||||
|
|
||||||
|
# Allow requests from subdomains like `www.product.com` and
|
||||||
|
# `beta1.product.com`.
|
||||||
|
Rails.application.config.hosts << /.*\.product\.com/
|
||||||
|
|
||||||
|
A special case is supported that allows you to whitelist all sub-domains:
|
||||||
|
|
||||||
|
# Allow requests from subdomains like `www.product.com` and
|
||||||
|
# `beta1.product.com`.
|
||||||
|
Rails.application.config.hosts << ".product.com"
|
||||||
|
|
||||||
|
*Genadi Samokovarov*
|
||||||
|
|
||||||
* Remove redundant suffixes on generated helpers.
|
* Remove redundant suffixes on generated helpers.
|
||||||
|
|
||||||
*Gannon McGibbon*
|
*Gannon McGibbon*
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "ipaddr"
|
||||||
require "active_support/core_ext/kernel/reporting"
|
require "active_support/core_ext/kernel/reporting"
|
||||||
require "active_support/file_update_checker"
|
require "active_support/file_update_checker"
|
||||||
require "rails/engine/configuration"
|
require "rails/engine/configuration"
|
||||||
|
@ -11,7 +12,7 @@ module Rails
|
||||||
attr_accessor :allow_concurrency, :asset_host, :autoflush_log,
|
attr_accessor :allow_concurrency, :asset_host, :autoflush_log,
|
||||||
:cache_classes, :cache_store, :consider_all_requests_local, :console,
|
:cache_classes, :cache_store, :consider_all_requests_local, :console,
|
||||||
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
|
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
|
||||||
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
|
:force_ssl, :helpers_paths, :hosts, :logger, :log_formatter, :log_tags,
|
||||||
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
|
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
|
||||||
:ssl_options, :public_file_server,
|
:ssl_options, :public_file_server,
|
||||||
:session_options, :time_zone, :reload_classes_only_on_change,
|
:session_options, :time_zone, :reload_classes_only_on_change,
|
||||||
|
@ -29,6 +30,7 @@ module Rails
|
||||||
@filter_parameters = []
|
@filter_parameters = []
|
||||||
@filter_redirect = []
|
@filter_redirect = []
|
||||||
@helpers_paths = []
|
@helpers_paths = []
|
||||||
|
@hosts = Array(([IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0"), "localhost"] if Rails.env.development?))
|
||||||
@public_file_server = ActiveSupport::OrderedOptions.new
|
@public_file_server = ActiveSupport::OrderedOptions.new
|
||||||
@public_file_server.enabled = true
|
@public_file_server.enabled = true
|
||||||
@public_file_server.index_name = "index"
|
@public_file_server.index_name = "index"
|
||||||
|
|
|
@ -13,6 +13,8 @@ module Rails
|
||||||
|
|
||||||
def build_stack
|
def build_stack
|
||||||
ActionDispatch::MiddlewareStack.new do |middleware|
|
ActionDispatch::MiddlewareStack.new do |middleware|
|
||||||
|
middleware.use ::ActionDispatch::HostAuthorization, config.hosts, config.action_dispatch.hosts_response_app
|
||||||
|
|
||||||
if config.force_ssl
|
if config.force_ssl
|
||||||
middleware.use ::ActionDispatch::SSL, config.ssl_options
|
middleware.use ::ActionDispatch::SSL, config.ssl_options
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ require "rails/application_controller"
|
||||||
require "action_dispatch/routing/inspector"
|
require "action_dispatch/routing/inspector"
|
||||||
|
|
||||||
class Rails::InfoController < Rails::ApplicationController # :nodoc:
|
class Rails::InfoController < Rails::ApplicationController # :nodoc:
|
||||||
prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
|
prepend_view_path ActionDispatch::DebugView::RESCUES_TEMPLATE_PATH
|
||||||
layout -> { request.xhr? ? false : "application" }
|
layout -> { request.xhr? ? false : "application" }
|
||||||
|
|
||||||
before_action :require_local!
|
before_action :require_local!
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
require "rails/application_controller"
|
require "rails/application_controller"
|
||||||
|
|
||||||
class Rails::MailersController < Rails::ApplicationController # :nodoc:
|
class Rails::MailersController < Rails::ApplicationController # :nodoc:
|
||||||
prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
|
prepend_view_path ActionDispatch::DebugView::RESCUES_TEMPLATE_PATH
|
||||||
|
|
||||||
before_action :require_local!, unless: :show_previews?
|
before_action :require_local!, unless: :show_previews?
|
||||||
before_action :find_preview, :set_locale, only: :preview
|
before_action :find_preview, :set_locale, only: :preview
|
||||||
|
|
|
@ -26,6 +26,7 @@ module ApplicationTests
|
||||||
|
|
||||||
assert_equal [
|
assert_equal [
|
||||||
"Webpacker::DevServerProxy",
|
"Webpacker::DevServerProxy",
|
||||||
|
"ActionDispatch::HostAuthorization",
|
||||||
"Rack::Sendfile",
|
"Rack::Sendfile",
|
||||||
"ActionDispatch::Static",
|
"ActionDispatch::Static",
|
||||||
"ActionDispatch::Executor",
|
"ActionDispatch::Executor",
|
||||||
|
@ -58,6 +59,7 @@ module ApplicationTests
|
||||||
|
|
||||||
assert_equal [
|
assert_equal [
|
||||||
"Webpacker::DevServerProxy",
|
"Webpacker::DevServerProxy",
|
||||||
|
"ActionDispatch::HostAuthorization",
|
||||||
"Rack::Sendfile",
|
"Rack::Sendfile",
|
||||||
"ActionDispatch::Static",
|
"ActionDispatch::Static",
|
||||||
"ActionDispatch::Executor",
|
"ActionDispatch::Executor",
|
||||||
|
@ -140,7 +142,7 @@ module ApplicationTests
|
||||||
add_to_config "config.ssl_options = { redirect: { host: 'example.com' } }"
|
add_to_config "config.ssl_options = { redirect: { host: 'example.com' } }"
|
||||||
boot!
|
boot!
|
||||||
|
|
||||||
assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware[1].args
|
assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware[2].args
|
||||||
end
|
end
|
||||||
|
|
||||||
test "removing Active Record omits its middleware" do
|
test "removing Active Record omits its middleware" do
|
||||||
|
@ -224,7 +226,7 @@ module ApplicationTests
|
||||||
test "insert middleware after" do
|
test "insert middleware after" do
|
||||||
add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config"
|
add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config"
|
||||||
boot!
|
boot!
|
||||||
assert_equal "Rack::Config", middleware.third
|
assert_equal "Rack::Config", middleware.fourth
|
||||||
end
|
end
|
||||||
|
|
||||||
test "unshift middleware" do
|
test "unshift middleware" do
|
||||||
|
@ -236,19 +238,19 @@ module ApplicationTests
|
||||||
test "Rails.cache does not respond to middleware" do
|
test "Rails.cache does not respond to middleware" do
|
||||||
add_to_config "config.cache_store = :memory_store"
|
add_to_config "config.cache_store = :memory_store"
|
||||||
boot!
|
boot!
|
||||||
assert_equal "Rack::Runtime", middleware.fifth
|
assert_equal "Rack::Runtime", middleware[5]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Rails.cache does respond to middleware" do
|
test "Rails.cache does respond to middleware" do
|
||||||
boot!
|
boot!
|
||||||
assert_equal "ActiveSupport::Cache::Strategy::LocalCache", middleware.fifth
|
assert_equal "ActiveSupport::Cache::Strategy::LocalCache", middleware[5]
|
||||||
assert_equal "Rack::Runtime", middleware[5]
|
assert_equal "Rack::Runtime", middleware[6]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "insert middleware before" do
|
test "insert middleware before" do
|
||||||
add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config"
|
add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config"
|
||||||
boot!
|
boot!
|
||||||
assert_equal "Rack::Config", middleware.second
|
assert_equal "Rack::Config", middleware.third
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can't change middleware after it's built" do
|
test "can't change middleware after it's built" do
|
||||||
|
|
|
@ -197,6 +197,7 @@ module TestHelpers
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_config <<-RUBY
|
add_to_config <<-RUBY
|
||||||
|
config.hosts << proc { true }
|
||||||
config.eager_load = false
|
config.eager_load = false
|
||||||
config.session_store :cookie_store, key: "_myapp_session"
|
config.session_store :cookie_store, key: "_myapp_session"
|
||||||
config.active_support.deprecation = :log
|
config.active_support.deprecation = :log
|
||||||
|
@ -220,6 +221,7 @@ module TestHelpers
|
||||||
@app = Class.new(Rails::Application) do
|
@app = Class.new(Rails::Application) do
|
||||||
def self.name; "RailtiesTestApp"; end
|
def self.name; "RailtiesTestApp"; end
|
||||||
end
|
end
|
||||||
|
@app.config.hosts << proc { true }
|
||||||
@app.config.eager_load = false
|
@app.config.eager_load = false
|
||||||
@app.config.session_store :cookie_store, key: "_myapp_session"
|
@app.config.session_store :cookie_store, key: "_myapp_session"
|
||||||
@app.config.active_support.deprecation = :log
|
@app.config.active_support.deprecation = :log
|
||||||
|
|
Loading…
Reference in a new issue