From 9af67f8bff2b7b339df674ea29790d21ec366567 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 16 Aug 2021 10:45:26 +1000 Subject: [PATCH] Issue #6618 - azp claim should not be required for single value aud array Signed-off-by: Lachlan Roberts --- .../jetty/security/openid/JwtDecoder.java | 2 +- .../security/openid/OpenIdCredentials.java | 14 ++++--- .../openid/OpenIdCredentialsTest.java | 40 +++++++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java index dfdbd511f8f2..69d03a478bc5 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java @@ -35,6 +35,7 @@ public class JwtDecoder * @param jwt the JWT to decode. * @return the map of claims encoded in the JWT. */ + @SuppressWarnings("unchecked") public static Map decode(String jwt) { if (LOG.isDebugEnabled()) @@ -54,7 +55,6 @@ public static Map decode(String jwt) Object parsedJwtHeader = json.fromJSON(jwtHeaderString); if (!(parsedJwtHeader instanceof Map)) throw new IllegalStateException("Invalid JWT header"); - @SuppressWarnings("unchecked") Map jwtHeader = (Map)parsedJwtHeader; if (LOG.isDebugEnabled()) LOG.debug("JWT Header: {}", jwtHeader); diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java index c87ef1604f26..2dee0d32faf6 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java @@ -15,6 +15,7 @@ import java.io.Serializable; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -95,7 +96,7 @@ public void redeemAuthCode(OpenIdConfiguration configuration) throws Exception claims = JwtDecoder.decode(idToken); if (LOG.isDebugEnabled()) LOG.debug("claims {}", claims); - validateClaims(configuration); + validateClaims(claims, configuration); } finally { @@ -105,14 +106,14 @@ public void redeemAuthCode(OpenIdConfiguration configuration) throws Exception } } - private void validateClaims(OpenIdConfiguration configuration) throws Exception + static void validateClaims(Map claims, OpenIdConfiguration configuration) throws Exception { // Issuer Identifier for the OpenID Provider MUST exactly match the value of the iss (issuer) Claim. if (!configuration.getIssuer().equals(claims.get("iss"))) throw new AuthenticationException("Issuer Identifier MUST exactly match the iss Claim"); // The aud (audience) Claim MUST contain the client_id value. - validateAudience(configuration); + validateAudience(claims, configuration); // If an azp (authorized party) Claim is present, verify that its client_id is the Claim Value. Object azp = claims.get("azp"); @@ -126,7 +127,7 @@ private void validateClaims(OpenIdConfiguration configuration) throws Exception throw new AuthenticationException("ID Token has expired"); } - private void validateAudience(OpenIdConfiguration configuration) throws AuthenticationException + private static void validateAudience(Map claims, OpenIdConfiguration configuration) throws AuthenticationException { Object aud = claims.get("aud"); String clientId = configuration.getClientId(); @@ -138,10 +139,11 @@ private void validateAudience(OpenIdConfiguration configuration) throws Authenti throw new AuthenticationException("Audience Claim MUST contain the client_id value"); else if (isList) { - if (!Arrays.asList((Object[])aud).contains(clientId)) + List list = Arrays.asList((Object[])aud); + if (!list.contains(clientId)) throw new AuthenticationException("Audience Claim MUST contain the client_id value"); - if (claims.get("azp") == null) + if (list.size() > 1 && claims.get("azp") == null) throw new AuthenticationException("A multi-audience ID token needs to contain an azp claim"); } else if (!isValidType) diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java new file mode 100644 index 000000000000..b816d9752363 --- /dev/null +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java @@ -0,0 +1,40 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security.openid; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.client.HttpClient; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class OpenIdCredentialsTest +{ + @Test + public void testSingleAudienceValueInArray() throws Exception + { + String issuer = "myIssuer123"; + String clientId = "myClientId456"; + OpenIdConfiguration configuration = new OpenIdConfiguration(issuer, "", "", clientId, "", new HttpClient()); + + Map claims = new HashMap<>(); + claims.put("iss", issuer); + claims.put("aud", new String[]{clientId}); + claims.put("exp", System.currentTimeMillis() + 5000); + + assertDoesNotThrow(() -> OpenIdCredentials.validateClaims(claims, configuration)); + } +}