Allow IPs with port in the HostAuthorization middleware
When a host was an IP with a port, the IPAddr object comparisson was raising an exception which was making the middleware reject the request. Now we extract the hostname out of the host when comparing with IPAddr objects.
This commit is contained in:
parent
0afa65723a
commit
3a4275fa31
@ -17,7 +17,15 @@ module ActionDispatch
|
||||
# if +config.consider_all_requests_local+ is set to true, otherwise the body is empty.
|
||||
class HostAuthorization
|
||||
ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
|
||||
PORT_REGEX = /(?::\d+)?/.freeze
|
||||
PORT_REGEX = /(?::\d+)/ # :nodoc:
|
||||
IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
|
||||
IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
|
||||
IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
|
||||
VALID_IP_HOSTNAME = Regexp.union( # :nodoc:
|
||||
/\A#{IPV4_HOSTNAME}\z/,
|
||||
/\A#{IPV6_HOSTNAME}\z/,
|
||||
/\A#{IPV6_HOSTNAME_WITH_PORT}\z/,
|
||||
)
|
||||
|
||||
class Permissions # :nodoc:
|
||||
def initialize(hosts)
|
||||
@ -30,11 +38,17 @@ def empty?
|
||||
|
||||
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
|
||||
if allowed.is_a?(IPAddr)
|
||||
begin
|
||||
allowed === extract_hostname(host)
|
||||
rescue
|
||||
# IPAddr#=== raises an error if you give it a hostname instead of
|
||||
# IP. Treat similar errors as blocked access.
|
||||
false
|
||||
end
|
||||
else
|
||||
allowed === host
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -50,16 +64,20 @@ def sanitize_hosts(hosts)
|
||||
end
|
||||
|
||||
def sanitize_regexp(host)
|
||||
/\A#{host}#{PORT_REGEX}\z/
|
||||
/\A#{host}#{PORT_REGEX}?\z/
|
||||
end
|
||||
|
||||
def sanitize_string(host)
|
||||
if host.start_with?(".")
|
||||
/\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}\z/i
|
||||
/\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
|
||||
else
|
||||
/\A#{Regexp.escape host}#{PORT_REGEX}\z/i
|
||||
/\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
|
||||
end
|
||||
end
|
||||
|
||||
def extract_hostname(host)
|
||||
host.slice(VALID_IP_HOSTNAME, "host") || host
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultResponseApp # :nodoc:
|
||||
|
@ -166,6 +166,102 @@ class HostAuthorizationTest < ActionDispatch::IntegrationTest
|
||||
assert_match "Success", response.body
|
||||
end
|
||||
|
||||
test "localhost using IPV4 works in dev" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ActionDispatch::HostAuthorization::ALLOWED_HOSTS_IN_DEVELOPMENT)
|
||||
|
||||
get "/", env: {
|
||||
"HOST" => "127.0.0.1",
|
||||
"action_dispatch.show_detailed_exceptions" => true
|
||||
}
|
||||
|
||||
assert_response :ok
|
||||
assert_match "Success", response.body
|
||||
end
|
||||
|
||||
test "localhost using IPV4 with port works in dev" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ActionDispatch::HostAuthorization::ALLOWED_HOSTS_IN_DEVELOPMENT)
|
||||
|
||||
get "/", env: {
|
||||
"HOST" => "127.0.0.1:3000",
|
||||
"action_dispatch.show_detailed_exceptions" => true
|
||||
}
|
||||
|
||||
assert_response :ok
|
||||
assert_match "Success", response.body
|
||||
end
|
||||
|
||||
test "localhost using IPV4 binding in all addresses works in dev" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ActionDispatch::HostAuthorization::ALLOWED_HOSTS_IN_DEVELOPMENT)
|
||||
|
||||
get "/", env: {
|
||||
"HOST" => "0.0.0.0",
|
||||
"action_dispatch.show_detailed_exceptions" => true
|
||||
}
|
||||
|
||||
assert_response :ok
|
||||
assert_match "Success", response.body
|
||||
end
|
||||
|
||||
test "localhost using IPV4 with port binding in all addresses works in dev" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ActionDispatch::HostAuthorization::ALLOWED_HOSTS_IN_DEVELOPMENT)
|
||||
|
||||
get "/", env: {
|
||||
"HOST" => "0.0.0.0:3000",
|
||||
"action_dispatch.show_detailed_exceptions" => true
|
||||
}
|
||||
|
||||
assert_response :ok
|
||||
assert_match "Success", response.body
|
||||
end
|
||||
|
||||
test "localhost using IPV6 works in dev" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ActionDispatch::HostAuthorization::ALLOWED_HOSTS_IN_DEVELOPMENT)
|
||||
|
||||
get "/", env: {
|
||||
"HOST" => "::1",
|
||||
"action_dispatch.show_detailed_exceptions" => true
|
||||
}
|
||||
|
||||
assert_response :ok
|
||||
assert_match "Success", response.body
|
||||
end
|
||||
|
||||
test "localhost using IPV6 with port works in dev" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ActionDispatch::HostAuthorization::ALLOWED_HOSTS_IN_DEVELOPMENT)
|
||||
|
||||
get "/", env: {
|
||||
"HOST" => "[::1]:3000",
|
||||
"action_dispatch.show_detailed_exceptions" => true
|
||||
}
|
||||
|
||||
assert_response :ok
|
||||
assert_match "Success", response.body
|
||||
end
|
||||
|
||||
test "localhost using IPV6 binding in all addresses works in dev" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ActionDispatch::HostAuthorization::ALLOWED_HOSTS_IN_DEVELOPMENT)
|
||||
|
||||
get "/", env: {
|
||||
"HOST" => "::",
|
||||
"action_dispatch.show_detailed_exceptions" => true
|
||||
}
|
||||
|
||||
assert_response :ok
|
||||
assert_match "Success", response.body
|
||||
end
|
||||
|
||||
test "localhost using IPV6 with port binding in all addresses works in dev" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ActionDispatch::HostAuthorization::ALLOWED_HOSTS_IN_DEVELOPMENT)
|
||||
|
||||
get "/", env: {
|
||||
"HOST" => "[::]:3000",
|
||||
"action_dispatch.show_detailed_exceptions" => true
|
||||
}
|
||||
|
||||
assert_response :ok
|
||||
assert_match "Success", response.body
|
||||
end
|
||||
|
||||
test "hosts with port works" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ["host.test"])
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user