Skip to content

Commit

Permalink
Convert JWTBuildImpl to instances
Browse files Browse the repository at this point in the history
This makes it so we only need to check once for which implementation to use.
  • Loading branch information
bitwiseman committed Nov 9, 2023
1 parent dbef882 commit ef87bb7
Showing 1 changed file with 84 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.jsonwebtoken.security.SignatureAlgorithm;
import org.kohsuke.github.GHException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Key;
import java.security.PrivateKey;
Expand All @@ -26,6 +27,8 @@ final class JwtBuilderUtil {

private static final Logger LOGGER = Logger.getLogger(JwtBuilderUtil.class.getName());

private static IJwtBuilder builder;

/**
* Build a JWT.
*
Expand All @@ -40,33 +43,66 @@ final class JwtBuilderUtil {
* @return JWT
*/
static String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) {
if (builder == null) {
createBuilderImpl(issuedAt, expiration, applicationId, privateKey);
}
return builder.buildJwt(issuedAt, expiration, applicationId, privateKey);
}

private static void createBuilderImpl(Instant issuedAt,
Instant expiration,
String applicationId,
PrivateKey privateKey) {
// Figure out which builder to use and cache it. We don't worry about thread safety here because we're fine if
// the builder is assigned multiple times. The end result will be the same.
try {
return DefaultBuilderImpl.buildJwt(issuedAt, expiration, applicationId, privateKey);
builder = new DefaultBuilderImpl();
} catch (NoSuchMethodError | NoClassDefFoundError e) {
LOGGER.info(
LOGGER.warning(
"You are using an outdated version of the io.jsonwebtoken:jjwt-* suite. v0.12.x or later is recommended.");
}

// older jjwt library versions
try {
return ReflectionBuilderImpl.buildJwt(issuedAt, expiration, applicationId, privateKey);
} catch (SecurityException | ReflectiveOperationException re) {
throw new GHException(
"Could not build JWT using reflection on io.jsonwebtoken:jjwt-* suite."
+ "The minimum supported version is v0.11.x, v0.12.x or later is recommended.",
re);
try {
ReflectionBuilderImpl reflectionBuider = new ReflectionBuilderImpl();
// Build a JWT to eagerly check for any reflection errors.
reflectionBuider.buildJwtWithReflection(issuedAt, expiration, applicationId, privateKey);

builder = reflectionBuider;
} catch (ReflectiveOperationException re) {
throw new GHException(

Check warning on line 71 in src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java#L70-L71

Added lines #L70 - L71 were not covered by tests
"Could not build JWT using reflection on io.jsonwebtoken:jjwt-* suite."
+ "The minimum supported version is v0.11.x, v0.12.x or later is recommended.",
re);
}
}
}

/**
* IJwtBuilder interface to isolate loading of JWT classes allowing us to catch and handle linkage errors.
*/
interface IJwtBuilder {
/**
* Build a JWT.
*
* @param issuedAt
* issued at
* @param expiration
* expiration
* @param applicationId
* application id
* @param privateKey
* private key
* @return JWT
*/
String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey);
}

/**
* A class to isolate loading of JWT classes allowing us to catch and handle linkage errors.
*
* Without this class, JwtBuilderUtil.buildJwt() immediately throws NoClassDefFoundError when called. With this
* class the error is thrown when DefaultBuilder.build() is called allowing us to catch and handle it.
*/
private static class DefaultBuilderImpl {

private static class DefaultBuilderImpl implements IJwtBuilder {
/**
* This method builds a JWT using 0.12.x or later versions of jjwt library
*
Expand All @@ -80,10 +116,7 @@ private static class DefaultBuilderImpl {
* private key
* @return JWT
*/
private static String buildJwt(Instant issuedAt,
Instant expiration,
String applicationId,
PrivateKey privateKey) {
public String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) {

// io.jsonwebtoken.security.SignatureAlgorithm is not present in v0.11.x and below.
// Trying to call a method that uses it causes "NoClassDefFoundError" if v0.11.x is being used.
Expand All @@ -102,7 +135,28 @@ private static String buildJwt(Instant issuedAt,
/**
* A class to encapsulate building a JWT using reflection.
*/
private static class ReflectionBuilderImpl {
private static class ReflectionBuilderImpl implements IJwtBuilder {

private Method setIssuedAtMethod;
private Method setExpirationMethod;
private Method setIssuerMethod;
private Enum<?> rs256SignatureAlgorithm;
private Method signWithMethod;
private Method serializeToJsonMethod;

ReflectionBuilderImpl() throws ReflectiveOperationException {
JwtBuilder jwtBuilder = Jwts.builder();
Class<?> jwtReflectionClass = jwtBuilder.getClass();

setIssuedAtMethod = jwtReflectionClass.getMethod("setIssuedAt", Date.class);
setIssuerMethod = jwtReflectionClass.getMethod("setIssuer", String.class);
setExpirationMethod = jwtReflectionClass.getMethod("setExpiration", Date.class);
Class<?> signatureAlgorithmClass = Class.forName("io.jsonwebtoken.SignatureAlgorithm");
rs256SignatureAlgorithm = createEnumInstance(signatureAlgorithmClass, "RS256");
signWithMethod = jwtReflectionClass.getMethod("signWith", Key.class, signatureAlgorithmClass);
serializeToJsonMethod = jwtReflectionClass.getMethod("serializeToJsonWith", Serializer.class);
}

/**
* This method builds a JWT using older (pre 0.12.x) versions of jjwt library by leveraging reflection.
*
Expand All @@ -115,25 +169,22 @@ private static class ReflectionBuilderImpl {
* @param privateKey
* private key
* @return JWTBuilder
* @throws ReflectiveOperationException
* if reflection fails
*/
private static String buildJwt(Instant issuedAt,
public String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) {

try {
return buildJwtWithReflection(issuedAt, expiration, applicationId, privateKey);
} catch (ReflectiveOperationException e) {

Check warning on line 177 in src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java#L177

Added line #L177 was not covered by tests
// This should never happen. Reflection errors should have been caught during initialization.
throw new GHException("Reflection errors during JWT creation should have been checked already.", e);

Check warning on line 179 in src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java#L179

Added line #L179 was not covered by tests
}
}

private String buildJwtWithReflection(Instant issuedAt,
Instant expiration,
String applicationId,
PrivateKey privateKey) throws ReflectiveOperationException {

PrivateKey privateKey) throws IllegalAccessException, InvocationTargetException {
JwtBuilder jwtBuilder = Jwts.builder();
Class<?> jwtReflectionClass = jwtBuilder.getClass();

Method setIssuedAtMethod = jwtReflectionClass.getMethod("setIssuedAt", Date.class);
Method setIssuerMethod = jwtReflectionClass.getMethod("setIssuer", String.class);
Method setExpirationMethod = jwtReflectionClass.getMethod("setExpiration", Date.class);
Class<?> signatureAlgorithmClass = Class.forName("io.jsonwebtoken.SignatureAlgorithm");
Enum<?> rs256SignatureAlgorithm = createEnumInstance(signatureAlgorithmClass, "RS256");
Method signWithMethod = jwtReflectionClass.getMethod("signWith", Key.class, signatureAlgorithmClass);
Method serializeToJsonMethod = jwtReflectionClass.getMethod("serializeToJsonWith", Serializer.class);

Object builderObj = jwtBuilder;
builderObj = setIssuedAtMethod.invoke(builderObj, Date.from(issuedAt));
builderObj = setExpirationMethod.invoke(builderObj, Date.from(expiration));
Expand Down

0 comments on commit ef87bb7

Please sign in to comment.