Skip to content

Commit d67e926

Browse files
oschwaldclaude
andcommitted
Add support for new anonymizer and IP risk outputs
Adds Anonymizer record to InsightsResponse with VPN confidence, network last seen, and provider name fields. Adds ipRiskSnapshot field to Traits for static IP risk scores. Deprecates anonymous IP flags in Traits in favor of the new Anonymizer record for Insights responses. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent acf4792 commit d67e926

File tree

10 files changed

+204
-3
lines changed

10 files changed

+204
-3
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,20 @@ CHANGELOG
1818
field.
1919
* The deprecation notices for IP Risk database support have been removed.
2020
IP Risk database support will continue to be maintained.
21+
* A new `Anonymizer` record has been added to the `InsightsResponse` model. This
22+
record consolidates anonymizer information including VPN confidence scores,
23+
network last seen dates, and provider names. It includes the following fields:
24+
`confidence`, `isAnonymous`, `isAnonymousVpn`, `isHostingProvider`,
25+
`isPublicProxy`, `isResidentialProxy`, `isTorExitNode`, `networkLastSeen`, and
26+
`providerName`.
27+
* A new `ipRiskSnapshot` field has been added to the `Traits` record. This field
28+
provides a static risk score (ranging from 0.01 to 99) associated with the IP
29+
address. This is available from the GeoIP2 Precision Insights web service.
30+
* The anonymous IP flags in the `Traits` record (`isAnonymous`, `isAnonymousVpn`,
31+
`isHostingProvider`, `isPublicProxy`, `isResidentialProxy`, and `isTorExitNode`)
32+
have been deprecated in favor of using the new `Anonymizer` record in the
33+
`InsightsResponse`. These fields will continue to work but will be removed in
34+
version 6.0.0.
2135
* **BREAKING:** The deprecated `WebServiceClient.Builder` methods
2236
`connectTimeout(int)`, `readTimeout(int)`, and `proxy(Proxy)` have been
2337
removed. Use `connectTimeout(Duration)`, `requestTimeout(Duration)`, and

