1919import com .codahale .metrics .MetricRegistry ;
2020import com .fasterxml .jackson .annotation .JsonAutoDetect ;
2121import com .fasterxml .jackson .annotation .JsonCreator ;
22+ import com .fasterxml .jackson .annotation .JsonIgnore ;
2223import com .fasterxml .jackson .annotation .JsonInclude ;
2324import com .fasterxml .jackson .annotation .JsonProperty ;
24- import com .fasterxml .jackson .annotation .JsonIgnore ;
2525import com .fasterxml .jackson .annotation .JsonTypeName ;
26+ import com .fasterxml .jackson .databind .JsonNode ;
27+ import com .fasterxml .jackson .databind .ObjectMapper ;
2628import com .fasterxml .jackson .databind .annotation .JsonDeserialize ;
2729import com .google .auto .value .AutoValue ;
2830import com .google .common .annotations .VisibleForTesting ;
2931import com .google .common .collect .Maps ;
3032import com .google .inject .assistedinject .Assisted ;
31- import com .unboundid .util .json .JSONException ;
32- import com .unboundid .util .json .JSONObject ;
33+ import jakarta .annotation .Nonnull ;
34+ import jakarta .inject .Inject ;
35+ import jakarta .validation .constraints .NotEmpty ;
3336import okhttp3 .OkHttpClient ;
3437import okhttp3 .Request ;
3538import okhttp3 .Response ;
4750import org .slf4j .Logger ;
4851import org .slf4j .LoggerFactory ;
4952
50- import jakarta .annotation .Nonnull ;
51- import jakarta .inject .Inject ;
52- import jakarta .validation .constraints .NotEmpty ;
53-
5453import java .io .IOException ;
5554import java .util .Map ;
5655import java .util .Objects ;
5958public class GreyNoiseQuickIPDataAdapter extends LookupDataAdapter {
6059 private static final Logger LOG = LoggerFactory .getLogger (GreyNoiseQuickIPDataAdapter .class );
6160 public static final String NAME = "GreyNoise" ;
62- static final String GREYNOISE_IPQC_ENDPOINT = "https://api.greynoise.io/v2/noise/quick/" ;
61+ // v3 unified IP Lookup endpoint; the quick variant is selected with the "quick=true" query parameter.
62+ static final String GREYNOISE_IPQC_ENDPOINT = "https://api.greynoise.io/v3/ip/" ;
6363
6464 private final EncryptedValueService encryptedValueService ;
6565 private final Config config ;
6666 private final OkHttpClient okHttpClient ;
6767 private final CustomizationConfig customizationConfig ;
68+ private final ObjectMapper objectMapper ;
6869
6970 private static final AtomicBoolean VALID_GREYNOISE_LICENSE = new AtomicBoolean (false );
7071
@@ -75,12 +76,14 @@ public GreyNoiseQuickIPDataAdapter(@Assisted("id") String id,
7576 MetricRegistry metricRegistry ,
7677 EncryptedValueService encryptedValueService ,
7778 OkHttpClient okHttpClient ,
78- CustomizationConfig customizationConfig ) {
79+ CustomizationConfig customizationConfig ,
80+ ObjectMapper objectMapper ) {
7981 super (id , name , config , metricRegistry );
8082 this .config = (Config ) config ;
8183 this .encryptedValueService = encryptedValueService ;
8284 this .okHttpClient = okHttpClient ;
8385 this .customizationConfig = customizationConfig ;
86+ this .objectMapper = objectMapper ;
8487 }
8588
8689 @ Override
@@ -135,10 +138,10 @@ protected LookupResult doGet(Object keyObject) {
135138 }
136139 }
137140 Request request = new Request .Builder ()
138- .url (GREYNOISE_IPQC_ENDPOINT + ip )
141+ .url (GREYNOISE_IPQC_ENDPOINT + ip + "?quick=true" )
139142 .method ("GET" , null )
140143 .addHeader ("Accept" , "application/json" )
141- .addHeader ("key" , encryptedValueService .decrypt (config .apiToken ()))
144+ .addHeader ("key" , Objects . requireNonNull ( encryptedValueService .decrypt (config .apiToken () )))
142145 .addHeader ("User-Agent" , customizationConfig .productName ())
143146 .build ();
144147 try (Response response = okHttpClient .newCall (request ).execute ()) {
@@ -150,18 +153,31 @@ protected LookupResult doGet(Object keyObject) {
150153 }
151154
152155 @ VisibleForTesting
153- static LookupResult parseResponse (Response response ) {
154-
156+ LookupResult parseResponse (Response response ) {
155157 if (response .isSuccessful ()) {
156158 Map <Object , Object > map = Maps .newHashMap ();
157159
158160 try {
159- JSONObject obj = new JSONObject (response .body ().string ());
160- map .put ("ip" , Objects .requireNonNull (obj ).getFieldAsString ("ip" ));
161- map .put ("noise" , Objects .requireNonNull (obj ).getFieldAsBoolean ("noise" ));
162- map .put ("code" , Objects .requireNonNull (obj ).getFieldAsString ("code" ));
163- map .put ("riot" , Objects .requireNonNull (obj ).getFieldAsBoolean ("riot" ));
164- } catch (JSONException | IOException e ) {
161+ JsonNode root = objectMapper .readTree (response .body ().string ());
162+ if (root .hasNonNull ("ip" )) {
163+ map .put ("ip" , root .get ("ip" ).asText ());
164+ }
165+
166+ // v3 nests the scanner data; "found" is the v3 equivalent of the v2 top-level "noise" flag.
167+ JsonNode isi = root .path ("internet_scanner_intelligence" );
168+ map .put ("noise" , isi .path ("found" ).asBoolean (false ));
169+ if (isi .hasNonNull ("classification" )) {
170+ map .put ("classification" , isi .get ("classification" ).asText ());
171+ }
172+
173+ // v3 nests the RIOT data under business_service_intelligence; "found" replaces the v2 "riot" flag.
174+ JsonNode bsi = root .path ("business_service_intelligence" );
175+ map .put ("riot" , bsi .path ("found" ).asBoolean (false ));
176+ final String trustLevel = bsi .path ("trust_level" ).asText ("" );
177+ if (!trustLevel .isEmpty ()) {
178+ map .put ("trust_level" , trustLevel );
179+ }
180+ } catch (IOException e ) {
165181 LOG .error ("An error occurred while parsing Lookup result [{}]" , e .toString ());
166182 }
167183 return LookupResult .withoutTTL ().multiValue (map ).build ();
@@ -174,7 +190,9 @@ static LookupResult parseResponse(Response response) {
174190 public void set (Object key , Object value ) {
175191 }
176192
177- // Check if provided API token has a valid non-community GreyNoise subscription.
193+ // Check if the provided API token is accepted by GreyNoise. The free/community tier has been retired, so any
194+ // authenticated request (HTTP 200) corresponds to a valid subscription, while an invalid or missing key returns
195+ // HTTP 401.
178196 private boolean isValidSubscription (String apiKey ) {
179197 Request request = new Request .Builder ()
180198 .url ("https://api.greynoise.io/ping" )
@@ -185,9 +203,7 @@ private boolean isValidSubscription(String apiKey) {
185203 .build ();
186204
187205 try (Response response = okHttpClient .newCall (request ).execute ()) {
188- JSONObject json = new JSONObject (response .body ().string ());
189- return response .isSuccessful ()
190- && json .hasField ("offering" ) && !json .getFieldAsString ("offering" ).equals ("community" );
206+ return response .isSuccessful ();
191207 } catch (Exception e ) {
192208 LOG .warn ("An error occurred while retrieving subscription type." , e );
193209 return false ;
0 commit comments