提交 2591cc66 编写于 作者: J José Valim

Merge pull request #3617 from indirect/remote_ip

refactor RemoteIp middleware
......@@ -155,24 +155,7 @@ def ip
@ip ||= super
end
# Which IP addresses are "trusted proxies" that can be stripped from
# the right-hand-side of X-Forwarded-For.
#
# http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces.
TRUSTED_PROXIES = %r{
^127\.0\.0\.1$ | # localhost
^(10 | # private IP 10.x.x.x
172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
192\.168 # private IP 192.168.x.x
)\.
}x
# Determines originating IP address. REMOTE_ADDR is the standard
# but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
# HTTP_X_FORWARDED_FOR are set by proxies so check for these if
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
# delimited list in the case of multiple chained proxies; the last
# address which is not trusted is the originating IP.
# Originating IP address, usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end
......
......@@ -2,50 +2,69 @@ module ActionDispatch
class RemoteIp
class IpSpoofAttackError < StandardError ; end
class RemoteIpGetter
def initialize(env, check_ip_spoofing, trusted_proxies)
@env = env
@check_ip_spoofing = check_ip_spoofing
@trusted_proxies = trusted_proxies
# IP addresses that are "trusted proxies" that can be stripped from
# the comma-delimited list in the X-Forwarded-For header. See also:
# http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
TRUSTED_PROXIES = %r{
^127\.0\.0\.1$ | # localhost
^(10 | # private IP 10.x.x.x
172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
192\.168 # private IP 192.168.x.x
)\.
}x
attr_reader :check_ip_spoofing, :trusted_proxies
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
@app = app
@check_ip_spoofing = check_ip_spoofing
if custom_proxies
custom_regexp = Regexp.new(custom_proxies, "i")
@trusted_proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp)
else
@trusted_proxies = TRUSTED_PROXIES
end
end
def remote_addrs
@remote_addrs ||= begin
list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
list.reject { |addr| addr =~ @trusted_proxies }
end
def call(env)
env["action_dispatch.remote_ip"] = GetIp.new(env, self)
@app.call(env)
end
class GetIp
def initialize(env, middleware)
@env, @middleware = env, middleware
end
# Determines originating IP address. REMOTE_ADDR is the standard
# but will be wrong if the user is behind a proxy. Proxies will set
# HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
# HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
# multiple chained proxies. The last address which is not a known proxy
# will be the originating IP.
def to_s
return remote_addrs.first if remote_addrs.any?
forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
if client_ip = @env['HTTP_CLIENT_IP']
if @check_ip_spoofing && !forwarded_ips.include?(client_ip)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?!" \
"HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
end
return client_ip
client_ip = @env['HTTP_CLIENT_IP']
forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR')
remote_addrs = ips_from('REMOTE_ADDR')
check_ip = client_ip && @middleware.check_ip_spoofing
if check_ip && !forwarded_ips.include?(client_ip)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?!" \
"HTTP_CLIENT_IP=#{env['HTTP_CLIENT_IP'].inspect}" \
"HTTP_X_FORWARDED_FOR=#{env['HTTP_X_FORWARDED_FOR'].inspect}"
end
return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"]
client_ip || forwarded_ips.last || remote_addrs.last
end
end
def initialize(app, check_ip_spoofing = true, trusted_proxies = nil)
@app = app
@check_ip_spoofing = check_ip_spoofing
regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)'
regex << "|(#{trusted_proxies})" if trusted_proxies
@trusted_proxies = Regexp.new(regex, "i")
end
protected
def call(env)
env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies)
@app.call(env)
def ips_from(header)
ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
ips.reject{|ip| ip =~ @middleware.trusted_proxies }
end
end
end
end
\ No newline at end of file
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册