diff --git a/CHANGELOG.md b/CHANGELOG.md index 0799b303..ca3b1708 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ CHANGELOG ========= +5.2.0 (unreleased) +------------------ + +* A new `residential` field has been added to the `Anonymizer` record. This + is an `AnonymizerFeed` record containing `confidence`, `networkLastSeen`, + and `providerName` fields sourced from the GeoIP Residential Proxy feed. + Because the residential proxy feed is a superset of the data used for the + other `Anonymizer` fields, `residential` may be populated even when none of + the other fields on `Anonymizer` are set. The `AnonymizerFeed` record is + intended to be reused for other anonymizer network categories that may be + added in the future. + 5.1.0 (2026-05-12) ------------------ diff --git a/src/main/java/com/maxmind/geoip2/record/Anonymizer.java b/src/main/java/com/maxmind/geoip2/record/Anonymizer.java index 6c94527a..e003e986 100644 --- a/src/main/java/com/maxmind/geoip2/record/Anonymizer.java +++ b/src/main/java/com/maxmind/geoip2/record/Anonymizer.java @@ -31,6 +31,11 @@ * @param providerName The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated * with the network. This is only available from the GeoIP Insights * web service. + * @param residential Data about the network's presence in the GeoIP Residential Proxy feed. + * Note that the residential proxy feed is a superset of the data used for + * the other fields on this record, so this field may be present even when + * none of the other fields are set. This is only available from the GeoIP + * Insights web service. */ public record Anonymizer( @JsonProperty("confidence") @@ -58,14 +63,68 @@ public record Anonymizer( LocalDate networkLastSeen, @JsonProperty("provider_name") - String providerName + String providerName, + + @JsonProperty("residential") + AnonymizerFeed residential ) implements JsonSerializable { + /** + * Compact canonical constructor that sets a default for a null {@code residential} value. + */ + public Anonymizer { + residential = residential != null ? residential : new AnonymizerFeed(); + } + /** * Constructs an {@code Anonymizer} record with {@code null} values for all the nullable * fields and {@code false} for all boolean fields. */ public Anonymizer() { - this(null, false, false, false, false, false, false, null, null); + this(null, false, false, false, false, false, false, null, null, null); + } + + /** + * Constructs an {@code Anonymizer} record without the {@code residential} field. + * + * @param confidence the confidence that the network is an actively used VPN service + * @param isAnonymous whether the IP address belongs to any sort of anonymous network + * @param isAnonymousVpn whether the IP address is registered to an anonymous VPN provider + * @param isHostingProvider whether the IP address belongs to a hosting or VPN provider + * @param isPublicProxy whether the IP address belongs to a public proxy + * @param isResidentialProxy whether the IP address is on a suspected anonymizing network + * and belongs to a residential ISP + * @param isTorExitNode whether the IP address is a Tor exit node + * @param networkLastSeen the last day that the network was sighted in our analysis of + * anonymized networks + * @param providerName the name of the VPN provider associated with the network + * @deprecated Use the canonical constructor that also accepts the {@code residential} + * field. This constructor is provided for backward compatibility and will be + * removed in version 6.0.0. + */ + @Deprecated(since = "5.2.0", forRemoval = true) + public Anonymizer( + Integer confidence, + boolean isAnonymous, + boolean isAnonymousVpn, + boolean isHostingProvider, + boolean isPublicProxy, + boolean isResidentialProxy, + boolean isTorExitNode, + LocalDate networkLastSeen, + String providerName + ) { + this( + confidence, + isAnonymous, + isAnonymousVpn, + isHostingProvider, + isPublicProxy, + isResidentialProxy, + isTorExitNode, + networkLastSeen, + providerName, + null + ); } } diff --git a/src/main/java/com/maxmind/geoip2/record/AnonymizerFeed.java b/src/main/java/com/maxmind/geoip2/record/AnonymizerFeed.java new file mode 100644 index 00000000..ca02a478 --- /dev/null +++ b/src/main/java/com/maxmind/geoip2/record/AnonymizerFeed.java @@ -0,0 +1,43 @@ +package com.maxmind.geoip2.record; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.maxmind.geoip2.JsonSerializable; +import java.time.LocalDate; + +/** + *

+ * Contains data about a network's presence in a specific anonymizer data feed, such as the + * residential proxy feed. Additional feeds, such as VPNs, mobile networks, and hosting or + * datacenter providers, may be added to the {@link Anonymizer} record in the future using this + * same record type. + *

