maxmind-geoip2 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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