src/main/java/com/maxmind/geoip2/JsonSerializable.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.annotation.JsonInclude;
44
import com.fasterxml.jackson.databind.MapperFeature;
5+
import com.fasterxml.jackson.databind.SerializationFeature;
56
import com.fasterxml.jackson.databind.json.JsonMapper;
67
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
78
import java.io.IOException;
@@ -20,6 +21,7 @@ public interface JsonSerializable {
2021
default String toJson() throws IOException {
2122
JsonMapper mapper = JsonMapper.builder()
2223
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
24+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
2325
.addModule(new JavaTimeModule())
2426
.addModule(new InetAddressModule())
2527
.serializationInclusion(JsonInclude.Include.NON_NULL)

src/main/java/com/maxmind/geoip2/WebServiceClient.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import com.fasterxml.jackson.databind.InjectableValues;
66
import com.fasterxml.jackson.databind.MapperFeature;
77
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.databind.SerializationFeature;
89
import com.fasterxml.jackson.databind.json.JsonMapper;
10+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
911
import com.maxmind.geoip2.exception.AddressNotFoundException;
1012
import com.maxmind.geoip2.exception.AuthenticationException;
1113
import com.maxmind.geoip2.exception.GeoIp2Exception;
@@ -133,6 +135,8 @@ private WebServiceClient(Builder builder) {
133135
mapper = JsonMapper.builder()
134136
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
135137
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
138+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
139+
.addModule(new JavaTimeModule())
136140
.build();
137141

138142
requestTimeout = builder.requestTimeout;

src/main/java/com/maxmind/geoip2/model/InsightsResponse.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.fasterxml.jackson.annotation.JsonIgnore;
44
import com.fasterxml.jackson.annotation.JsonProperty;
55
import com.maxmind.geoip2.JsonSerializable;
6+
import com.maxmind.geoip2.record.Anonymizer;
67
import com.maxmind.geoip2.record.City;
78
import com.maxmind.geoip2.record.Continent;
89
import com.maxmind.geoip2.record.Country;
@@ -19,6 +20,9 @@
1920
* This class provides a model for the data returned by the Insights web
2021
* service.
2122
*
23+
* @param anonymizer Anonymizer record for the requested IP address. This contains information
24+
* about whether the IP address belongs to an anonymizing network such as a VPN,
25+
* proxy, or Tor exit node.
2226
* @param city City record for the requested IP address.
2327
* @param continent Continent record for the requested IP address.
2428
* @param country Country record for the requested IP address. This object represents the country
@@ -43,6 +47,9 @@
4347
* Services</a>
4448
*/
4549
public record InsightsResponse(
50+
@JsonProperty("anonymizer")
51+
Anonymizer anonymizer,
52+
4653
@JsonProperty("city")
4754
City city,
4855

@@ -78,6 +85,7 @@ public record InsightsResponse(
7885
* Compact canonical constructor that sets defaults for null values.
7986
*/
8087
public InsightsResponse {
88+
anonymizer = anonymizer != null ? anonymizer : new Anonymizer();
8189
city = city != null ? city : new City();
8290
continent = continent != null ? continent : new Continent();
8391
country = country != null ? country : new Country();
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.maxmind.geoip2.record;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.maxmind.geoip2.JsonSerializable;
5+
import java.time.LocalDate;
6+
7+
/**
8+
* <p>
9+
* Contains data for the anonymizer record associated with an IP address.
10+
* </p>
11+
* <p>
12+
* This record is returned by the GeoIP2 Precision Insights web service.
13+
* </p>
14+
*
15+
* @param confidence A score ranging from 1 to 99 that is our percent confidence that the
16+
* network is currently part of an actively used VPN service. This is only
17+
* available from the GeoIP2 Precision Insights web service.
18+
* @param isAnonymous Whether the IP address belongs to any sort of anonymous network.
19+
* @param isAnonymousVpn Whether the IP address is registered to an anonymous VPN provider. If a
20+
* VPN provider does not register subnets under names associated with them,
21+
* we will likely only flag their IP ranges using isHostingProvider.
22+
* @param isHostingProvider Whether the IP address belongs to a hosting or VPN provider (see
23+
* description of isAnonymousVpn).
24+
* @param isPublicProxy Whether the IP address belongs to a public proxy.
25+
* @param isResidentialProxy Whether the IP address is on a suspected anonymizing network and
26+
* belongs to a residential ISP.
27+
* @param isTorExitNode Whether the IP address is a Tor exit node.
28+
* @param networkLastSeen The last day that the network was sighted in our analysis of anonymized
29+
* networks. This is only available from the GeoIP2 Precision Insights web
30+
* service.
31+
* @param providerName The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated
32+
* with the network. This is only available from the GeoIP2 Precision Insights
33+
* web service.
34+
*/
35+
public record Anonymizer(
36+
@JsonProperty("confidence")
37+
Integer confidence,
38+
39+
@JsonProperty("is_anonymous")
40+
boolean isAnonymous,
41+
42+
@JsonProperty("is_anonymous_vpn")
43+
boolean isAnonymousVpn,
44+
45+
@JsonProperty("is_hosting_provider")
46+
boolean isHostingProvider,
47+
48+
@JsonProperty("is_public_proxy")
49+
boolean isPublicProxy,
50+
51+
@JsonProperty("is_residential_proxy")
52+
boolean isResidentialProxy,
53+
54+
@JsonProperty("is_tor_exit_node")
55+
boolean isTorExitNode,
56+
57+
@JsonProperty("network_last_seen")
58+
LocalDate networkLastSeen,
59+
60+
@JsonProperty("provider_name")
61+
String providerName
62+
) implements JsonSerializable {
63+
64+
/**
65+
* Constructs an {@code Anonymizer} record with {@code null} values for all the nullable
66+
* fields and {@code false} for all boolean fields.
67+
*/
68+
public Anonymizer() {
69+
this(null, false, false, false, false, false, false, null, null);
70+
}
71+
}

src/main/java/com/maxmind/geoip2/record/Traits.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,29 @@
3737
* address for the system the code is running on. If the system is behind a
3838
* NAT, this may differ from the IP address locally assigned to it.
3939
* @param isAnonymous This is true if the IP address belongs to any sort of anonymous network.
40+
* This field is deprecated. Please use the anonymizer object from the
41+
* Insights response.
4042
* @param isAnonymousVpn This is true if the IP address belongs to an anonymous VPN system.
43+
* This field is deprecated. Please use the anonymizer object from the
44+
* Insights response.
4145
* @param isAnycast This is true if the IP address is an anycast address.
4246
* @param isHostingProvider This is true if the IP address belongs to a hosting provider.
47+
* This field is deprecated. Please use the anonymizer object from the
48+
* Insights response.
4349
* @param isLegitimateProxy This is true if the IP address belongs to a legitimate proxy.
4450
* @param isPublicProxy This is true if the IP address belongs to a public proxy.
51+
* This field is deprecated. Please use the anonymizer object from the
52+
* Insights response.
4553
* @param isResidentialProxy This is true if the IP address is on a suspected anonymizing network
4654
* and belongs to a residential ISP.
55+
* This field is deprecated. Please use the anonymizer object from the
56+
* Insights response.
4757
* @param isTorExitNode This is true if the IP address is a Tor exit node.
58+
* This field is deprecated. Please use the anonymizer object from the
59+
* Insights response.
60+
* @param ipRiskSnapshot The risk score associated with the IP address. This is a static score
61+
* ranging from 0.01 to 99 indicating the risk associated with the IP address.
62+
* This is only available from the Insights web service.
4863
* @param isp The name of the ISP associated with the IP address. This is only available from
4964
* the City Plus and Insights web services and the Enterprise database.
5065
* @param mobileCountryCode The <a href="https://en.wikipedia.org/wiki/Mobile_country_code">
@@ -94,10 +109,12 @@ public record Traits(
94109
@MaxMindDbIpAddress
95110
InetAddress ipAddress,
96111

112+
@Deprecated(since = "5.0.0", forRemoval = true)
97113
@JsonProperty("is_anonymous")
98114
@MaxMindDbParameter(name = "is_anonymous", useDefault = true)
99115
boolean isAnonymous,
100116

117+
@Deprecated(since = "5.0.0", forRemoval = true)
101118
@JsonProperty("is_anonymous_vpn")
102119
@MaxMindDbParameter(name = "is_anonymous_vpn", useDefault = true)
103120
boolean isAnonymousVpn,
@@ -106,6 +123,7 @@ public record Traits(
106123
@MaxMindDbParameter(name = "is_anycast", useDefault = true)
107124
boolean isAnycast,
108125

126+
@Deprecated(since = "5.0.0", forRemoval = true)
109127
@JsonProperty("is_hosting_provider")
110128
@MaxMindDbParameter(name = "is_hosting_provider", useDefault = true)
111129
boolean isHostingProvider,
@@ -114,18 +132,25 @@ public record Traits(
114132
@MaxMindDbParameter(name = "is_legitimate_proxy", useDefault = true)
115133
boolean isLegitimateProxy,
116134

135+
@Deprecated(since = "5.0.0", forRemoval = true)
117136
@JsonProperty("is_public_proxy")
118137
@MaxMindDbParameter(name = "is_public_proxy", useDefault = true)
119138
boolean isPublicProxy,
120139

140+
@Deprecated(since = "5.0.0", forRemoval = true)
121141
@JsonProperty("is_residential_proxy")
122142
@MaxMindDbParameter(name = "is_residential_proxy", useDefault = true)
123143
boolean isResidentialProxy,
124144

145+
@Deprecated(since = "5.0.0", forRemoval = true)
125146
@JsonProperty("is_tor_exit_node")
126147
@MaxMindDbParameter(name = "is_tor_exit_node", useDefault = true)
127148
boolean isTorExitNode,
128149

150+
@JsonProperty("ip_risk_snapshot")
151+
@MaxMindDbParameter(name = "ip_risk_snapshot")
152+
Double ipRiskSnapshot,
153+
129154
@JsonProperty("isp")
130155
@MaxMindDbParameter(name = "isp")
131156
String isp,
@@ -168,7 +193,7 @@ public Traits() {
168193
this(null, null, (ConnectionType) null, null,
169194
null, false, false, false, false,
170195
false, false, false, false, null,
171-
null, null, null, null, null, null, null);
196+
null, null, null, null, null, null, null, null);
172197
}
173198

174199
/**

src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.maxmind.geoip2.WebServiceClient;
1616
import com.maxmind.geoip2.exception.GeoIp2Exception;
1717
import com.maxmind.geoip2.model.ConnectionTypeResponse.ConnectionType;
18+
import com.maxmind.geoip2.record.Anonymizer;
1819
import com.maxmind.geoip2.record.Location;
1920
import com.maxmind.geoip2.record.MaxMind;
2021
import com.maxmind.geoip2.record.Postal;
@@ -23,6 +24,7 @@
2324
import java.io.IOException;
2425
import java.net.InetAddress;
2526
import java.net.URISyntaxException;
27+
import java.time.LocalDate;
2628
import java.util.List;
2729
import org.junit.jupiter.api.BeforeEach;
2830
import org.junit.jupiter.api.Test;
@@ -168,6 +170,42 @@ public void testTraits() {
168170
traits.userCount(),
169171
"traits.userCount() does not return 2"
170172
);
173+
assertEquals(
174+
Double.valueOf(0.01),
175+
traits.ipRiskSnapshot(),
176+
"traits.ipRiskSnapshot() does not return 0.01"
177+
);
178+
}
179+
180+
@Test
181+
public void testAnonymizer() {
182+
Anonymizer anonymizer = this.insights.anonymizer();
183+
184+
assertNotNull(anonymizer, "insights.anonymizer() returns null");
185+
assertEquals(
186+
Integer.valueOf(99),
187+
anonymizer.confidence(),
188+
"anonymizer.confidence() does not return 99"
189+
);
190+
assertTrue(anonymizer.isAnonymous(), "anonymizer.isAnonymous() returns true");
191+
assertTrue(anonymizer.isAnonymousVpn(), "anonymizer.isAnonymousVpn() returns true");
192+
assertTrue(anonymizer.isHostingProvider(), "anonymizer.isHostingProvider() returns true");
193+
assertTrue(anonymizer.isPublicProxy(), "anonymizer.isPublicProxy() returns true");
194+
assertTrue(
195+
anonymizer.isResidentialProxy(),
196+
"anonymizer.isResidentialProxy() returns true"
197+
);
198+
assertTrue(anonymizer.isTorExitNode(), "anonymizer.isTorExitNode() returns true");
199+
assertEquals(
200+
LocalDate.parse("2024-12-31"),
201+
anonymizer.networkLastSeen(),
202+
"anonymizer.networkLastSeen() does not return 2024-12-31"
203+
);
204+
assertEquals(
205+
"NordVPN",
206+
anonymizer.providerName(),
207+
"anonymizer.providerName() does not return NordVPN"
208+
);
171209
}
172210

173211
@Test

src/test/java/com/maxmind/geoip2/model/JsonTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.fasterxml.jackson.databind.InjectableValues;
66
import com.fasterxml.jackson.databind.JsonNode;
77
import com.fasterxml.jackson.databind.MapperFeature;
8+
import com.fasterxml.jackson.databind.SerializationFeature;
89
import com.fasterxml.jackson.databind.json.JsonMapper;
910
import com.fasterxml.jackson.jr.ob.JSON;
1011
import java.io.IOException;
@@ -46,10 +47,22 @@ public void testInsightsSerialization() throws IOException {
4647
.put("network", "1.2.3.0/24")
4748
.put("organization", "Blorg")
4849
.put("user_type", "college")
50+
.put("ip_risk_snapshot", 0.01)
4951
// This is here just to simplify the testing. We expect the
5052
// difference
5153
.put("is_legitimate_proxy", false)
5254
.end()
55+
.startObjectField("anonymizer")
56+
.put("confidence", 99)
57+
.put("is_anonymous", true)
58+
.put("is_anonymous_vpn", true)
59+
.put("is_hosting_provider", true)
60+
.put("is_public_proxy", true)
61+
.put("is_residential_proxy", true)
62+
.put("is_tor_exit_node", true)
63+
.put("network_last_seen", "2024-12-31")
64+
.put("provider_name", "NordVPN")
65+
.end()
5366
.startObjectField("country")
5467
.startObjectField("names")
5568
.put("en", "United States of America")
@@ -334,6 +347,8 @@ public void testIspSerialization() throws Exception {
334347
throws IOException {
335348
JsonMapper mapper = JsonMapper.builder()
336349
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
350+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
351+
.addModule(new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule())
337352
.addModule(new com.maxmind.geoip2.InetAddressModule())
338353
.build();
339354
InjectableValues inject = new InjectableValues.Std().addValue(

src/test/resources/test-data/insights0.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@
8282
"organization": "Blorg",
8383
"static_ip_score": 1.3,
8484
"user_count": 2,
85-
"user_type": "college"
85+
"user_type": "college",
86+
"ip_risk_snapshot": 0.01
87+
},
88+
"anonymizer": {
89+
"confidence": 99,
90+
"is_anonymous": true,
91+
"is_anonymous_vpn": true,
92+
"is_hosting_provider": true,
93+
"is_public_proxy": true,
94+
"is_residential_proxy": true,
95+
"is_tor_exit_node": true,
96+
"network_last_seen": "2024-12-31",
97+
"provider_name": "NordVPN"
8698
}
8799
}

src/test/resources/test-data/insights1.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@
9898
"organization": "Blorg",
9999
"static_ip_score": 1.3,
100100
"user_count": 2,
101-
"user_type": "college"
101+
"user_type": "college",
102+
"ip_risk_snapshot": 0.01
103+
},
104+
"anonymizer": {
105+
"confidence": 99,
106+
"is_anonymous": true,
107+
"is_anonymous_vpn": true,
108+
"is_hosting_provider": true,
109+
"is_public_proxy": true,
110+
"is_residential_proxy": true,
111+
"is_tor_exit_node": true,
112+
"network_last_seen": "2024-12-31",
113+
"provider_name": "NordVPN"
102114
}
103115
}

0 commit comments

Comments
 (0)