+ *

+ * This record is returned by the GeoIP Insights web service. + *

+ * + * @param confidence A score ranging from 1 to 99 that is our percent confidence that the + * network is currently part of an actively used network of this feed's + * category. This is only available from the GeoIP Insights web service. + * @param networkLastSeen The last day that the network was sighted in this feed. This is only + * available from the GeoIP Insights web service. + * @param providerName The name of the provider (e.g., NordVPN, SurfShark, etc.) associated with + * the network. This is only available from the GeoIP Insights web service. + */ +public record AnonymizerFeed( + @JsonProperty("confidence") + Integer confidence, + + @JsonProperty("network_last_seen") + LocalDate networkLastSeen, + + @JsonProperty("provider_name") + String providerName +) implements JsonSerializable { + + /** + * Constructs an {@code AnonymizerFeed} record with {@code null} values for all fields. + */ + public AnonymizerFeed() { + this(null, null, null); + } +} diff --git a/src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java b/src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java index 38905e77..f4081dd4 100644 --- a/src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java +++ b/src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java @@ -16,6 +16,7 @@ import com.maxmind.geoip2.exception.GeoIp2Exception; import com.maxmind.geoip2.model.ConnectionTypeResponse.ConnectionType; import com.maxmind.geoip2.record.Anonymizer; +import com.maxmind.geoip2.record.AnonymizerFeed; import com.maxmind.geoip2.record.Location; import com.maxmind.geoip2.record.MaxMind; import com.maxmind.geoip2.record.Postal; @@ -206,6 +207,25 @@ public void testAnonymizer() { anonymizer.providerName(), "anonymizer.providerName() does not return NordVPN" ); + + AnonymizerFeed residential = anonymizer.residential(); + + assertNotNull(residential, "anonymizer.residential() returns null"); + assertEquals( + Integer.valueOf(82), + residential.confidence(), + "anonymizer.residential().confidence() does not return 82" + ); + assertEquals( + LocalDate.parse("2026-05-11"), + residential.networkLastSeen(), + "anonymizer.residential().networkLastSeen() does not return 2026-05-11" + ); + assertEquals( + "quickshift", + residential.providerName(), + "anonymizer.residential().providerName() does not return quickshift" + ); } @Test diff --git a/src/test/java/com/maxmind/geoip2/model/JsonTest.java b/src/test/java/com/maxmind/geoip2/model/JsonTest.java index 97e69a8b..f75157f2 100644 --- a/src/test/java/com/maxmind/geoip2/model/JsonTest.java +++ b/src/test/java/com/maxmind/geoip2/model/JsonTest.java @@ -62,6 +62,11 @@ public void testInsightsSerialization() throws IOException { .put("is_tor_exit_node", true) .put("network_last_seen", "2024-12-31") .put("provider_name", "NordVPN") + .startObjectField("residential") + .put("confidence", 82) + .put("network_last_seen", "2026-05-11") + .put("provider_name", "quickshift") + .end() .end() .startObjectField("country") .startObjectField("names") diff --git a/src/test/resources/test-data/insights0.json b/src/test/resources/test-data/insights0.json index 45118585..1f56bb33 100644 --- a/src/test/resources/test-data/insights0.json +++ b/src/test/resources/test-data/insights0.json @@ -94,6 +94,11 @@ "is_residential_proxy": true, "is_tor_exit_node": true, "network_last_seen": "2024-12-31", - "provider_name": "NordVPN" + "provider_name": "NordVPN", + "residential": { + "confidence": 82, + "network_last_seen": "2026-05-11", + "provider_name": "quickshift" + } } } \ No newline at end of file diff --git a/src/test/resources/test-data/insights1.json b/src/test/resources/test-data/insights1.json index 6cc70cba..666bcbb1 100644 --- a/src/test/resources/test-data/insights1.json +++ b/src/test/resources/test-data/insights1.json @@ -110,6 +110,11 @@ "is_residential_proxy": true, "is_tor_exit_node": true, "network_last_seen": "2024-12-31", - "provider_name": "NordVPN" + "provider_name": "NordVPN", + "residential": { + "confidence": 82, + "network_last_seen": "2026-05-11", + "provider_name": "quickshift" + } } } \ No newline at end of file