Skip to content

Commit 9610eca

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 9610eca

File tree

10 files changed

+258
-5
lines changed

10 files changed

+258
-5
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: 9 additions & 1 deletion
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;
@@ -39,6 +40,9 @@
3940
* to most specific (smallest). If the response did not contain any
4041
* subdivisions, this is an empty list.
4142
* @param traits Record for the traits of the requested IP address.
43+
* @param anonymizer Anonymizer record for the requested IP address. This contains information
44+
* about whether the IP address belongs to an anonymizing network such as a VPN,
45+
* proxy, or Tor exit node.
4246
* @see <a href="https://dev.maxmind.com/geoip/docs/web-services?lang=en">GeoIP2 Web
4347
* Services</a>
4448
*/
@@ -71,7 +75,10 @@ public record InsightsResponse(
7175
List<Subdivision> subdivisions,
7276

7377
@JsonProperty("traits")
74-
Traits traits
78+
Traits traits,
79+
80+
@JsonProperty("anonymizer")
81+
Anonymizer anonymizer
7582
) implements JsonSerializable {
7683

7784
/**
@@ -89,6 +96,7 @@ public record InsightsResponse(
8996
? representedCountry : new RepresentedCountry();
9097
subdivisions = subdivisions != null ? List.copyOf(subdivisions) : List.of();
9198
traits = traits != null ? traits : new Traits();
99+
anonymizer = anonymizer != null ? anonymizer : new Anonymizer();
92100
}
93101

94102
/**
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.maxmind.geoip2.record;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.maxmind.db.MaxMindDbConstructor;
5+
import com.maxmind.db.MaxMindDbParameter;
6+
import com.maxmind.geoip2.JsonSerializable;
7+
import java.time.LocalDate;
8+
9+
/**
10+
* <p>
11+
* Contains data for the anonymizer record associated with an IP address.
12+
* </p>
13+
* <p>
14+
* This record is returned by the GeoIP2 Precision Insights web service.
15+
* </p>
16+
*
17+
* @param confidence A score ranging from 1 to 99 that is our percent confidence that the
18+
* network is currently part of an actively used VPN service. This is only
19+
* available from the GeoIP2 Precision Insights web service.
20+
* @param isAnonymous Whether the IP address belongs to any sort of anonymous network.
21+
* @param isAnonymousVpn Whether the IP address is registered to an anonymous VPN provider. If a
22+
* VPN provider does not register subnets under names associated with them,
23+
* we will likely only flag their IP ranges using isHostingProvider.
24+
* @param isHostingProvider Whether the IP address belongs to a hosting or VPN provider (see
25+
* description of isAnonymousVpn).
26+
* @param isPublicProxy Whether the IP address belongs to a public proxy.
27+
* @param isResidentialProxy Whether the IP address is on a suspected anonymizing network and
28+
* belongs to a residential ISP.
29+
* @param isTorExitNode Whether the IP address is a Tor exit node.
30+
* @param networkLastSeen The last day that the network was sighted in our analysis of anonymized
31+
* networks. This is only available from the GeoIP2 Precision Insights web
32+
* service.
33+
* @param providerName The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated
34+
* with the network. This is only available from the GeoIP2 Precision Insights
35+
* web service.
36+
*/
37+
public record Anonymizer(
38+
@JsonProperty("confidence")
39+
@MaxMindDbParameter(name = "confidence")
40+
Integer confidence,
41+
42+
@JsonProperty("is_anonymous")
43+
@MaxMindDbParameter(name = "is_anonymous", useDefault = true)
44+
boolean isAnonymous,
45+
46+
@JsonProperty("is_anonymous_vpn")
47+
@MaxMindDbParameter(name = "is_anonymous_vpn", useDefault = true)
48+
boolean isAnonymousVpn,
49+
50+
@JsonProperty("is_hosting_provider")
51+
@MaxMindDbParameter(name = "is_hosting_provider", useDefault = true)
52+
boolean isHostingProvider,
53+
54+
@JsonProperty("is_public_proxy")
55+
@MaxMindDbParameter(name = "is_public_proxy", useDefault = true)
56+
boolean isPublicProxy,
57+
58+
@JsonProperty("is_residential_proxy")
59+
@MaxMindDbParameter(name = "is_residential_proxy", useDefault = true)
60+
boolean isResidentialProxy,
61+
62+
@JsonProperty("is_tor_exit_node")
63+
@MaxMindDbParameter(name = "is_tor_exit_node", useDefault = true)
64+
boolean isTorExitNode,
65+
66+
@JsonProperty("network_last_seen")
67+
@MaxMindDbParameter(name = "network_last_seen")
68+
LocalDate networkLastSeen,
69+
70+
@JsonProperty("provider_name")
71+
@MaxMindDbParameter(name = "provider_name")
72+
String providerName
73+
) implements JsonSerializable {
74+
75+
/**
76+
* Constructs an {@code Anonymizer} record with {@code null} values for all the nullable
77+
* fields and {@code false} for all boolean fields.
78+
*/
79+
public Anonymizer() {
80+
this(null, false, false, false, false, false, false, (LocalDate) null, null);
81+
}
82+
83+
/**
84+
* Constructs an instance of {@code Anonymizer} with date parsing from MaxMind database.
85+
*
86+
* @param confidence confidence that the network is a VPN
87+
* @param isAnonymous whether the IP address belongs to any sort of anonymous network
88+
* @param isAnonymousVpn whether the IP address belongs to an anonymous VPN system
89+
* @param isHostingProvider whether the IP address belongs to a hosting provider
90+
* @param isPublicProxy whether the IP address belongs to a public proxy system
91+
* @param isResidentialProxy whether the IP address belongs to a residential proxy system
92+
* @param isTorExitNode whether the IP address is a Tor exit node
93+
* @param networkLastSeen the last sighting of the network
94+
* @param providerName the name of the VPN provider
95+
*/
96+
@MaxMindDbConstructor
97+
public Anonymizer(
98+
@MaxMindDbParameter(name = "confidence") Integer confidence,
99+
@MaxMindDbParameter(name = "is_anonymous", useDefault = true) boolean isAnonymous,
100+
@MaxMindDbParameter(name = "is_anonymous_vpn", useDefault = true) boolean isAnonymousVpn,
101+
@MaxMindDbParameter(name = "is_hosting_provider", useDefault = true)
102+
boolean isHostingProvider,
103+
@MaxMindDbParameter(name = "is_public_proxy", useDefault = true) boolean isPublicProxy,
104+
@MaxMindDbParameter(name = "is_residential_proxy", useDefault = true)
105+
boolean isResidentialProxy,
106+
@MaxMindDbParameter(name = "is_tor_exit_node", useDefault = true)
107+
boolean isTorExitNode,
108+
@MaxMindDbParameter(name = "network_last_seen") String networkLastSeen,
109+
@MaxMindDbParameter(name = "provider_name") String providerName
110+
) {
111+
this(
112+
confidence,
113+
isAnonymous,
114+
isAnonymousVpn,
115+
isHostingProvider,
116+
isPublicProxy,
117+
isResidentialProxy,
118+
isTorExitNode,
119+
networkLastSeen != null ? LocalDate.parse(networkLastSeen) : null,
120+
providerName
121+
);
122+
}
123+
}

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,26 @@
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.
4860
* @param isp The name of the ISP associated with the IP address. This is only available from
4961
* the City Plus and Insights web services and the Enterprise database.
5062
* @param mobileCountryCode The <a href="https://en.wikipedia.org/wiki/Mobile_country_code">
@@ -72,6 +84,9 @@
7284
* @param staticIpScore The static IP score of the IP address. This is an indicator of how static
7385
* or dynamic an IP address is. This is only available from the Insights web
7486
* service.
87+
* @param ipRiskSnapshot The risk score associated with the IP address. This is a static score
88+
* ranging from 0.01 to 99 indicating the risk associated with the IP address.
89+
* This is only available from the Insights web service.
7590
*/
7691
public record Traits(
7792
@JsonProperty("autonomous_system_number")
@@ -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,14 +132,17 @@ 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,
@@ -158,7 +179,11 @@ public record Traits(
158179

159180
@JsonProperty("static_ip_score")
160181
@MaxMindDbParameter(name = "static_ip_score")
161-
Double staticIpScore
182+
Double staticIpScore,
183+
184+
@JsonProperty("ip_risk_snapshot")
185+
@MaxMindDbParameter(name = "ip_risk_snapshot")
186+
Double ipRiskSnapshot
162187
) implements JsonSerializable {
163188

164189
/**
@@ -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

0 commit comments

Comments
 (0)