diff --git a/docs/shared/security/providers/oidc.adoc b/docs/shared/security/providers/oidc.adoc
index 1931bc3a483..7d68a75afb6 100644
--- a/docs/shared/security/providers/oidc.adoc
+++ b/docs/shared/security/providers/oidc.adoc
@@ -82,6 +82,7 @@ an important distinction when more than one provider is used
|`proxy-protocol` |`http` |Proxy protocol to use when proxy is used
|`proxy-host` |`null` |Proxy host to use. When defined, triggers usage of proxy for HTTP requests
|`proxy-port` |`80` |Port of the proxy server to use
+|`relative-uris`|`false`|Can be set to `true` to force the use of relative URIs in all requests, regardless of the presence or absence of proxies or no-proxy lists. By default, requests that use the Proxy will have absolute URIs. Set this flag to `true` if the receiving host is unable to accept absolute URIs.
|`redirect-uri` |`/oidc/redirect` |URI to register web server component on, used by the OIDC server to redirect authorization requests to after a user logs in or approves scopes. Note that usually the redirect URI configured here must be the same one as configured on OIDC server.
|`scope-audience` |empty string |Audience of the scope required by this application. This is prefixed to the scope name when requesting scopes from the identity server.
|`cookie-use` |`true` |Whether to use cookie to store JWT. If used, redirects happen only in case the user is not authenticated or has insufficient scopes
diff --git a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcConfig.java b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcConfig.java
index b9697e105cc..2deff823f06 100644
--- a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcConfig.java
+++ b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcConfig.java
@@ -117,6 +117,13 @@
*
* redirect-uri |
* /oidc/redirect |
* URI to register web server component on, used by the OIDC server to
@@ -332,6 +339,7 @@ public final class OidcConfig {
static final boolean DEFAULT_PARAM_USE = false;
static final boolean DEFAULT_HEADER_USE = false;
static final String DEFAULT_PROXY_PROTOCOL = "http";
+ static final boolean DEFAULT_RELATIVE_URIS = false;
static final String DEFAULT_BASE_SCOPES = "openid";
static final boolean DEFAULT_JWT_VALIDATE_JWK = true;
static final boolean DEFAULT_REDIRECT = true;
@@ -385,6 +393,7 @@ public final class OidcConfig {
private final CrossOriginConfig crossOriginConfig;
private final boolean forceHttpsRedirects;
private final Duration tokenRefreshSkew;
+ private final boolean relativeUris;
private OidcConfig(Builder builder) {
this.clientId = builder.clientId;
@@ -418,6 +427,7 @@ private OidcConfig(Builder builder) {
this.tokenEndpointAuthentication = builder.tokenEndpointAuthentication;
this.clientTimeout = builder.clientTimeout;
this.forceHttpsRedirects = builder.forceHttpsRedirects;
+ this.relativeUris = builder.relativeUris;
if (tokenEndpointAuthentication == ClientAuthentication.CLIENT_SECRET_POST) {
// we should only store this if required
@@ -986,6 +996,16 @@ public Duration tokenRefreshSkew() {
return tokenRefreshSkew;
}
+ /**
+ * Determines whether to force the use of relative URIs in all requests,
+ * regardless of the presence or absence of proxies or no-proxy lists.
+ *
+ * @return {@code true} if we should use relative URIs
+ */
+ public boolean relativeUris() {
+ return relativeUris;
+ }
+
/**
* Client Authentication methods that are used by Clients to authenticate to the Authorization
* Server when using the Token Endpoint.
@@ -1088,6 +1108,7 @@ public static class Builder implements io.helidon.common.Builder {
private String proxyProtocol = DEFAULT_PROXY_PROTOCOL;
private String proxyHost;
private int proxyPort = DEFAULT_PROXY_PORT;
+ private boolean relativeUris = DEFAULT_RELATIVE_URIS;
private String scopeAudience;
private OidcMetadata.Builder oidcMetadata = OidcMetadata.builder();
private String frontendUri;
@@ -1140,6 +1161,7 @@ public OidcConfig build() {
WebClient.Builder webClientBuilder = OidcUtil.webClientBaseBuilder(proxyHost,
proxyPort,
+ relativeUris,
clientTimeout);
ClientBuilder clientBuilder = OidcUtil.clientBaseBuilder(proxyProtocol, proxyHost, proxyPort);
@@ -1275,6 +1297,7 @@ public Builder config(Config config) {
.ifPresent(this::proxyProtocol);
config.get("proxy-host").asString().ifPresent(this::proxyHost);
config.get("proxy-port").asInt().ifPresent(this::proxyPort);
+ config.get("relative-uris").asBoolean().ifPresent(this::relativeUris);
// our application
config.get("redirect-uri").asString().ifPresent(this::redirectUri);
@@ -1951,6 +1974,22 @@ public Builder proxyPort(int proxyPort) {
return this;
}
+ /**
+ * Can be set to {@code true} to force the use of relative URIs in all requests,
+ * regardless of the presence or absence of proxies or no-proxy lists. By default,
+ * requests that use the Proxy will have absolute URIs. Set this flag to {@code true}
+ * if the host is unable to accept absolute URIs.
+ * Defaults to {@value #DEFAULT_RELATIVE_URIS}.
+ *
+ * @param relativeUris relative URIs flag
+ * @return updated builder instance
+ */
+ @ConfiguredOption("false")
+ public Builder relativeUris(boolean relativeUris) {
+ this.relativeUris = relativeUris;
+ return this;
+ }
+
/**
* Client ID as generated by OIDC server.
*
diff --git a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcUtil.java b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcUtil.java
index ae07d5b20ea..b221118d255 100644
--- a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcUtil.java
+++ b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -69,12 +69,14 @@ static ClientBuilder clientBaseBuilder(String proxyProtocol, String proxyHost, i
static WebClient.Builder webClientBaseBuilder(String proxyHost,
int proxyPort,
+ boolean relativeUris,
Duration clientTimeout) {
WebClient.Builder webClientBuilder = WebClient.builder()
.addService(WebClientTracing.create())
.addMediaSupport(JsonpSupport.create())
.connectTimeout(clientTimeout.toMillis(), TimeUnit.MILLISECONDS)
- .readTimeout(clientTimeout.toMillis(), TimeUnit.MILLISECONDS);
+ .readTimeout(clientTimeout.toMillis(), TimeUnit.MILLISECONDS)
+ .relativeUris(relativeUris);
if (proxyHost != null) {
webClientBuilder.proxy(Proxy.builder()
diff --git a/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigAbstractTest.java b/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigAbstractTest.java
index 1347b45f488..a7afac2ab42 100644
--- a/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigAbstractTest.java
+++ b/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigAbstractTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2021 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2022 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,10 @@ void testExplicitValues() {
is("http://identity.oracle.com/authorization")),
() -> assertThat("Introspect endpoint",
config.introspectEndpoint().getUri(),
- is(URI.create("http://identity.oracle.com/introspect")))
+ is(URI.create("http://identity.oracle.com/introspect"))),
+ () -> assertThat("Validate relativeUris flag",
+ config.relativeUris(),
+ is(true))
);
}
diff --git a/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromBuilderTest.java b/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromBuilderTest.java
index c0fbea8f1fc..5cd0700fbc6 100644
--- a/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromBuilderTest.java
+++ b/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromBuilderTest.java
@@ -18,12 +18,18 @@
import io.helidon.config.Config;
import io.helidon.config.ConfigSources;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.WebServer;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
import java.net.URI;
+import java.time.Duration;
import java.util.Arrays;
import java.util.Map;
@@ -32,6 +38,9 @@
*/
class OidcConfigFromBuilderTest extends OidcConfigAbstractTest {
private OidcConfig oidcConfig;
+ private boolean isCommunicationWithProxy = true;
+ private String httpHostPort;
+ private boolean relativeUris;
private String cookieEncryptionPasswordValue;
OidcConfigFromBuilderTest() {
@@ -46,6 +55,7 @@ class OidcConfigFromBuilderTest extends OidcConfigAbstractTest {
.tokenEndpointUri(URI.create("http://identity.oracle.com/tokens"))
.authorizationEndpointUri(URI.create("http://identity.oracle.com/authorization"))
.introspectEndpointUri(URI.create("http://identity.oracle.com/introspect"))
+ .relativeUris(true)
.build();
}
@@ -54,6 +64,105 @@ OidcConfig getConfig() {
return oidcConfig;
}
+ @Test
+ void testDefaultValues() {
+ OidcConfig config = OidcConfig.builder()
+ // The next 3 parameters need to be set or config builder will fail
+ .identityUri(URI.create("https://identity.oracle.com"))
+ .clientId("client-id-value")
+ .clientSecret("client-secret-value")
+ // Set to false so it will not load metadata
+ .oidcMetadataWellKnown(false)
+ .build();
+ assertAll("All values using defaults",
+ () -> assertThat("Redirect URI", config.redirectUri(), is(OidcConfig.DEFAULT_REDIRECT_URI)),
+ () -> assertThat("Should Redirect", config.shouldRedirect(), is(OidcConfig.DEFAULT_REDIRECT)),
+ () -> assertThat("Logout URI", config.logoutUri(), is(OidcConfig.DEFAULT_LOGOUT_URI)),
+ () -> assertThat("Use Parameter", config.useParam(), is(OidcConfig.DEFAULT_PARAM_USE)),
+ () -> assertThat("Parameter Name", config.paramName(), is(OidcConfig.DEFAULT_PARAM_NAME)),
+ () -> assertThat("Relative URIs", config.relativeUris(), is(OidcConfig.DEFAULT_RELATIVE_URIS)),
+ () -> assertThat("Use Cookie", config.useCookie(), is(OidcConfig.DEFAULT_COOKIE_USE)),
+ () -> assertThat("Use Header", config.useHeader(), is(OidcConfig.DEFAULT_HEADER_USE)),
+ () -> assertThat("Base scopes to use", config.baseScopes(), is(OidcConfig.DEFAULT_BASE_SCOPES)),
+ () -> assertThat("Cookie value prefix", config.cookieValuePrefix(), is(OidcConfig.DEFAULT_COOKIE_NAME + "=")),
+ () -> assertThat("Cookie name", config.cookieName(), is(OidcConfig.DEFAULT_COOKIE_NAME)),
+ () -> assertThat("Realm", config.realm(), is(OidcConfig.DEFAULT_REALM)),
+ () -> assertThat("Redirect Attempt Parameter", config.redirectAttemptParam(), is(OidcConfig.DEFAULT_ATTEMPT_PARAM)),
+ () -> assertThat("Max Redirects", config.maxRedirects(), is(OidcConfig.DEFAULT_MAX_REDIRECTS)),
+ () -> assertThat("Client Timeout", config.clientTimeout(), is(Duration.ofSeconds(OidcConfig.DEFAULT_TIMEOUT_SECONDS))),
+ () -> assertThat("Force HTTPS Redirects", config.forceHttpsRedirects(), is(OidcConfig.DEFAULT_FORCE_HTTPS_REDIRECTS)),
+ () -> assertThat("Token Refresh Skew", config.tokenRefreshSkew(), is(OidcConfig.DEFAULT_TOKEN_REFRESH_SKEW)),
+ // cookie options should be separated by space as defined by the specification
+ () -> assertThat("Cookie options", config.cookieOptions(), is("; Path=/; HttpOnly; SameSite=Lax")),
+ () -> assertThat("Audience", config.audience(), is("https://identity.oracle.com")),
+ () -> assertThat("Parameter name", config.paramName(), is("accessToken")),
+ () -> assertThat("Issuer", config.issuer(), nullValue()),
+ () -> assertThat("Client without authentication", config.generalClient(), notNullValue()),
+ () -> assertThat("Client with authentication", config.appClient(), notNullValue()),
+ () -> assertThat("JWK Keys", config.signJwk(), notNullValue())
+ );
+ }
+
+ @Test
+ void testRequestUrisWithProxy() {
+ httpHostPort = ""; // This will be set once the server is up
+ isCommunicationWithProxy = true; // initial request is with a proxy
+ // This server will simulate a Proxy on the 1st request and Identity Server on the 2nd request
+ WebServer proxyAndIdentityServer = WebServer.builder()
+ .host("localhost")
+ .routing(Routing.builder()
+ .any((req, res) -> {
+ // Simulate a successful Proxy response
+ if (isCommunicationWithProxy) {
+ // Flip to false so next request will simulate Identity Server interaction
+ isCommunicationWithProxy = false;
+ res.send();
+ }
+ // Simulate a failed Identity response if relativeURIs=false but the request URI is relative
+ else if (!relativeUris && !req.uri().toASCIIString().startsWith(httpHostPort)) {
+ res.status(500);
+ res.send("URI must be absolute");
+ }
+ // Simulate a failed Identity response if relativeURIs=true but the request URI is absolute
+ else if (relativeUris && req.uri().toASCIIString().startsWith(httpHostPort)) {
+ res.status(500);
+ res.send("URI must be relative");
+ }
+ // Simulate a successful Identity response
+ else {
+ res.send("{}");
+ }
+ }))
+ .build();
+ proxyAndIdentityServer.start().await(Duration.ofSeconds(10));
+ httpHostPort = "http://localhost:" + proxyAndIdentityServer.port();
+
+ // 1st test will simulate relativeUris=false and will fail if URI is relative
+ OidcConfig.builder()
+ // The next 3 parameters need to be set or config builder will fail
+ .identityUri(URI.create(httpHostPort + "/identity"))
+ .clientId("client-id-value")
+ .clientSecret("client-secret-value")
+ .proxyProtocol("http")
+ .proxyHost("localhost")
+ .proxyPort(proxyAndIdentityServer.port())
+ .build();
+
+ // 2nd test will simulate relativeUris=true and will fail if URI is absolute
+ relativeUris = true;
+ OidcConfig.builder()
+ // The next 3 parameters need to be set or config builder will fail
+ .identityUri(URI.create(httpHostPort + "/identity"))
+ .clientId("client-id-value")
+ .clientSecret("client-secret-value")
+ .proxyProtocol("http")
+ .proxyHost("localhost")
+ .proxyPort(proxyAndIdentityServer.port())
+ .relativeUris(relativeUris)
+ .build();
+ proxyAndIdentityServer.shutdown();
+ }
+
@Test
void testCookieEncryptionPasswordFromBuilderConfig() {
OidcConfig.Builder builder = new TestOidcConfigBuilder();
diff --git a/security/providers/oidc-common/src/test/resources/application.yaml b/security/providers/oidc-common/src/test/resources/application.yaml
index 33b86fa0ca8..fcd5dd056c9 100644
--- a/security/providers/oidc-common/src/test/resources/application.yaml
+++ b/security/providers/oidc-common/src/test/resources/application.yaml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2018, 2022 Oracle and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -28,3 +28,4 @@ security:
token-endpoint-uri: "http://identity.oracle.com/tokens"
authorization-endpoint-uri: "http://identity.oracle.com/authorization"
introspect-endpoint-uri: "http://identity.oracle.com/introspect"
+ relative-uris: true
diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java
index ecd6b94c880..8a93e05786f 100644
--- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java
+++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java
@@ -204,6 +204,18 @@ public Builder proxy(Proxy proxy) {
return this;
}
+ /**
+ * Can be set to {@code true} to force the use of relative URIs in all requests,
+ * regardless of the presence or absence of proxies or no-proxy lists.
+ *
+ * @param relativeUris relative URIs flag
+ * @return updated builder instance
+ */
+ public Builder relativeUris(boolean relativeUris) {
+ this.configuration.relativeUris(relativeUris);
+ return this;
+ }
+
@Override
public Builder mediaContext(MediaContext mediaContext) {
configuration.mediaContext(mediaContext);
diff --git a/webclient/webclient/src/test/java/io/helidon/webclient/ProxyTest.java b/webclient/webclient/src/test/java/io/helidon/webclient/ProxyTest.java
index f7f6f05e734..0ef61269c38 100644
--- a/webclient/webclient/src/test/java/io/helidon/webclient/ProxyTest.java
+++ b/webclient/webclient/src/test/java/io/helidon/webclient/ProxyTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2021 Oracle and/or its affiliates.
+ * Copyright (c) 2020, 2022 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,16 +80,27 @@ void testNoProxyHandlingPredicate() {
}
@Test
- void testForceRelativeUris() {
+ void testForceRelativeUrisViaWebClientConfiguration() {
Config config = Config.create();
Proxy proxy = Proxy.create(config.get("proxy"));
WebClientConfiguration webConfig = WebClientConfiguration.builder()
- .config(config.get("force-relative-uris")).build();
- boolean relativeUris = webConfig.relativeUris();
- assertThat(relativizeNoProxy(URI.create("http://www.localhost/foo"), proxy, relativeUris).toString(),
- is("/foo"));
- assertThat(relativizeNoProxy(URI.create("http://identity.oci.com/foo/bar"), proxy, relativeUris).toString(),
- is("/foo/bar"));
+ .config(config.get("force-relative-uris"))
+ .proxy(proxy)
+ .build();
+ validateRelativizeNoProxy(webConfig);
+ }
+
+ @Test
+ void testForceRelativeUrisViaWebClient() {
+ Proxy proxy = Proxy.builder()
+ .host("localhost")
+ .port(8080)
+ .build();
+ WebClientConfiguration webConfig = WebClient.builder()
+ .relativeUris(true)
+ .proxy(proxy)
+ .configuration();
+ validateRelativizeNoProxy(webConfig);
}
@Test
@@ -99,7 +110,16 @@ void testDefaultProxyType() {
assertThat(proxy.type(), is(Proxy.ProxyType.HTTP));
}
+ private void validateRelativizeNoProxy(WebClientConfiguration webConfig) {
+ boolean relativeUris = webConfig.relativeUris();
+ Proxy proxy = webConfig.proxy().get();
+ assertThat(relativizeNoProxy(URI.create("http://www.localhost/foo"), proxy, relativeUris).toString(),
+ is("/foo"));
+ assertThat(relativizeNoProxy(URI.create("http://identity.oci.com/foo/bar"), proxy, relativeUris).toString(),
+ is("/foo/bar"));
+ }
+
private URI address(String host, int port) {
return URI.create("http://" + host + ":" + port);
}
-}
\ No newline at end of file
+}
|