Skip to content

Commit

Permalink
Issue ipinfo#50: HTTP errors are ignored and result in empty response…
Browse files Browse the repository at this point in the history
…s with cache poisoning.

WARNING This change is not a drop in replacement as if we get 403 because the token is incorrect, a checked exception is raised.
Also note that, if an empty or null token is passed, it is now a fail fast (instead of getting a null pointer exception at the first request)
  • Loading branch information
Thierry De Leeuw committed Sep 11, 2024
1 parent 32b6aac commit 1472eb0
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 12 deletions.
10 changes: 7 additions & 3 deletions src/main/java/io/ipinfo/api/IPinfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.ipinfo.api.cache.Cache;
import io.ipinfo.api.cache.SimpleCache;
import io.ipinfo.api.context.Context;
import io.ipinfo.api.errors.InvalidTokenException;
import io.ipinfo.api.errors.RateLimitedException;
import io.ipinfo.api.model.ASNResponse;
import io.ipinfo.api.model.IPResponse;
Expand Down Expand Up @@ -60,7 +61,7 @@ public static void main(String... args) {
* @return IPResponse response from the api.
* @throws RateLimitedException an exception when your api key has been rate limited.
*/
public IPResponse lookupIP(String ip) throws RateLimitedException {
public IPResponse lookupIP(String ip) throws RateLimitedException, InvalidTokenException {
IPResponse response = (IPResponse)cache.get(cacheKey(ip));
if (response != null) {
return response;
Expand All @@ -80,7 +81,7 @@ public IPResponse lookupIP(String ip) throws RateLimitedException {
* @return ASNResponse response from the api.
* @throws RateLimitedException an exception when your api key has been rate limited.
*/
public ASNResponse lookupASN(String asn) throws RateLimitedException {
public ASNResponse lookupASN(String asn) throws RateLimitedException, InvalidTokenException {
ASNResponse response = (ASNResponse)cache.get(cacheKey(asn));
if (response != null) {
return response;
Expand All @@ -100,7 +101,7 @@ public ASNResponse lookupASN(String asn) throws RateLimitedException {
* @return String the URL to the map.
* @throws RateLimitedException an exception when your API key has been rate limited.
*/
public String getMap(List<String> ips) throws RateLimitedException {
public String getMap(List<String> ips) throws RateLimitedException, InvalidTokenException {
MapResponse response = new MapRequest(client, token, ips).handle();
return response.getReportUrl();
}
Expand Down Expand Up @@ -374,6 +375,9 @@ public Builder setCache(Cache cache) {
}

public IPinfo build() {
if (token == null || token.isEmpty()) {
throw new IllegalArgumentException("A token must be provided.");
}
return new IPinfo(client, new Context(), token, cache);
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/io/ipinfo/api/errors/ClientErrorException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.ipinfo.api.errors;

/**
* <p>This exception is raised when a client error is occurring (Http status code like 4xx).</p>
*
* <p>Note that HTTP status 403 (Forbidden) is now reported as a checked exception of type @see InvalidTokenException .</p>
*/
public class ClientErrorException extends HttpErrorException {
public ClientErrorException(int statusCode, String message) {
super(statusCode, message);
}
}
17 changes: 17 additions & 0 deletions src/main/java/io/ipinfo/api/errors/HttpErrorException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.ipinfo.api.errors;

/**
* This covers all non 403 and 429 Http error statuses.
*/
public class HttpErrorException extends RuntimeException {
private final int statusCode;

public HttpErrorException(int statusCode, String message) {
super(message);
this.statusCode = statusCode;
}

public int getStatusCode() {
return statusCode;
}
}
13 changes: 13 additions & 0 deletions src/main/java/io/ipinfo/api/errors/InvalidTokenException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.ipinfo.api.errors;

/**
* <p>This exception is raised when the service returns a 403 (access denied).</p>
*
* <p>That likely indicates that the token is either missing, incorrect or expired.
* It is a checked exception so, if you have multiple tokens (example during transition), you can fall back to another token.</p>
*/
public class InvalidTokenException extends Exception {
public InvalidTokenException() {
super("The server did not accepted the provided token or no token was provided.");
}
}
10 changes: 10 additions & 0 deletions src/main/java/io/ipinfo/api/errors/ServerErrorException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.ipinfo.api.errors;

/**
* This exception is raised when a server error is returned by IpInfo (Http status like 5xx)
*/
public class ServerErrorException extends HttpErrorException {
public ServerErrorException(int statusCode, String message) {
super(statusCode, message);
}
}
3 changes: 2 additions & 1 deletion src/main/java/io/ipinfo/api/request/ASNRequest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.ipinfo.api.request;

import io.ipinfo.api.errors.ErrorResponseException;
import io.ipinfo.api.errors.InvalidTokenException;
import io.ipinfo.api.errors.RateLimitedException;
import io.ipinfo.api.model.ASNResponse;
import okhttp3.OkHttpClient;
Expand All @@ -18,7 +19,7 @@ public ASNRequest(OkHttpClient client, String token, String asn) {
}

@Override
public ASNResponse handle() throws RateLimitedException {
public ASNResponse handle() throws RateLimitedException, InvalidTokenException {
String url = String.format(URL_FORMAT, asn);
Request.Builder request = new Request.Builder().url(url).get();

Expand Down
13 changes: 9 additions & 4 deletions src/main/java/io/ipinfo/api/request/BaseRequest.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package io.ipinfo.api.request;

import com.google.gson.Gson;
import io.ipinfo.api.errors.ErrorResponseException;
import io.ipinfo.api.errors.RateLimitedException;
import io.ipinfo.api.errors.*;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
Expand All @@ -18,9 +17,9 @@ protected BaseRequest(OkHttpClient client, String token) {
this.token = token;
}

public abstract T handle() throws RateLimitedException;
public abstract T handle() throws RateLimitedException, InvalidTokenException;

public Response handleRequest(Request.Builder request) throws RateLimitedException {
public Response handleRequest(Request.Builder request) throws RateLimitedException, InvalidTokenException {
request
.addHeader("Authorization", Credentials.basic(token, ""))
.addHeader("user-agent", "IPinfoClient/Java/3.0.0")
Expand All @@ -41,6 +40,12 @@ public Response handleRequest(Request.Builder request) throws RateLimitedExcepti

if (response.code() == 429) {
throw new RateLimitedException();
} else if ((response.code() == 403)) {
throw new InvalidTokenException();
} else if ((response.code() >= 400) && (response.code() <= 499)) {
throw new ClientErrorException(response.code(), "Http error " + response.code() + ". " + response.message());
} else if ((response.code() >= 500) && (response.code() <= 599)) {
throw new ServerErrorException(response.code(), "Http error " + response.code() + ". " + response.message());
}

return response;
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/ipinfo/api/request/IPRequest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.ipinfo.api.request;

import io.ipinfo.api.errors.ErrorResponseException;
import io.ipinfo.api.errors.InvalidTokenException;
import io.ipinfo.api.errors.RateLimitedException;
import io.ipinfo.api.model.IPResponse;
import okhttp3.OkHttpClient;
Expand All @@ -19,7 +20,7 @@ public IPRequest(OkHttpClient client, String token, String ip) {
}

@Override
public IPResponse handle() throws RateLimitedException {
public IPResponse handle() throws RateLimitedException, InvalidTokenException {
if (isBogon(ip)) {
try {
return new IPResponse(ip, true);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/ipinfo/api/request/MapRequest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.ipinfo.api.request;

import io.ipinfo.api.errors.ErrorResponseException;
import io.ipinfo.api.errors.InvalidTokenException;
import io.ipinfo.api.errors.RateLimitedException;
import io.ipinfo.api.model.MapResponse;
import okhttp3.*;
Expand All @@ -17,7 +18,7 @@ public MapRequest(OkHttpClient client, String token, List<String> ips) {
}

@Override
public MapResponse handle() throws RateLimitedException {
public MapResponse handle() throws RateLimitedException, InvalidTokenException {
String jsonIpList = gson.toJson(ips);
RequestBody requestBody = RequestBody.create(null, jsonIpList);
Request.Builder request = new Request.Builder().url(URL).post(requestBody);
Expand Down
5 changes: 3 additions & 2 deletions src/test/java/io/ipinfo/IPinfoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.ipinfo.api.IPinfo;
import io.ipinfo.api.errors.ErrorResponseException;
import io.ipinfo.api.errors.InvalidTokenException;
import io.ipinfo.api.errors.RateLimitedException;
import io.ipinfo.api.model.ASNResponse;
import io.ipinfo.api.model.IPResponse;
Expand Down Expand Up @@ -56,7 +57,7 @@ public void testGoogleDNS() {
() -> assertEquals(bogonResp.getIp(), "2001:0:c000:200::0:255:1"),
() -> assertTrue(bogonResp.getBogon())
);
} catch (RateLimitedException e) {
} catch (RateLimitedException | InvalidTokenException e) {
fail(e);
}
}
Expand All @@ -69,7 +70,7 @@ public void testGetMap() {

try {
String mapUrl = ii.getMap(Arrays.asList("1.1.1.1", "2.2.2.2", "8.8.8.8"));
} catch (RateLimitedException e) {
} catch (RateLimitedException | InvalidTokenException e) {
fail(e);
}
}
Expand Down

0 comments on commit 1472eb0

Please sign in to comment.