maxmind-geoip2 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/Gemfile +1 -7
  4. data/README.dev.md +1 -1
  5. data/README.md +37 -15
  6. data/lib/maxmind/geoip2/client.rb +48 -26
  7. data/lib/maxmind/geoip2/model/anonymous_ip.rb +8 -0
  8. data/lib/maxmind/geoip2/reader.rb +43 -18
  9. data/lib/maxmind/geoip2/record/traits.rb +12 -3
  10. data/maxmind-geoip2.gemspec +12 -5
  11. data/test/data/bad-data/maxminddb-python/bad-unicode-in-map-key.mmdb +0 -0
  12. data/test/data/source-data/GeoIP2-Anonymous-IP-Test.json +1 -0
  13. data/test/data/source-data/GeoIP2-ISP-Test.json +3 -1
  14. data/test/data/source-data/GeoIP2-Precision-Enterprise-Test.json +87 -0
  15. data/test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
  16. data/test/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
  17. data/test/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
  18. data/test/data/test-data/GeoIP2-City-Test.mmdb +0 -0
  19. data/test/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
  20. data/test/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
  21. data/test/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
  22. data/test/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
  23. data/test/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
  24. data/test/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
  25. data/test/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
  26. data/test/data/test-data/GeoIP2-Static-IP-Score-Test.mmdb +0 -0
  27. data/test/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
  28. data/test/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
  29. data/test/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb +0 -0
  30. data/test/data/test-data/MaxMind-DB-string-value-entries.mmdb +0 -0
  31. data/test/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb +0 -0
  32. data/test/data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb +0 -0
  33. data/test/data/test-data/MaxMind-DB-test-decoder.mmdb +0 -0
  34. data/test/data/test-data/MaxMind-DB-test-ipv4-24.mmdb +0 -0
  35. data/test/data/test-data/MaxMind-DB-test-ipv4-28.mmdb +0 -0
  36. data/test/data/test-data/MaxMind-DB-test-ipv4-32.mmdb +0 -0
  37. data/test/data/test-data/MaxMind-DB-test-ipv6-24.mmdb +0 -0
  38. data/test/data/test-data/MaxMind-DB-test-ipv6-28.mmdb +0 -0
  39. data/test/data/test-data/MaxMind-DB-test-ipv6-32.mmdb +0 -0
  40. data/test/data/test-data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  41. data/test/data/test-data/MaxMind-DB-test-mixed-24.mmdb +0 -0
  42. data/test/data/test-data/MaxMind-DB-test-mixed-28.mmdb +0 -0
  43. data/test/data/test-data/MaxMind-DB-test-mixed-32.mmdb +0 -0
  44. data/test/data/test-data/MaxMind-DB-test-nested.mmdb +0 -0
  45. data/test/data/test-data/MaxMind-DB-test-pointer-decoder.mmdb +0 -0
  46. data/test/data/test-data/write-test-data.pl +68 -18
  47. data/test/test_client.rb +4 -2
  48. data/test/test_model_country.rb +16 -0
  49. data/test/test_reader.rb +59 -0
  50. metadata +100 -10
  51. data/Gemfile.lock +0 -70
@@ -13,7 +13,7 @@ module MaxMind
13
13
  # @!visibility private
14
14
  def initialize(record)
15
15
  super(record)
16
- if !record.key?('network') && record.key?('ip_address') &&
16
+ if record && !record.key?('network') && record.key?('ip_address') &&
17
17
  record.key?('prefix_length')
18
18
  ip = IPAddr.new(record['ip_address']).mask(record['prefix_length'])
19
19
  # We could use ip.prefix instead of record['prefix_length'], but that
@@ -69,7 +69,7 @@ module MaxMind
69
69
  # NAT, this may differ from the IP address locally assigned to it. This
70
70
  # attribute is returned by all end points.
71
71
  #
72
- # @return [String]
72
+ # @return [String, nil]
73
73
  def ip_address
74
74
  get('ip_address')
75
75
  end
@@ -119,6 +119,15 @@ module MaxMind
119
119
  get('is_public_proxy')
120
120
  end
121
121
 
122
+ # This is true if the IP address is on a suspected anonymizing network
123
+ # and belongs to a residential ISP. This property is only available
124
+ # from GeoIP2 Precision Insights.
125
+ #
126
+ # @return [Boolean]
127
+ def residential_proxy?
128
+ get('is_residential_proxy')
129
+ end
130
+
122
131
  # This is true if the IP address is a Tor exit node. This property is only
