DEV: Use DiscourseIpInfo for all IP queries. (#6482)
* DEV: Use DiscourseIpInfo for all IP queries. * UX: Use latitude and longitude for more precision.
This commit is contained in:
parent
4b7ab97a01
commit
e1e392f15b
|
@ -5,16 +5,6 @@ import copyText from "discourse/lib/copy-text";
|
|||
export default Ember.Component.extend({
|
||||
classNames: ["ip-lookup"],
|
||||
|
||||
city: function() {
|
||||
return [
|
||||
this.get("location.city"),
|
||||
this.get("location.region"),
|
||||
this.get("location.country")
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(", ");
|
||||
}.property("location.{city,region,country}"),
|
||||
|
||||
otherAccountsToDelete: function() {
|
||||
// can only delete up to 50 accounts at a time
|
||||
var total = Math.min(50, this.get("totalOthersWithSameIP") || 0);
|
||||
|
@ -72,24 +62,19 @@ export default Ember.Component.extend({
|
|||
}
|
||||
|
||||
text += I18n.t("ip_lookup.location");
|
||||
if (location.loc) {
|
||||
text += `: ${location.loc} ${this.get("city")}\n`;
|
||||
if (location.location) {
|
||||
text += `: ${location.location}\n`;
|
||||
} else {
|
||||
text += `: ${I18n.t("ip_lookup.location_not_found")}\n`;
|
||||
}
|
||||
|
||||
if (location.org) {
|
||||
if (location.organization) {
|
||||
text += I18n.t("ip_lookup.organisation");
|
||||
text += `: ${location.org}\n`;
|
||||
}
|
||||
|
||||
if (location.phone) {
|
||||
text += I18n.t("ip_lookup.phone");
|
||||
text += `: ${location.phone}\n`;
|
||||
text += `: ${location.organization}\n`;
|
||||
}
|
||||
}
|
||||
const copyRange = $('<p id="copy-range"></p>');
|
||||
copyRange.html(text.trim().replace("\n", "<br>"));
|
||||
copyRange.html(text.trim().replace(/\n/g, "<br>"));
|
||||
$(document.body).append(copyRange);
|
||||
if (copyText(text, copyRange[0])) {
|
||||
this.set("copied", true);
|
||||
|
|
|
@ -22,22 +22,16 @@
|
|||
|
||||
<dt>{{i18n 'ip_lookup.location'}}</dt>
|
||||
<dd>
|
||||
{{#if location.loc}}
|
||||
<a href="https://maps.google.com/maps?q={{unbound location.loc}}" target="_blank">{{location.loc}}</a><br>
|
||||
{{city}}
|
||||
{{#if location.location}}
|
||||
<a href="https://maps.google.com/maps?q={{unbound location.latitude}},{{unbound location.longitude}}" target="_blank">{{location.location}}</a>
|
||||
{{else}}
|
||||
{{i18n 'ip_lookup.location_not_found'}}
|
||||
{{/if}}
|
||||
</dd>
|
||||
|
||||
{{#if location.org}}
|
||||
{{#if location.organization}}
|
||||
<dt>{{i18n 'ip_lookup.organisation'}}</dt>
|
||||
<dd>{{location.org}}</dd>
|
||||
{{/if}}
|
||||
|
||||
{{#if location.phone}}
|
||||
<dt>{{i18n 'ip_lookup.phone'}}</dt>
|
||||
<dd>{{location.phone}}</dd>
|
||||
<dd>{{location.organization}}</dd>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{loading-spinner size="small"}}
|
||||
|
|
|
@ -435,18 +435,8 @@ class Admin::UsersController < Admin::AdminController
|
|||
|
||||
def ip_info
|
||||
params.require(:ip)
|
||||
ip = params[:ip]
|
||||
|
||||
# should we cache results in redis?
|
||||
begin
|
||||
location = Excon.get(
|
||||
"https://ipinfo.io/#{ip}/json",
|
||||
read_timeout: 10, connect_timeout: 10
|
||||
)&.body
|
||||
rescue Excon::Error
|
||||
end
|
||||
|
||||
render json: location
|
||||
render json: DiscourseIpInfo.get(params[:ip])
|
||||
end
|
||||
|
||||
def sync_sso
|
||||
|
|
|
@ -597,7 +597,7 @@ en:
|
|||
topics_entered: "topics entered"
|
||||
post_count: "# posts"
|
||||
confirm_delete_other_accounts: "Are you sure you want to delete these accounts?"
|
||||
powered_by: "powered by <a href='https://ipinfo.io'>ipinfo.io</a>"
|
||||
powered_by: "using <a href='https://maxmind.com'>MaxMindDB</a>"
|
||||
copied: "copied"
|
||||
|
||||
user_fields:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require_dependency 'maxminddb'
|
||||
require_dependency 'resolv'
|
||||
|
||||
class DiscourseIpInfo
|
||||
include Singleton
|
||||
|
@ -8,10 +9,14 @@ class DiscourseIpInfo
|
|||
end
|
||||
|
||||
def open_db(path)
|
||||
begin
|
||||
@mmdb_filename = File.join(path, 'GeoLite2-City.mmdb')
|
||||
@mmdb = MaxMindDB.new(@mmdb_filename, MaxMindDB::LOW_MEMORY_FILE_READER)
|
||||
@loc_mmdb = mmdb_load(File.join(path, 'GeoLite2-City.mmdb'))
|
||||
@asn_mmdb = mmdb_load(File.join(path, 'GeoLite2-ASN.mmdb'))
|
||||
@cache = LruRedux::ThreadSafeCache.new(1000)
|
||||
end
|
||||
|
||||
def mmdb_load(filepath)
|
||||
begin
|
||||
MaxMindDB.new(filepath, MaxMindDB::LOW_MEMORY_FILE_READER)
|
||||
rescue Errno::ENOENT => e
|
||||
Rails.logger.warn("MaxMindDB could not be found: #{e}")
|
||||
rescue
|
||||
|
@ -20,30 +25,51 @@ class DiscourseIpInfo
|
|||
end
|
||||
|
||||
def lookup(ip, locale = :en)
|
||||
return {} unless @mmdb
|
||||
ret = {}
|
||||
|
||||
if @loc_mmdb
|
||||
begin
|
||||
result = @mmdb.lookup(ip)
|
||||
result = @loc_mmdb.lookup(ip)
|
||||
if result&.found?
|
||||
ret[:country] = result.country.name(locale) || result.country.name
|
||||
ret[:country_code] = result.country.iso_code
|
||||
ret[:region] = result.subdivisions.most_specific.name(locale) || result.subdivisions.most_specific.name
|
||||
ret[:city] = result.city.name(locale) || result.city.name
|
||||
ret[:latitude] = result.location.latitude
|
||||
ret[:longitude] = result.location.longitude
|
||||
ret[:location] = [ret[:city], ret[:region], ret[:country]].reject(&:blank?).join(", ")
|
||||
end
|
||||
rescue
|
||||
Rails.logger.error("IP #{ip} could not be looked up in MaxMindDB.")
|
||||
Rails.logger.error("IP #{ip} could not be looked up in MaxMind GeoLite2-City database.")
|
||||
end
|
||||
end
|
||||
|
||||
return {} if !result || !result.found?
|
||||
if @asn_mmdb
|
||||
begin
|
||||
result = @asn_mmdb.lookup(ip)
|
||||
if result&.found?
|
||||
result = result.to_hash
|
||||
ret[:asn] = result["autonomous_system_number"]
|
||||
ret[:organization] = result["autonomous_system_organization"]
|
||||
end
|
||||
rescue
|
||||
Rails.logger.error("IP #{ip} could not be looked up in MaxMind GeoLite2-ASN database.")
|
||||
end
|
||||
end
|
||||
|
||||
locale = locale.to_s.sub('_', '-')
|
||||
begin
|
||||
result = Resolv::DNS.new.getname(ip)
|
||||
ret[:hostname] = result&.to_s
|
||||
rescue Resolv::ResolvError
|
||||
end
|
||||
|
||||
{
|
||||
country: result.country.name(locale) || result.country.name,
|
||||
country_code: result.country.iso_code,
|
||||
region: result.subdivisions.most_specific.name(locale) || result.subdivisions.most_specific.name,
|
||||
city: result.city.name(locale) || result.city.name,
|
||||
}
|
||||
ret
|
||||
end
|
||||
|
||||
def get(ip, locale = :en)
|
||||
return {} unless @mmdb
|
||||
|
||||
ip = ip.to_s
|
||||
locale = locale.to_s.sub('_', '-')
|
||||
|
||||
@cache["#{ip}-#{locale}"] ||= lookup(ip, locale)
|
||||
end
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@ require 'zlib'
|
|||
|
||||
desc "downloads MaxMind's GeoLite2-City database"
|
||||
task "maxminddb:get" do
|
||||
puts "Downloading maxmind db"
|
||||
uri = URI("http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz")
|
||||
|
||||
def download_mmdb(name)
|
||||
puts "Downloading MaxMindDb #{name}"
|
||||
uri = URI("http://geolite.maxmind.com/download/geoip/database/#{name}.tar.gz")
|
||||
tar_gz_archive = Net::HTTP.get(uri)
|
||||
|
||||
extractor = Gem::Package::TarReader.new(Zlib::GzipReader.new(StringIO.new(tar_gz_archive)))
|
||||
|
@ -13,8 +15,8 @@ task "maxminddb:get" do
|
|||
extractor.each do |entry|
|
||||
next unless entry.full_name.ends_with?(".mmdb")
|
||||
|
||||
filename = File.join(Rails.root, 'vendor', 'data', 'GeoLite2-City.mmdb')
|
||||
puts "Writing #{filename}"
|
||||
filename = File.join(Rails.root, 'vendor', 'data', "#{name}.mmdb")
|
||||
puts "Writing #{filename}..."
|
||||
File.open(filename, "wb") do |f|
|
||||
f.write(entry.read)
|
||||
end
|
||||
|
@ -22,3 +24,7 @@ task "maxminddb:get" do
|
|||
|
||||
extractor.close
|
||||
end
|
||||
|
||||
download_mmdb('GeoLite2-City')
|
||||
download_mmdb('GeoLite2-ASN')
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'rails_helper'
|
||||
require 'discourse_ip_info'
|
||||
|
||||
RSpec.describe Admin::UsersController do
|
||||
let(:admin) { Fabricate(:admin) }
|
||||
|
@ -710,19 +711,24 @@ RSpec.describe Admin::UsersController do
|
|||
end
|
||||
|
||||
describe '#ip_info' do
|
||||
it "uses ipinfo.io webservice to retrieve the info" do
|
||||
ip = "192.168.1.1"
|
||||
ip_data = {
|
||||
city: "Jeddah",
|
||||
country: "SA",
|
||||
ip: ip
|
||||
}
|
||||
url = "https://ipinfo.io/#{ip}/json"
|
||||
it "retrieves IP info" do
|
||||
ip = "81.2.69.142"
|
||||
|
||||
DiscourseIpInfo.open_db(File.join(Rails.root, 'spec', 'fixtures', 'mmdb'))
|
||||
Resolv::DNS.any_instance.stubs(:getname).with(ip).returns("ip-81-2-69-142.example.com")
|
||||
|
||||
stub_request(:get, url).to_return(status: 200, body: ip_data.to_json)
|
||||
get "/admin/users/ip-info.json", params: { ip: ip }
|
||||
expect(response.status).to eq(200)
|
||||
expect(JSON.parse(response.body).symbolize_keys).to eq(ip_data)
|
||||
expect(JSON.parse(response.body).symbolize_keys).to eq(
|
||||
city: "London",
|
||||
country: "United Kingdom",
|
||||
country_code: "GB",
|
||||
hostname: "ip-81-2-69-142.example.com",
|
||||
location: "London, England, United Kingdom",
|
||||
region: "England",
|
||||
latitude: 51.5142,
|
||||
longitude: -0.0931,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue