Skip to content

Commit bf32b6c

Browse files
Will-Lin4mohs1
authored andcommitted
Add a custom exception with error codes (#41)
Add a custom exception (InvalidJsonTokenException) that supports error codes. Update the existing thrown exceptions to include this custom exception with the appropriate error code as a cause. Update test cases to test these error codes.
1 parent ec8a472 commit bf32b6c

10 files changed

Lines changed: 279 additions & 119 deletions

src/main/java/net/oauth/jsontoken/AbstractJsonTokenParser.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.util.List;
2626
import net.oauth.jsontoken.crypto.AsciiStringVerifier;
2727
import net.oauth.jsontoken.crypto.Verifier;
28+
import net.oauth.jsontoken.exceptions.ErrorCode;
29+
import net.oauth.jsontoken.exceptions.InvalidJsonTokenException;
2830
import org.apache.commons.codec.binary.Base64;
2931
import org.joda.time.Instant;
3032

@@ -83,9 +85,10 @@ public JsonToken deserialize(String tokenString) {
8385
* or if tokenString is not a properly formatted JWT
8486
*/
8587
public void verify(JsonToken jsonToken, List<Verifier> verifiers) throws SignatureException {
86-
if (! signatureIsValid(jsonToken.getTokenString(), verifiers)) {
87-
throw new SignatureException("Invalid signature for token: " +
88-
jsonToken.getTokenString());
88+
if (!signatureIsValid(jsonToken.getTokenString(), verifiers)) {
89+
throw new SignatureException(
90+
"Invalid signature for token: " + jsonToken.getTokenString(),
91+
new InvalidJsonTokenException(ErrorCode.BAD_SIGNATURE));
8992
}
9093

9194
Instant issuedAt = jsonToken.getIssuedAt();
@@ -100,10 +103,22 @@ public void verify(JsonToken jsonToken, List<Verifier> verifiers) throws Signatu
100103
}
101104

102105
if (issuedAt != null && expiration != null) {
103-
if (issuedAt.isAfter(expiration)
104-
|| ! clock.isCurrentTimeInInterval(issuedAt, expiration)) {
105-
throw new IllegalStateException(String.format("Invalid iat and/or exp. iat: %s exp: %s "
106-
+ "now: %s", jsonToken.getIssuedAt(), jsonToken.getExpiration(), clock.now()));
106+
String errorMessage = String.format("Invalid iat and/or exp. iat: %s exp: %s now: %s",
107+
jsonToken.getIssuedAt(), jsonToken.getExpiration(), clock.now());
108+
109+
if (issuedAt.isAfter(expiration)) {
110+
throw new IllegalStateException(
111+
errorMessage, new InvalidJsonTokenException(ErrorCode.BAD_TIME_RANGE));
112+
}
113+
114+
if (!clock.isCurrentTimeInInterval(issuedAt, expiration)) {
115+
if (clock.now().isAfter(expiration)) {
116+
throw new IllegalStateException(
117+
errorMessage, new InvalidJsonTokenException(ErrorCode.EXPIRED_TOKEN));
118+
} else {
119+
throw new IllegalStateException(
120+
errorMessage, new InvalidJsonTokenException(ErrorCode.BAD_TIME_RANGE));
121+
}
107122
}
108123
}
109124

@@ -173,8 +188,10 @@ public boolean issuedAtIsValid(JsonToken jsonToken, Instant now) {
173188
private List<String> splitTokenString(String tokenString) {
174189
List<String> pieces = Splitter.on(JsonTokenUtil.DELIMITER).splitToList(tokenString);
175190
if (pieces.size() != 3) {
176-
throw new IllegalStateException("Expected JWT to have 3 segments separated by '" +
177-
JsonTokenUtil.DELIMITER + "', but it has " + pieces.size() + " segments");
191+
throw new IllegalStateException(
192+
"Expected JWT to have 3 segments separated by '" +
193+
JsonTokenUtil.DELIMITER + "', but it has " + pieces.size() + " segments",
194+
new InvalidJsonTokenException(ErrorCode.MALFORMED_TOKEN_STRING));
178195
}
179196
return pieces;
180197
}

src/main/java/net/oauth/jsontoken/AsyncJsonTokenParser.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717
package net.oauth.jsontoken;
1818

19-
import com.google.common.base.Function;
2019
import com.google.common.base.Preconditions;
2120
import com.google.common.util.concurrent.AsyncFunction;
2221
import com.google.common.util.concurrent.Futures;
@@ -29,6 +28,8 @@
2928
import net.oauth.jsontoken.crypto.Verifier;
3029
import net.oauth.jsontoken.discovery.AsyncVerifierProvider;
3130
import net.oauth.jsontoken.discovery.AsyncVerifierProviders;
31+
import net.oauth.jsontoken.exceptions.ErrorCode;
32+
import net.oauth.jsontoken.exceptions.InvalidJsonTokenException;
3233

3334
/**
3435
* The asynchronous counterpart of {@link JsonTokenParser}.
@@ -117,24 +118,29 @@ private ListenableFuture<List<Verifier>> provideVerifiers(JsonToken jsonToken) {
117118
AsyncVerifierProvider provider =
118119
asyncVerifierProviders.getVerifierProvider(signatureAlgorithm);
119120
if (provider == null) {
120-
throw new IllegalArgumentException("Signature algorithm not supported: "
121-
+ signatureAlgorithm);
121+
return Futures.immediateFailedFuture(
122+
new InvalidJsonTokenException(
123+
ErrorCode.UNSUPPORTED_ALGORITHM,
124+
"Signature algorithm not supported: " + signatureAlgorithm));
122125
}
123126
futureVerifiers = provider.findVerifier(jsonToken.getIssuer(), jsonToken.getKeyId());
124127
} catch (Exception e) {
125128
return Futures.immediateFailedFuture(e);
126129
}
127130

128-
Function<List<Verifier>, List<Verifier>> checkNullFunction =
131+
// Use AsyncFunction instead of Function to allow for checked exceptions to propagate forward
132+
AsyncFunction<List<Verifier>, List<Verifier>> checkNullFunction =
129133
verifiers -> {
130-
if (verifiers == null) {
131-
throw new IllegalStateException("No valid verifier for issuer: "
132-
+ jsonToken.getIssuer());
134+
if (verifiers == null || verifiers.isEmpty()) {
135+
return Futures.immediateFailedFuture(
136+
new InvalidJsonTokenException(
137+
ErrorCode.NO_VERIFIER,
138+
"No valid verifier for issuer: " + jsonToken.getIssuer()));
133139
}
134-
return verifiers;
140+
return Futures.immediateFuture(verifiers);
135141
};
136142

137-
return Futures.transform(futureVerifiers, checkNullFunction, executor);
143+
return Futures.transformAsync(futureVerifiers, checkNullFunction, executor);
138144
}
139145

140146
}

src/main/java/net/oauth/jsontoken/JsonToken.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import net.oauth.jsontoken.crypto.AsciiStringSigner;
2626
import net.oauth.jsontoken.crypto.SignatureAlgorithm;
2727
import net.oauth.jsontoken.crypto.Signer;
28+
import net.oauth.jsontoken.exceptions.ErrorCode;
29+
import net.oauth.jsontoken.exceptions.InvalidJsonTokenException;
2830
import org.apache.commons.codec.binary.Base64;
2931
import org.joda.time.Instant;
3032

@@ -254,10 +256,18 @@ public SignatureAlgorithm getSignatureAlgorithm() {
254256

255257
JsonElement algorithmName = header.get(ALGORITHM_HEADER);
256258
if (algorithmName == null) {
257-
throw new IllegalStateException("JWT header is missing the required '" +
258-
ALGORITHM_HEADER + "' parameter");
259+
throw new IllegalStateException(
260+
"JWT header is missing the required '" + ALGORITHM_HEADER + "' parameter",
261+
new InvalidJsonTokenException(ErrorCode.BAD_HEADER));
262+
}
263+
264+
try {
265+
return SignatureAlgorithm.getFromJsonName(algorithmName.getAsString());
266+
} catch (IllegalArgumentException e) {
267+
throw new IllegalArgumentException(
268+
e.getMessage(),
269+
new InvalidJsonTokenException(ErrorCode.UNSUPPORTED_ALGORITHM));
259270
}
260-
return SignatureAlgorithm.getFromJsonName(algorithmName.getAsString());
261271
}
262272

263273
public String getTokenString() {
@@ -329,8 +339,11 @@ private String getSignature() throws SignatureException {
329339
}
330340

331341
if (signer == null) {
332-
throw new SignatureException("can't sign JsonToken with signer.");
342+
throw new SignatureException(
343+
"can't sign JsonToken with signer",
344+
new InvalidJsonTokenException(ErrorCode.ILLEGAL_STATE));
333345
}
346+
334347
String signature;
335348
// now, generate the signature
336349
AsciiStringSigner asciiSigner = new AsciiStringSigner(signer);

src/main/java/net/oauth/jsontoken/JsonTokenParser.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import net.oauth.jsontoken.crypto.Verifier;
2626
import net.oauth.jsontoken.discovery.VerifierProvider;
2727
import net.oauth.jsontoken.discovery.VerifierProviders;
28+
import net.oauth.jsontoken.exceptions.ErrorCode;
29+
import net.oauth.jsontoken.exceptions.InvalidJsonTokenException;
2830

2931
/**
3032
* Class that parses and verifies JSON Tokens.
@@ -114,7 +116,9 @@ private List<Verifier> provideVerifiers(JsonToken jsonToken) {
114116

115117
List<Verifier> verifiers = provider.findVerifier(jsonToken.getIssuer(), jsonToken.getKeyId());
116118
if (verifiers == null) {
117-
throw new IllegalStateException("No valid verifier for issuer: " + jsonToken.getIssuer());
119+
throw new IllegalStateException(
120+
"No valid verifier for issuer: " + jsonToken.getIssuer(),
121+
new InvalidJsonTokenException(ErrorCode.NO_VERIFIER));
118122
}
119123

120124
return verifiers;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package net.oauth.jsontoken.exceptions;
2+
3+
public enum ErrorCode {
4+
5+
/**
6+
* The header is missing required parameters.
7+
*/
8+
BAD_HEADER,
9+
10+
/**
11+
* Signature failed verification.
12+
*/
13+
BAD_SIGNATURE,
14+
15+
/**
16+
* IAT is after EXP or IAT is in the future
17+
*/
18+
BAD_TIME_RANGE,
19+
20+
/**
21+
* IAT and EXP are both in the past.
22+
*/
23+
EXPIRED_TOKEN,
24+
25+
/**
26+
* The token is in an illegal state because of incorrect use of the library.
27+
* If this error code appears, immediately rethrow as an {@link IllegalStateException}.
28+
*/
29+
ILLEGAL_STATE,
30+
31+
/**
32+
* Token string is corrupted and/or does not contain three components.
33+
*/
34+
MALFORMED_TOKEN_STRING,
35+
36+
/**
37+
* There are no verifiers available for a given issuer and keyId.
38+
*/
39+
NO_VERIFIER,
40+
41+
/**
42+
* Generic catch-all for exceptions with scenarios that are not pre-defined.
43+
*/
44+
UNKNOWN,
45+
46+
/**
47+
* The signature algorithm is not supported or is unknown.
48+
*/
49+
UNSUPPORTED_ALGORITHM
50+
51+
}
52+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package net.oauth.jsontoken.exceptions;
2+
3+
public final class InvalidJsonTokenException extends Exception {
4+
private final ErrorCode errorCode;
5+
6+
public InvalidJsonTokenException(ErrorCode errorCode) {
7+
this.errorCode = errorCode;
8+
}
9+
10+
public InvalidJsonTokenException(ErrorCode errorCode, String message) {
11+
super(message);
12+
this.errorCode = errorCode;
13+
}
14+
15+
public InvalidJsonTokenException(ErrorCode errorCode, Throwable cause) {
16+
super(cause);
17+
this.errorCode = errorCode;
18+
}
19+
20+
public ErrorCode getErrorCode() {
21+
return errorCode;
22+
}
23+
24+
}

0 commit comments

Comments
 (0)