123
132
  # available from GeoIP2 Precision Insights.
124
133
  #
@@ -140,7 +149,7 @@ module MaxMind
140
149
  # this is the largest network where all of the fields besides ip_address
141
150
  # have the same value.
142
151
  #
143
- # @return [String]
152
+ # @return [String, nil]
144
153
  def network
145
154
  get('network')
146
155
  end
@@ -5,7 +5,7 @@ Gem::Specification.new do |s|
5
5
  s.files = Dir['**/*']
6
6
  s.name = 'maxmind-geoip2'
7
7
  s.summary = 'A gem for interacting with the GeoIP2 webservices and databases.'
8
- s.version = '0.3.0'
8
+ s.version = '1.0.0'
9
9
 
10
10
  s.description = 'A gem for interacting with the GeoIP2 webservices and databases. MaxMind provides geolocation data as downloadable databases as well as through a webservice.'
11
11
  s.email = 'support@maxmind.com'
@@ -13,13 +13,20 @@ Gem::Specification.new do |s|
13
13
  s.licenses = ['Apache-2.0', 'MIT']
14
14
  s.metadata = {
15
15
  'bug_tracker_uri' => 'https://github.com/maxmind/GeoIP2-ruby/issues',
16
- 'changelog_uri' => 'https://github.com/maxmind/GeoIP2-ruby/blob/master/CHANGELOG.md',
17
- 'documentation_uri' => 'https://github.com/maxmind/GeoIP2-ruby',
16
+ 'changelog_uri' => 'https://github.com/maxmind/GeoIP2-ruby/blob/main/CHANGELOG.md',
17
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/maxmind-geoip2',
18
18
  'homepage_uri' => 'https://github.com/maxmind/GeoIP2-ruby',
19
19
  'source_code_uri' => 'https://github.com/maxmind/GeoIP2-ruby',
20
20
  }
21
- s.required_ruby_version = '>= 2.4.0'
21
+ s.required_ruby_version = '>= 2.5.0'
22
22
 
23
- s.add_runtime_dependency 'http', ['~> 4.3']
23
+ s.add_runtime_dependency 'connection_pool', ['~> 2.2']
24
+ s.add_runtime_dependency 'http', '>= 4.3', '< 6.0'
24
25
  s.add_runtime_dependency 'maxmind-db', ['~> 1.1']
26
+
27
+ s.add_development_dependency 'minitest'
28
+ s.add_development_dependency 'rake'
29
+ s.add_development_dependency 'rubocop'
30
+ s.add_development_dependency 'rubocop-performance'
31
+ s.add_development_dependency 'webmock'
25
32
  end
@@ -18,6 +18,7 @@
18
18
  "is_anonymous_vpn" : true,
19
19
  "is_hosting_provider" : true,
20
20
  "is_public_proxy" : true,
21
+ "is_residential_proxy" : true,
21
22
  "is_tor_exit_node" : true
22
23
  }
23
24
  },
@@ -12587,7 +12587,9 @@
12587
12587
  {
12588
12588
  "2c0f:ff80::/25" : {
12589
12589
  "autonomous_system_number" : 237,
12590
- "autonomous_system_organization" : "Merit Network Inc."
12590
+ "autonomous_system_organization" : "Merit Network Inc.",
12591
+ "isp" : "Merit Network Inc.",
12592
+ "organization" : "Merit Network Inc."
12591
12593
  }
12592
12594
  }
12593
12595
  ]
@@ -2057,5 +2057,92 @@
2057
2057
  "user_type" : "government"
2058
2058
  }
2059
2059
  }
