diff --git a/app/assets/javascripts/admin/components/ip-lookup.js.es6 b/app/assets/javascripts/admin/components/ip-lookup.js.es6
index 4717b365028..357ba10d5f5 100644
--- a/app/assets/javascripts/admin/components/ip-lookup.js.es6
+++ b/app/assets/javascripts/admin/components/ip-lookup.js.es6
@@ -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 = $('
');
- copyRange.html(text.trim().replace("\n", "
"));
+ copyRange.html(text.trim().replace(/\n/g, "
"));
$(document.body).append(copyRange);
if (copyText(text, copyRange[0])) {
this.set("copied", true);
diff --git a/app/assets/javascripts/discourse/templates/components/ip-lookup.hbs b/app/assets/javascripts/discourse/templates/components/ip-lookup.hbs
index fec7b7d0229..573db188560 100644
--- a/app/assets/javascripts/discourse/templates/components/ip-lookup.hbs
+++ b/app/assets/javascripts/discourse/templates/components/ip-lookup.hbs
@@ -22,22 +22,16 @@
{{i18n 'ip_lookup.location'}}
- {{#if location.loc}}
- {{location.loc}}
- {{city}}
+ {{#if location.location}}
+ {{location.location}}
{{else}}
{{i18n 'ip_lookup.location_not_found'}}
{{/if}}
- {{#if location.org}}
+ {{#if location.organization}}
{{i18n 'ip_lookup.organisation'}}
- {{location.org}}
- {{/if}}
-
- {{#if location.phone}}
- {{i18n 'ip_lookup.phone'}}
- {{location.phone}}
+ {{location.organization}}
{{/if}}
{{else}}
{{loading-spinner size="small"}}
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index e73015e67cd..776568e4c51 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -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
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 74a99dbf438..275fab90f88 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -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 ipinfo.io"
+ powered_by: "using MaxMindDB"
copied: "copied"
user_fields:
diff --git a/lib/discourse_ip_info.rb b/lib/discourse_ip_info.rb
index 11703cff79d..e01c4457ec4 100644
--- a/lib/discourse_ip_info.rb
+++ b/lib/discourse_ip_info.rb
@@ -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)
+ @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
- @mmdb_filename = File.join(path, 'GeoLite2-City.mmdb')
- @mmdb = MaxMindDB.new(@mmdb_filename, MaxMindDB::LOW_MEMORY_FILE_READER)
- @cache = LruRedux::ThreadSafeCache.new(1000)
+ 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 = {}
- begin
- result = @mmdb.lookup(ip)
- rescue
- Rails.logger.error("IP #{ip} could not be looked up in MaxMindDB.")
+ if @loc_mmdb
+ begin
+ 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 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
diff --git a/lib/tasks/maxminddb.rake b/lib/tasks/maxminddb.rake
index 79b31d2899a..9ce9ea995be 100644
--- a/lib/tasks/maxminddb.rake
+++ b/lib/tasks/maxminddb.rake
@@ -3,22 +3,28 @@ 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")
- tar_gz_archive = Net::HTTP.get(uri)
- extractor = Gem::Package::TarReader.new(Zlib::GzipReader.new(StringIO.new(tar_gz_archive)))
- extractor.rewind
+ 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.each do |entry|
- next unless entry.full_name.ends_with?(".mmdb")
+ extractor = Gem::Package::TarReader.new(Zlib::GzipReader.new(StringIO.new(tar_gz_archive)))
+ extractor.rewind
- filename = File.join(Rails.root, 'vendor', 'data', 'GeoLite2-City.mmdb')
- puts "Writing #{filename}"
- File.open(filename, "wb") do |f|
- f.write(entry.read)
+ extractor.each do |entry|
+ next unless entry.full_name.ends_with?(".mmdb")
+
+ filename = File.join(Rails.root, 'vendor', 'data', "#{name}.mmdb")
+ puts "Writing #{filename}..."
+ File.open(filename, "wb") do |f|
+ f.write(entry.read)
+ end
end
+
+ extractor.close
end
- extractor.close
+ download_mmdb('GeoLite2-City')
+ download_mmdb('GeoLite2-ASN')
end
diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb
index 886eeae9fbf..224131c87ea 100644
--- a/spec/requests/admin/users_controller_spec.rb
+++ b/spec/requests/admin/users_controller_spec.rb
@@ -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