Skip to content

Commit

Permalink
OAuth2 client: add support for RFC 8414 metadata discovery (#8789)
Browse files Browse the repository at this point in the history
  • Loading branch information
adutra authored Jun 11, 2024
1 parent aa05ef4 commit 1da4e62
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,6 +30,17 @@ class OAuth2Utils {

private static final Random RANDOM = new SecureRandom();

/**
* Common locations for OpenID provider metadata.
*
* @see <a
* href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">OpenID
* Connect Discovery 1.0</a>
* @see <a href="https://tools.ietf.org/html/rfc8414#section-5">RFC 8414 Section 5</a>
*/
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)
Expand All @@ -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<Exception> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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");
}
}

Expand All @@ -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
}
}

Expand Down

0 comments on commit 1da4e62

Please sign in to comment.