2060
+ },
2061
+ {
2062
+ "::2.20.32.110/127" : {
2063
+ "city" : {
2064
+ "confidence" : 60,
2065
+ "geoname_id" : 315808,
2066
+ "names" : {
2067
+ "de" : "Elazığ",
2068
+ "en" : "Elâzığ",
2069
+ "fr" : "Elazığ",
2070
+ "ja" : "エラズー",
2071
+ "ru" : "Элязыг",
2072
+ "zh-CN" : "埃拉泽"
2073
+ }
2074
+ },
2075
+ "continent" : {
2076
+ "code" : "AS",
2077
+ "geoname_id" : 6255147,
2078
+ "names" : {
2079
+ "de" : "Asien",
2080
+ "en" : "Asia",
2081
+ "es" : "Asia",
2082
+ "fr" : "Asie",
2083
+ "ja" : "アジア",
2084
+ "pt-BR" : "Ásia",
2085
+ "ru" : "Азия",
2086
+ "zh-CN" : "亚洲"
2087
+ }
2088
+ },
2089
+ "country" : {
2090
+ "confidence" : 90,
2091
+ "geoname_id" : 298795,
2092
+ "iso_code" : "TR",
2093
+ "names" : {
2094
+ "de" : "Türkei",
2095
+ "en" : "Turkey",
2096
+ "es" : "Turquía",
2097
+ "fr" : "Turquie",
2098
+ "ja" : "トルコ共和国",
2099
+ "pt-BR" : "Turquia",
2100
+ "ru" : "Турция",
2101
+ "zh-CN" : "土耳其"
2102
+ }
2103
+ },
2104
+ "location" : {
2105
+ "accuracy_radius" : 50,
2106
+ "latitude" : 38.6229,
2107
+ "longitude" : 39.3217,
2108
+ "time_zone" : "Europe/Istanbul"
2109
+ },
2110
+ "postal" : {
2111
+ "code" : "23100",
2112
+ "confidence" : 30
2113
+ },
2114
+ "registered_country" : {
2115
+ "geoname_id" : 298795,
2116
+ "iso_code" : "TR",
2117
+ "names" : {
2118
+ "de" : "Türkei",
2119
+ "en" : "Turkey",
2120
+ "es" : "Turquía",
2121
+ "fr" : "Turquie",
2122
+ "ja" : "トルコ共和国",
2123
+ "pt-BR" : "Turquia",
2124
+ "ru" : "Турция",
2125
+ "zh-CN" : "土耳其"
2126
+ }
2127
+ },
2128
+ "subdivisions" : [
2129
+ {
2130
+ "confidence" : 60,
2131
+ "geoname_id" : 315807,
2132
+ "iso_code" : "23",
2133
+ "names" : {
2134
+ "en" : "Elazığ"
2135
+ }
2136
+ }
2137
+ ],
2138
+ "traits" : {
2139
+ "autonomous_system_number" : 8517,
2140
+ "autonomous_system_organization" : "National Academic Network and Information Center",
2141
+ "connection_type" : "Cable/DSL",
2142
+ "isp" : "National Academic Network and Information Center",
2143
+ "organization" : "National Academic Network and Information Center",
2144
+ "user_type" : "business"
2145
+ }
2146
+ }
2060
2147
  }
2061
2148
  ]
@@ -19,7 +19,7 @@ use Test::MaxMind::DB::Common::Util qw( standard_test_metadata );
19
19
  my $Dir = dirname( abs_path($0) );
20
20
 
