diff --git a/.rubocop.yml b/.rubocop.yml index 973ecd0..460c27c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,5 @@ -require: rubocop-performance +plugins: + - rubocop-performance AllCops: TargetRubyVersion: '3.0' @@ -40,4 +41,4 @@ Style/HashTransformKeys: Enabled: true Gemspec/DevelopmentDependencies: - Enabled: false + Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index a8647f7..48860e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 1.3.0 +* Support for the GeoIP Anonymous Plus database has been added. To do a + lookup in this database, use the `anonymous_plus` method on + `MaxMind::GeoIP2::Reader`. * Ruby 3.0+ is now required. If you're using Ruby 2.5, 2.6, or 2.7, please use version 1.2.0 of this gem. * Deprecated `metro_code` on `MaxMind::GeoIP2::Record::Location`. The code diff --git a/README.md b/README.md index 6f4edf6..3fff9f2 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,21 @@ record = reader.anonymous_ip('128.101.101.101') puts "Anonymous" if record.is_anonymous ``` +### Anonymous Plus Example + +```ruby +require 'maxmind/geoip2' + +# This creates the Reader object which should be reused across lookups. +reader = MaxMind::GeoIP2::Reader.new( + database: '/usr/share/GeoIP/GeoIP-Anonymous-Plus.mmdb', +) + +record = reader.anonymous_plus('128.101.101.101') + +puts record.anonymizer_confidence # 30 +``` + ### ASN Example ```ruby diff --git a/lib/maxmind/geoip2/model/anonymous_plus.rb b/lib/maxmind/geoip2/model/anonymous_plus.rb new file mode 100644 index 0000000..109c9a5 --- /dev/null +++ b/lib/maxmind/geoip2/model/anonymous_plus.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'date' +require 'maxmind/geoip2/model/anonymous_ip' + +module MaxMind + module GeoIP2 + module Model + # Model class for the Anonymous Plus database. + class AnonymousPlus < AnonymousIP + # A score ranging from 1 to 99 that is our percent confidence that the + # network is currently part of an actively used VPN service. + # + # @return [Integer, nil] + def anonymizer_confidence + get('anonymizer_confidence') + end + + # The last day that the network was sighted in our analysis of + # anonymized networks. This value is parsed lazily. + # + # @return [Date, nil] A Date object representing the last seen date, + # or nil if the date is not available. + def network_last_seen + return @network_last_seen if defined?(@network_last_seen) + + date_string = get('network_last_seen') + + if !date_string + return nil + end + + @network_last_seen = Date.parse(date_string) + end + + # The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) + # associated with the network. + # + # @return [String, nil] + def provider_name + get('provider_name') + end + end + end + end +end diff --git a/lib/maxmind/geoip2/reader.rb b/lib/maxmind/geoip2/reader.rb index 6c546e7..26e4712 100644 --- a/lib/maxmind/geoip2/reader.rb +++ b/lib/maxmind/geoip2/reader.rb @@ -3,6 +3,7 @@ require 'maxmind/db' require 'maxmind/geoip2/errors' require 'maxmind/geoip2/model/anonymous_ip' +require 'maxmind/geoip2/model/anonymous_plus' require 'maxmind/geoip2/model/asn' require 'maxmind/geoip2/model/city' require 'maxmind/geoip2/model/connection_type' @@ -86,7 +87,7 @@ def initialize(*args) # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/PerceivedComplexity - # Look up the IP address in the database. + # Look up the IP address in the Anonymous IP database. # # @param ip_address [String] a string in the standard notation. It may be # IPv4 or IPv6. @@ -110,6 +111,30 @@ def anonymous_ip(ip_address) ) end + # Look up the IP address in the Anonymous Plus database. + # + # @param ip_address [String] a string in the standard notation. It may be + # IPv4 or IPv6. + # + # @return [MaxMind::GeoIP2::Model::AnonymousPlus] + # + # @raise [ArgumentError] if used against a non-Anonymous Plus database + # or if you attempt to look up an IPv6 address in an IPv4 only database. + # + # @raise [AddressNotFoundError] if the IP address is not found in the + # database. + # + # @raise [MaxMind::DB::InvalidDatabaseError] if the database appears + # corrupt. + def anonymous_plus(ip_address) + flat_model_for( + Model::AnonymousPlus, + 'anonymous_plus', + 'GeoIP-Anonymous-Plus', + ip_address, + ) + end + # Look up the IP address in an ASN database. # # @param ip_address [String] a string in the standard notation. It may be diff --git a/test/data b/test/data index 1271107..f387805 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 1271107ccad72c320bc7dc8aefd767cba550101a +Subproject commit f387805e4af30036ec4245c9069ac1f2fdae4953 diff --git a/test/test_reader.rb b/test/test_reader.rb index 7691b41..54593c6 100644 --- a/test/test_reader.rb +++ b/test/test_reader.rb @@ -37,6 +37,29 @@ def test_anonymous_ip_residential_proxy reader.close end + def test_anonymous_plus + reader = MaxMind::GeoIP2::Reader.new( + 'test/data/test-data/GeoIP-Anonymous-Plus-Test.mmdb', + ) + ip = '1.2.0.1' + record = reader.anonymous_plus(ip) + + assert_equal(30, record.anonymizer_confidence) + assert_equal(true, record.anonymous?) + assert_equal(true, record.anonymous_vpn?) + assert_equal(false, record.hosting_provider?) + assert_equal(Date.new(2025, 4, 14), record.network_last_seen) + assert_equal('foo', record.provider_name) + assert_equal(false, record.public_proxy?) + assert_equal(false, record.residential_proxy?) + assert_equal(false, record.tor_exit_node?) + + assert_equal(ip, record.ip_address) + assert_equal('1.2.0.1/32', record.network) + + reader.close + end + def test_asn reader = MaxMind::GeoIP2::Reader.new( 'test/data/test-data/GeoLite2-ASN-Test.mmdb',