PageRenderTime 33ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/geocoder/request.rb

http://github.com/alexreisner/geocoder
Ruby | 114 lines | 64 code | 13 blank | 37 comment | 7 complexity | 248ea9493a06e60ede40203721ef96a5 MD5 | raw file
  1. require 'ipaddr'
  2. module Geocoder
  3. module Request
  4. # The location() method is vulnerable to trivial IP spoofing.
  5. # Don't use it in authorization/authentication code, or any
  6. # other security-sensitive application. Use safe_location
  7. # instead.
  8. def location
  9. @location ||= Geocoder.search(geocoder_spoofable_ip, ip_address: true).first
  10. end
  11. # This safe_location() protects you from trivial IP spoofing.
  12. # For requests that go through a proxy that you haven't
  13. # whitelisted as trusted in your Rack config, you will get the
  14. # location for the IP of the last untrusted proxy in the chain,
  15. # not the original client IP. You WILL NOT get the location
  16. # corresponding to the original client IP for any request sent
  17. # through a non-whitelisted proxy.
  18. def safe_location
  19. @safe_location ||= Geocoder.search(ip, ip_address: true).first
  20. end
  21. # There's a whole zoo of nonstandard headers added by various
  22. # proxy softwares to indicate original client IP.
  23. # ANY of these can be trivially spoofed!
  24. # (except REMOTE_ADDR, which should by set by your server,
  25. # and is included at the end as a fallback.
  26. # Order does matter: we're following the convention established in
  27. # ActionDispatch::RemoteIp::GetIp::calculate_ip()
  28. # https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/remote_ip.rb
  29. # where the forwarded_for headers, possibly containing lists,
  30. # are arbitrarily preferred over headers expected to contain a
  31. # single address.
  32. GEOCODER_CANDIDATE_HEADERS = ['HTTP_X_FORWARDED_FOR',
  33. 'HTTP_X_FORWARDED',
  34. 'HTTP_FORWARDED_FOR',
  35. 'HTTP_FORWARDED',
  36. 'HTTP_X_CLIENT_IP',
  37. 'HTTP_CLIENT_IP',
  38. 'HTTP_X_REAL_IP',
  39. 'HTTP_X_CLUSTER_CLIENT_IP',
  40. 'REMOTE_ADDR']
  41. def geocoder_spoofable_ip
  42. # We could use a more sophisticated IP-guessing algorithm here,
  43. # in which we'd try to resolve the use of different headers by
  44. # different proxies. The idea is that by comparing IPs repeated
  45. # in different headers, you can sometimes decide which header
  46. # was used by a proxy further along in the chain, and thus
  47. # prefer the headers used earlier. However, the gains might not
  48. # be worth the performance tradeoff, since this method is likely
  49. # to be called on every request in a lot of applications.
  50. GEOCODER_CANDIDATE_HEADERS.each do |header|
  51. if @env.has_key? header
  52. addrs = geocoder_split_ip_addresses(@env[header])
  53. addrs = geocoder_remove_port_from_addresses(addrs)
  54. addrs = geocoder_reject_non_ipv4_addresses(addrs)
  55. addrs = geocoder_reject_trusted_ip_addresses(addrs)
  56. return addrs.first if addrs.any?
  57. end
  58. end
  59. @env['REMOTE_ADDR']
  60. end
  61. private
  62. def geocoder_split_ip_addresses(ip_addresses)
  63. ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
  64. end
  65. # use Rack's trusted_proxy?() method to filter out IPs that have
  66. # been configured as trusted; includes private ranges by
  67. # default. (we don't want every lookup to return the location
  68. # of our own proxy/load balancer)
  69. def geocoder_reject_trusted_ip_addresses(ip_addresses)
  70. ip_addresses.reject { |ip| trusted_proxy?(ip) }
  71. end
  72. def geocoder_remove_port_from_addresses(ip_addresses)
  73. ip_addresses.map do |ip|
  74. # IPv4
  75. if ip.count('.') > 0
  76. ip.split(':').first
  77. # IPv6 bracket notation
  78. elsif match = ip.match(/\[(\S+)\]/)
  79. match.captures.first
  80. # IPv6 bare notation
  81. else
  82. ip
  83. end
  84. end
  85. end
  86. def geocoder_reject_non_ipv4_addresses(ip_addresses)
  87. ips = []
  88. for ip in ip_addresses
  89. begin
  90. valid_ip = IPAddr.new(ip)
  91. rescue
  92. valid_ip = false
  93. end
  94. ips << valid_ip.to_s if valid_ip
  95. end
  96. return ips.any? ? ips : ip_addresses
  97. end
  98. end
  99. end
  100. ActionDispatch::Request.__send__(:include, Geocoder::Request) if defined?(ActionDispatch::Request)
  101. Rack::Request.__send__(:include, Geocoder::Request) if defined?(Rack::Request)