21
21
  sub main {
22
- my @sizes = ( 24, 28, 32 );
22
+ my @sizes = ( 24, 28, 32 );
23
23
  my @ipv4_range = ( '1.1.1.1', '1.1.1.32' );
24
24
 
25
25
  my @ipv4_subnets = Net::Works::Network->range_as_subnets(@ipv4_range);
@@ -71,6 +71,7 @@ sub main {
71
71
  }
72
72
 
73
73
  write_decoder_test_db();
74
+ write_pointer_decoder_test_db();
74
75
  write_deeply_nested_structures_db();
75
76
 
76
77
  write_geoip2_dbs();
@@ -231,23 +232,7 @@ sub write_test_db {
231
232
  );
232
233
 
233
234
  sub write_decoder_test_db {
234
- my $writer = MaxMind::DB::Writer::Tree->new(
235
- ip_version => 6,
236
- record_size => 24,
237
- database_type => 'MaxMind DB Decoder Test',
238
- languages => ['en'],
239
- description => {
240
- en =>
241
- 'MaxMind DB Decoder Test database - contains every MaxMind DB data type',
242
- },
243
- alias_ipv6_to_ipv4 => 1,
244
- remove_reserved_networks => 0,
245
- map_key_type_callback => sub {
246
- my $key = $_[0];
247
- $key =~ s/X$//;
248
- return $key eq 'array' ? [ 'array', 'uint32' ] : $key;
249
- },
250
- );
235
+ my $writer = _decoder_writer();
251
236
 
252
237
  my @subnets
253
238
  = map { Net::Works::Network->new_from_string( string => $_ ) }
@@ -285,6 +270,70 @@ sub write_test_db {
285
270
 
286
271
  return;
287
272
  }
273
+
274
+ sub write_pointer_decoder_test_db {
275
+
276
+ # We want to create a database where most values are pointers
277
+ no warnings 'redefine';
278
+ local *MaxMind::DB::Writer::Serializer::_should_cache_value
279
+ = sub { 1 };
280
+ my $writer = _decoder_writer();
281
+
282
+ # We add these slightly different records so that we end up with
283
+ # pointers for the individual values in the maps, not just pointers
284
+ # to the map
285
+ $writer->insert_network(
286
+ '1.0.0.0/32',
287
+ {
288
+ %all_types,
289
+ booleanX => 0,
290
+ arrayX => [ 1, 2, 3, 4, ],
291
+ mapXX => {
292
+ utf8_stringX => 'hello',
293
+ arrayX => [ 7, 8, 9, 10 ],
294
+ booleanX => 0,
295
+ },
296
+ },
297
+ );
298
+
299
+ $writer->insert_network(
300
+ '1.1.1.0/32',
301
+ {
302
+ %all_types,
303
+
304
+ # This has to be 0 rather than 1 as otherwise the buggy
305
+ # Perl writer will think it is the same as an uint32 value of
306
+ # 1 and make a pointer to a value of a different type.
307
+ boolean => 0,
308
+ },
309
+ );
310
+
311
+ open my $fh, '>', "$Dir/MaxMind-DB-test-pointer-decoder.mmdb";
312
+ $writer->write_tree($fh);
313
+ close $fh;
314
+
315
+ return;
316
+ }
317
+
318
+ sub _decoder_writer {
319
+ return MaxMind::DB::Writer::Tree->new(
320
+ ip_version => 6,
321
+ record_size => 24,
322
+ database_type => 'MaxMind DB Decoder Test',
323
+ languages => ['en'],
324
+ description => {
325
+ en =>
326
+ 'MaxMind DB Decoder Test database - contains every MaxMind DB data type',
327
+ },
328
+ alias_ipv6_to_ipv4 => 1,
329
+ remove_reserved_networks => 0,
330
+ map_key_type_callback => sub {
331
+ my $key = $_[0];
332
+ $key =~ s/X*$//;
333
+ return $key eq 'array' ? [ 'array', 'uint32' ] : $key;
334
+ },
335
+ );
336
+ }
288
337
  }
289
338
 
290
339
  {
@@ -427,6 +476,7 @@ sub _universal_map_key_type_callback {
427
476
  is_in_european_union => 'boolean',
428
477
  is_legitimate_proxy => 'boolean',
429
478
  is_public_proxy => 'boolean',
479
+ is_residential_proxy => 'boolean',
430
480
  is_satellite_provider => 'boolean',
431
481
  is_tor_exit_node => 'boolean',
432
482
  iso_code => 'utf8_string',
data/test/test_client.rb CHANGED
@@ -42,6 +42,7 @@ class ClientTest < Minitest::Test
42
42
  },
43
43
  'traits' => {
44
44
  'ip_address' => '1.2.3.40',
45
+ 'is_residential_proxy' => true,
45
46
  'network' => '1.2.3.0/24',
46
47
  'static_ip_score' => 1.3,
47
48
  'user_count' => 2,
@@ -82,6 +83,7 @@ class ClientTest < Minitest::Test
82
83
 
83
84
  assert_equal(42, record.continent.geoname_id)
84
85
 
86
+ assert_equal(true, record.traits.residential_proxy?)
85
87
  assert_equal('1.2.3.0/24', record.traits.network)
86
88
  assert_equal(1.3, record.traits.static_ip_score)
87
89
  assert_equal(2, record.traits.user_count)
@@ -337,12 +339,12 @@ class ClientTest < Minitest::Test
337
339
  status: 400,
338
340
  },
339
341
  '1.2.3.71' => {
340
- body: JSON.generate({ 'code': 'HI' }),
342
+ body: JSON.generate({ code: 'HI' }),
341
343
  headers: { 'Content-Type': CONTENT_TYPES[:country] },
342
344
  status: 400,
343
345
  },
344
346
  '1.2.3.8' => {
345
- body: JSON.generate({ 'weird': 42 }),
347
+ body: JSON.generate({ weird: 42 }),
346
348
  headers: { 'Content-Type': CONTENT_TYPES[:country] },
347
349
  status: 400,
348
350
  },
@@ -77,4 +77,20 @@ class CountryModelTest < Minitest::Test
77
77
  model = MaxMind::GeoIP2::Model::Country.new(RAW, ['en'])
78
78
  assert_raises(NoMethodError) { model.traits.unknown }
79
79
  end
80
+
81
+ # This can happen if we're being created from a not fully populated response
82
+ # when used by minFraud. It shouldn't ever happen from GeoIP2 though.
83
+ def test_no_traits
84
+ model = MaxMind::GeoIP2::Model::Country.new(
85
+ {
86
+ 'continent' => {
87
+ 'code' => 'NA',
88
+ 'geoname_id' => 42,
89
+ 'names' => { 'en' => 'North America' },
90
+ },
91
+ },
92
+ ['en'],
93
+ )
94
+ assert_equal(42, model.continent.geoname_id)
95
+ end
80
96
  end
data/test/test_reader.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'ipaddr'
4
+ require 'maxmind/db'
4
5
  require 'maxmind/geoip2'
5
6
  require 'minitest/autorun'
6
7
 
@@ -16,6 +17,7 @@ class ReaderTest < Minitest::Test
16
17
  assert_equal(true, record.anonymous_vpn?)
17
18
  assert_equal(false, record.hosting_provider?)
18
19
  assert_equal(false, record.public_proxy?)
20
+ assert_equal(false, record.residential_proxy?)
19
21
  assert_equal(false, record.tor_exit_node?)
20
22
  assert_equal(ip, record.ip_address)
21
23
  assert_equal('1.2.0.0/16', record.network)
@@ -23,6 +25,18 @@ class ReaderTest < Minitest::Test
23
25
  reader.close
24
26
  end
25
27
 
28
+ def test_anonymous_ip_residential_proxy
29
+ reader = MaxMind::GeoIP2::Reader.new(
30
+ 'test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb',
31
+ )
32
+ ip = '81.2.69.1'
33
+ record = reader.anonymous_ip(ip)
34
+
35
+ assert_equal(true, record.residential_proxy?)
36
+
37
+ reader.close
38
+ end
39
+
26
40
  def test_asn
27
41
  reader = MaxMind::GeoIP2::Reader.new(
28
42
  'test/data/test-data/GeoLite2-ASN-Test.mmdb',
@@ -456,4 +470,49 @@ class ReaderTest < Minitest::Test
456
470
  assert_equal('GeoIP2-City', reader.metadata.database_type)
457
471
  reader.close
458
472
  end
473
+
474
+ def test_constructor_with_minimum_keyword_arguments
475
+ reader = MaxMind::GeoIP2::Reader.new(
476
+ database: 'test/data/test-data/GeoIP2-Country-Test.mmdb',
477
+ )
478
+ record = reader.country('81.2.69.160')
479
+ assert_equal('United Kingdom', record.country.name)
480
+ reader.close
481
+ end
482
+
483
+ def test_constructor_with_all_keyword_arguments
484
+ reader = MaxMind::GeoIP2::Reader.new(
485
+ database: 'test/data/test-data/GeoIP2-Country-Test.mmdb',
486
+ locales: %w[ru],
487
+ mode: MaxMind::DB::MODE_MEMORY,
488
+ )
489
+ record = reader.country('81.2.69.160')
490
+ assert_equal('Великобритания', record.country.name)
491
+ reader.close
492
+ end
493
+
494
+ def test_constructor_missing_database
495
+ error = assert_raises(ArgumentError) do
496
+ MaxMind::GeoIP2::Reader.new
497
+ end
498
+ assert_equal('Invalid database parameter', error.message)
499
+
500
+ error = assert_raises(ArgumentError) do
501
+ MaxMind::GeoIP2::Reader.new(
502
+ locales: %w[ru],
503
+ )
504
+ end
505
+ assert_equal('Invalid database parameter', error.message)
506
+ end
507
+
508
+ def test_old_constructor_parameters
509
+ reader = MaxMind::GeoIP2::Reader.new(
510
+ 'test/data/test-data/GeoIP2-Country-Test.mmdb',
511
+ %w[ru],
512
+ mode: MaxMind::DB::MODE_MEMORY,
513
+ )
514
+ record = reader.country('81.2.69.160')
515
+ assert_equal('Великобритания', record.country.name)
516
+ reader.close
517
+ end
459
518
  end