Allow mixed usage of boolean and string when merging OIDC claims (#59112) (#59512)

Certain OPs mix usage of boolean and string for boolean type OIDC claims. For example, the same "email_verified" field is presented as boolean in IdToken, but is a string of "true" in the response of user info. This inconsistency results in failures when we try to merge them during authorization.

This PR introduce a small leniency so that it will merge a boolean with a string that has value of the boolean's string representation. In another word, it will merge true with "true", also will merge false with "false", but nothing else.
This commit is contained in:
Yang Wang 2020-07-14 20:41:16 +10:00 committed by GitHub
parent 4180333bbc
commit 2e71d0aa91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 2 deletions

View File

@ -670,8 +670,15 @@ public class OpenIdConnectAuthenticator {
} else if (value1 instanceof JSONObject) {
idToken.put(entry.getKey(), mergeObjects((JSONObject) value1, value2));
} else if (value1.getClass().equals(value2.getClass()) == false) {
throw new IllegalStateException("Error merging ID token and userinfo claim value for claim [" + entry.getKey() + "]. " +
"Cannot merge [" + value1.getClass().getName() + "] with [" + value2.getClass().getName() + "]");
// A special handling for certain OPs that mix the usage of true and "true"
if (value1 instanceof Boolean && value2 instanceof String && String.valueOf(value1).equals(value2)) {
idToken.put(entry.getKey(), value1);
} else if (value2 instanceof Boolean && value1 instanceof String && String.valueOf(value2).equals(value1)) {
idToken.put(entry.getKey(), value2);
} else {
throw new IllegalStateException("Error merging ID token and userinfo claim value for claim [" + entry.getKey() + "]. " +
"Cannot merge [" + value1.getClass().getName() + "] with [" + value2.getClass().getName() + "]");
}
}
}
for (Map.Entry<String, Object> entry : userInfo.entrySet()) {

View File

@ -829,6 +829,51 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
assertTrue(combinedAddress.containsKey("country"));
}
public void testJsonObjectMergingWithBooleanLeniency() {
final JSONObject idTokenObject = new JWTClaimsSet.Builder()
.claim("email_verified", true)
.claim("email_verified_1", "true")
.claim("email_verified_2", false)
.claim("email_verified_3", "false")
.build()
.toJSONObject();
final JSONObject userInfoObject = new JWTClaimsSet.Builder()
.claim("email_verified", "true")
.claim("email_verified_1", true)
.claim("email_verified_2", "false")
.claim("email_verified_3", false)
.build()
.toJSONObject();
OpenIdConnectAuthenticator.mergeObjects(idTokenObject, userInfoObject);
assertSame(Boolean.TRUE, idTokenObject.get("email_verified"));
assertSame(Boolean.TRUE, idTokenObject.get("email_verified_1"));
assertSame(Boolean.FALSE, idTokenObject.get("email_verified_2"));
assertSame(Boolean.FALSE, idTokenObject.get("email_verified_3"));
final JSONObject idTokenObject1 = new JWTClaimsSet.Builder()
.claim("email_verified", true)
.build()
.toJSONObject();
final JSONObject userInfoObject1 = new JWTClaimsSet.Builder()
.claim("email_verified", "false")
.build()
.toJSONObject();
IllegalStateException e =
expectThrows(IllegalStateException.class, () -> OpenIdConnectAuthenticator.mergeObjects(idTokenObject1, userInfoObject1));
assertThat(e.getMessage(), containsString("Cannot merge [java.lang.Boolean] with [java.lang.String]"));
final JSONObject idTokenObject2 = new JWTClaimsSet.Builder()
.claim("email_verified", true)
.build()
.toJSONObject();
final JSONObject userInfoObject2 = new JWTClaimsSet.Builder()
.claim("email_verified", "yes")
.build()
.toJSONObject();
e = expectThrows(IllegalStateException.class, () -> OpenIdConnectAuthenticator.mergeObjects(idTokenObject2, userInfoObject2));
assertThat(e.getMessage(), containsString("Cannot merge [java.lang.Boolean] with [java.lang.String]"));
}
private OpenIdConnectProviderConfiguration getOpConfig() throws URISyntaxException {
return new OpenIdConnectProviderConfiguration(
new Issuer("https://op.example.com"),