New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend type support on JWT claims #451
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,9 +31,14 @@ | |
|
||
package com.google.auth.oauth2; | ||
|
||
import com.google.api.client.util.Preconditions; | ||
import com.google.auto.value.AutoValue; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableMap; | ||
import java.io.Serializable; | ||
import java.util.Collection; | ||
import java.util.Date; | ||
import java.util.List; | ||
import java.util.Map; | ||
import javax.annotation.Nullable; | ||
|
||
|
@@ -68,10 +73,10 @@ public abstract class JwtClaims implements Serializable { | |
* | ||
* @return additional claims | ||
*/ | ||
abstract Map<String, String> getAdditionalClaims(); | ||
abstract Map<String, ?> getAdditionalClaims(); | ||
|
||
public static Builder newBuilder() { | ||
return new AutoValue_JwtClaims.Builder().setAdditionalClaims(ImmutableMap.<String, String>of()); | ||
return new AutoValue_JwtClaims.Builder().setAdditionalClaims(ImmutableMap.<String, Object>of()); | ||
} | ||
|
||
/** | ||
|
@@ -83,7 +88,7 @@ public static Builder newBuilder() { | |
* @return new claims | ||
*/ | ||
public JwtClaims merge(JwtClaims other) { | ||
ImmutableMap.Builder<String, String> newClaimsBuilder = ImmutableMap.builder(); | ||
ImmutableMap.Builder<String, Object> newClaimsBuilder = ImmutableMap.builder(); | ||
newClaimsBuilder.putAll(getAdditionalClaims()); | ||
newClaimsBuilder.putAll(other.getAdditionalClaims()); | ||
|
||
|
@@ -111,14 +116,123 @@ public boolean isComplete() { | |
|
||
@AutoValue.Builder | ||
public abstract static class Builder { | ||
/** Basic types supported by JSON standard. */ | ||
private static List<Class<? extends Serializable>> SUPPORTED_BASIC_TYPES = | ||
ImmutableList.of( | ||
String.class, | ||
Integer.class, | ||
Double.class, | ||
Float.class, | ||
Boolean.class, | ||
Date.class, | ||
String[].class, | ||
Integer[].class, | ||
Double[].class, | ||
Float[].class, | ||
Boolean[].class, | ||
Date[].class); | ||
|
||
private static final String ERROR_MESSAGE = | ||
"Invalid type on additional claims. Valid types are String, Integer, " | ||
+ "Double, Float, Boolean, Date, List and Map. Map keys must be Strings."; | ||
|
||
public abstract Builder setAudience(String audience); | ||
|
||
public abstract Builder setIssuer(String issuer); | ||
|
||
public abstract Builder setSubject(String subject); | ||
|
||
public abstract Builder setAdditionalClaims(Map<String, String> additionalClaims); | ||
|
||
public abstract JwtClaims build(); | ||
public abstract Builder setAdditionalClaims(Map<String, ?> additionalClaims); | ||
|
||
protected abstract JwtClaims autoBuild(); | ||
|
||
public JwtClaims build() { | ||
JwtClaims claims = autoBuild(); | ||
Preconditions.checkState(validateClaims(claims.getAdditionalClaims()), ERROR_MESSAGE); | ||
return claims; | ||
} | ||
|
||
/** | ||
* Validate if the objects on a Map are valid for a JWT claim. | ||
* | ||
* @param claims Map of claim objects to be validated | ||
*/ | ||
private static boolean validateClaims(Map<String, ?> claims) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is interesting code. I wonder if there's a library for this somewhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I asked a fellow programmer friends who are more experience than me on Java before coming up with this and they didn't know of any. I ultimately got inspired by the logic used on java-jwt though I wanted to provide a more robust support than that implementation. Open to any suggestions you may have on how to improve this code! |
||
if (!validateKeys(claims)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it really required here given that the key type is explicitly defined as String? |
||
return false; | ||
} | ||
|
||
for (Object claim : claims.values()) { | ||
if (!validateObject(claim)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Validates if the object is a valid JSON supported type. | ||
* | ||
* @param object to be evaluated | ||
*/ | ||
private static final boolean validateObject(@Nullable Object object) { | ||
// According to JSON spec, null is a valid value. | ||
if (object == null) { | ||
return true; | ||
} | ||
|
||
if (object instanceof List) { | ||
return validateCollection((List) object); | ||
} else if (object instanceof Map) { | ||
return validateKeys((Map) object) && validateCollection(((Map) object).values()); | ||
} | ||
|
||
return isSupportedValue(object); | ||
} | ||
|
||
/** | ||
* Validates the keys on a given map. Keys must be Strings. | ||
* | ||
* @param map map to be evaluated | ||
*/ | ||
private static final boolean validateKeys(Map map) { | ||
for (Object key : map.keySet()) { | ||
if (!(key instanceof String)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Validates if a collection is a valid JSON value. Empty collections are considered valid. | ||
* | ||
* @param collection collection to be evaluated | ||
*/ | ||
private static final boolean validateCollection(Collection collection) { | ||
if (collection.isEmpty()) { | ||
return true; | ||
} | ||
|
||
for (Object item : collection) { | ||
if (!validateObject(item)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Validates if the given object is an instance of a valid JSON basic type. | ||
* | ||
* @param value object to be evaluated. | ||
*/ | ||
private static final boolean isSupportedValue(Object value) { | ||
Class clazz = value.getClass(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can remove this and pass into contains directly? since its trivial and not reused |
||
return SUPPORTED_BASIC_TYPES.contains(clazz); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a breaking change. It should be possible to keep the old one marked as Deprecated and calling this new signature internally