Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add relativeUris flag in OidcConfig to allow Oidc webclient to use relative path on the request URI #5267

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/shared/security/providers/oidc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@
* <td>Port of the proxy server to use</td>
* </tr>
* <tr>
* <td>relative-uris</td>
* <td>false</td>
* <td>Flag to force the use of relative URIs in all requests. By default,
* requests that use the Proxy will have absolute URIs. Set this flag to
* true if the host is unable to accept absolute URIs.</td>
* </tr>
* <tr>
* <td>redirect-uri</td>
* <td>/oidc/redirect</td>
* <td>URI to register web server component on, used by the OIDC server to
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1088,6 +1108,7 @@ public static class Builder implements io.helidon.common.Builder<OidcConfig> {
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;
Expand Down Expand Up @@ -1140,6 +1161,7 @@ public OidcConfig build() {

WebClient.Builder webClientBuilder = OidcUtil.webClientBaseBuilder(proxyHost,
proxyPort,
relativeUris,
clientTimeout);
ClientBuilder clientBuilder = OidcUtil.clientBaseBuilder(proxyProtocol, proxyHost, proxyPort);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
klustria marked this conversation as resolved.
Show resolved Hide resolved
this.relativeUris = relativeUris;
return this;
}

/**
* Client ID as generated by OIDC server.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -17,12 +17,15 @@
package io.helidon.security.providers.oidc.common;

import java.time.Duration;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import javax.ws.rs.client.ClientBuilder;

import io.helidon.common.Errors;
import io.helidon.config.Config;
import io.helidon.config.ConfigSources;
import io.helidon.media.jsonp.JsonpSupport;
import io.helidon.security.providers.common.OutboundConfig;
import io.helidon.webclient.Proxy;
Expand Down Expand Up @@ -69,12 +72,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)
.config(Config.create(ConfigSources.create(Map.of("relative-uris", String.valueOf(relativeUris)))));
klustria marked this conversation as resolved.
Show resolved Hide resolved

if (proxyHost != null) {
webClientBuilder.proxy(Proxy.builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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() {
Expand All @@ -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();
}

Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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