diff --git a/api/client/src/main/java/org/projectnessie/client/auth/oauth2/OAuth2Utils.java b/api/client/src/main/java/org/projectnessie/client/auth/oauth2/OAuth2Utils.java index f64e3e8a27c..26f1e66e793 100644 --- a/api/client/src/main/java/org/projectnessie/client/auth/oauth2/OAuth2Utils.java +++ b/api/client/src/main/java/org/projectnessie/client/auth/oauth2/OAuth2Utils.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.databind.JsonNode; import java.net.URI; import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; import java.util.Random; import org.projectnessie.client.http.HttpClient; import org.projectnessie.client.http.HttpClientException; @@ -28,6 +30,17 @@ class OAuth2Utils { private static final Random RANDOM = new SecureRandom(); + /** + * Common locations for OpenID provider metadata. + * + * @see OpenID + * Connect Discovery 1.0 + * @see RFC 8414 Section 5 + */ + private static final String[] WELL_KNOWN_PATHS = + new String[] {".well-known/openid-configuration", ".well-known/oauth-authorization-server"}; + static String randomAlphaNumString(int length) { return RANDOM .ints('0', 'z' + 1) @@ -38,17 +51,32 @@ static String randomAlphaNumString(int length) { } public static JsonNode fetchOpenIdProviderMetadata(HttpClient httpClient, URI issuerUrl) { - HttpResponse response = - httpClient.newRequest(issuerUrl).path(".well-known/openid-configuration").get(); - Status status = response.getStatus(); - if (status != Status.OK) { - throw new HttpClientException( - "OpenID provider metadata request returned status code " + status.getCode()); + List failures = null; + for (String path : WELL_KNOWN_PATHS) { + try { + HttpResponse response = httpClient.newRequest(issuerUrl).path(path).get(); + Status status = response.getStatus(); + if (status != Status.OK) { + throw new HttpClientException( + "OpenID provider metadata request returned status code " + status.getCode()); + } + JsonNode data = response.readEntity(JsonNode.class); + if (!data.has("issuer") || !data.has("authorization_endpoint")) { + throw new HttpClientException("Invalid OpenID provider metadata"); + } + return data; + } catch (Exception e) { + if (failures == null) { + failures = new ArrayList<>(WELL_KNOWN_PATHS.length); + } + failures.add(e); + } } - JsonNode data = response.readEntity(JsonNode.class); - if (!data.has("issuer") || !data.has("authorization_endpoint")) { - throw new HttpClientException("Invalid OpenID provider metadata"); + HttpClientException e = + new HttpClientException("Failed to fetch OpenID provider metadata", failures.get(0)); + for (int i = 1; i < failures.size(); i++) { + e.addSuppressed(failures.get(i)); } - return data; + throw e; } } diff --git a/api/client/src/test/java/org/projectnessie/client/auth/oauth2/TestOAuth2Utils.java b/api/client/src/test/java/org/projectnessie/client/auth/oauth2/TestOAuth2Utils.java index befab08782f..76bfe75e867 100644 --- a/api/client/src/test/java/org/projectnessie/client/auth/oauth2/TestOAuth2Utils.java +++ b/api/client/src/test/java/org/projectnessie/client/auth/oauth2/TestOAuth2Utils.java @@ -16,7 +16,8 @@ package org.projectnessie.client.auth.oauth2; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchException; +import static org.assertj.core.api.InstanceOfAssertFactories.throwable; import static org.projectnessie.client.auth.oauth2.OAuth2ClientConfig.OBJECT_MAPPER; import com.fasterxml.jackson.databind.JsonNode; @@ -57,8 +58,12 @@ void testRandomAlphaNumString(int length) { @CsvSource({ "'' , /.well-known/openid-configuration", "/ , /.well-known/openid-configuration", + "'' , /.well-known/oauth-authorization-server", + "/ , /.well-known/oauth-authorization-server", "/realms/master , /realms/master/.well-known/openid-configuration", - "/realms/master/ , /realms/master/.well-known/openid-configuration" + "/realms/master/ , /realms/master/.well-known/openid-configuration", + "/realms/master , /realms/master/.well-known/oauth-authorization-server", + "/realms/master/ , /realms/master/.well-known/oauth-authorization-server" }) void fetchOpenIdProviderMetadataSuccess(String issuerPath, String wellKnownPath) throws Exception { @@ -75,9 +80,18 @@ void fetchOpenIdProviderMetadataWrongEndpoint() throws Exception { try (HttpTestServer server = new HttpTestServer(handler("/wrong/path", DATA), true); HttpClient httpClient = newHttpClient(server)) { URI issuerUrl = server.getUri().resolve("/realms/master/"); - assertThatThrownBy(() -> OAuth2Utils.fetchOpenIdProviderMetadata(httpClient, issuerUrl)) + Exception e = + catchException(() -> OAuth2Utils.fetchOpenIdProviderMetadata(httpClient, issuerUrl)); + assertThat(e) + .isInstanceOf(HttpClientException.class) + .hasMessageContaining("Failed to fetch OpenID provider metadata"); + assertThat(e.getCause()) .isInstanceOf(HttpClientException.class) .hasMessageContaining("404"); // messages differ between HttpClient impls + assertThat(e.getSuppressed()) + .singleElement() + .asInstanceOf(throwable(HttpClientException.class)) + .hasMessageContaining("404"); } } @@ -88,9 +102,18 @@ void fetchOpenIdProviderMetadataWrongData() throws Exception { handler("/realms/master/.well-known/openid-configuration", WRONG_DATA), true); HttpClient httpClient = newHttpClient(server)) { URI issuerUrl = server.getUri().resolve("/realms/master/"); - assertThatThrownBy(() -> OAuth2Utils.fetchOpenIdProviderMetadata(httpClient, issuerUrl)) - .isInstanceOfAny(HttpClientException.class) + Exception e = + catchException(() -> OAuth2Utils.fetchOpenIdProviderMetadata(httpClient, issuerUrl)); + assertThat(e) + .isInstanceOf(HttpClientException.class) + .hasMessageContaining("Failed to fetch OpenID provider metadata"); + assertThat(e.getCause()) + .isInstanceOf(HttpClientException.class) .hasMessage("Invalid OpenID provider metadata"); + assertThat(e.getSuppressed()) + .singleElement() + .asInstanceOf(throwable(HttpClientException.class)) + .hasMessageContaining("404"); // messages differ between HttpClient impls } }