diff --git a/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonConnector.java b/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonConnector.java index 6ebf8dcd980..8156474535a 100644 --- a/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonConnector.java +++ b/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonConnector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -33,6 +33,7 @@ import java.util.logging.Logger; import io.helidon.common.Version; +import io.helidon.config.Config; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; import io.helidon.webclient.WebClientResponse; @@ -91,12 +92,21 @@ public int read() throws IOException { HelidonStructures.createProxy(config).ifPresent(webClientBuilder::proxy); - HelidonStructures.helidonConfig(config).ifPresent(webClientBuilder::config); + Optional helidonConfig = HelidonStructures.helidonConfig(config); + helidonConfig.ifPresent(webClientBuilder::config); webClientBuilder.connectTimeout(ClientProperties.getValue(config.getProperties(), ClientProperties.CONNECT_TIMEOUT, 10000), TimeUnit.MILLISECONDS); - HelidonStructures.createSSL(client.getSslContext()).ifPresent(webClientBuilder::tls); + //Whether WebClient TLS has been already set via config + boolean helidonConfigTlsSet = helidonConfig.map(hc -> hc.get("tls").exists()).orElse(false); + boolean isJerseyClient = client instanceof JerseyClient; + //Whether Jersey client has non-default SslContext set. If so, we should honor these settings + boolean jerseyHasDefaultSsl = isJerseyClient && ((JerseyClient) client).isDefaultSslContext(); + + if (!helidonConfigTlsSet || !isJerseyClient || !jerseyHasDefaultSsl) { + HelidonStructures.createSSL(client.getSslContext()).ifPresent(webClientBuilder::tls); + } webClient = webClientBuilder.build(); } diff --git a/tests/integration/pom.xml b/tests/integration/pom.xml index 3abff4830d6..ee57934143c 100644 --- a/tests/integration/pom.xml +++ b/tests/integration/pom.xml @@ -1,6 +1,6 @@ + + + 4.0.0 + + io.helidon.tests.integration + helidon-tests-integration + 3.2.6-SNAPSHOT + + + helidon-tests-integration-restclient-connector + Helidon Integration Test RestClient Webclient Connector + + + + io.helidon.microprofile.bundles + helidon-microprofile + + + io.helidon.microprofile.rest-client + helidon-microprofile-rest-client + + + org.jboss + jandex + runtime + true + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.microprofile.tests + helidon-microprofile-tests-junit5 + test + + + io.helidon.jersey + helidon-jersey-connector + test + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + + + \ No newline at end of file diff --git a/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResource.java b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResource.java new file mode 100644 index 00000000000..9d80a04f57b --- /dev/null +++ b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResource.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.resclient.connector; + +import java.util.Collections; + +import jakarta.json.Json; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +/** + * A typical greet resource that only handles a single GET for a default message. + */ +@Path("/greet") +public class GreetResource { + + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + + @GET + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getDefaultMessage() { + return createResponse("World"); + } + + private JsonObject createResponse(String who) { + String msg = String.format("%s %s!", "Hello", who); + + return JSON.createObjectBuilder() + .add("message", msg) + .build(); + } +} diff --git a/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceClient.java b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceClient.java new file mode 100644 index 00000000000..0ff3f8b3a47 --- /dev/null +++ b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceClient.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.resclient.connector; + +import jakarta.json.JsonObject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; + +/** + * RestClient interface for a simple greet resource that includes a few FT annotations. + */ +@Path("/greet") +@RegisterProvider(GreetResourceFilter.class) +public interface GreetResourceClient { + + @GET + @Produces(MediaType.APPLICATION_JSON) + JsonObject getDefaultMessage(); + +} diff --git a/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceFilter.java b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceFilter.java new file mode 100644 index 00000000000..ccfaf09bf69 --- /dev/null +++ b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.resclient.connector; + +import java.io.IOException; +import java.net.URI; + +import io.helidon.common.context.Contexts; + +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; + +/** + * A client request filter that replaces port 8080 by the ephemeral port allocated for the + * webserver in each run. This is necessary since {@link GreetResourceClient} uses an annotation + * to specify the base URI, and its value cannot be changed dynamically. + */ +public class GreetResourceFilter implements ClientRequestFilter { + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + URI uri = requestContext.getUri(); + String fixedUri = uri.toString().replace("8080", extractDynamicPort()); + requestContext.setUri(URI.create(fixedUri)); + } + + private String extractDynamicPort() { + URI uri = Contexts.globalContext().get(getClass(), URI.class).orElseThrow(); + String uriString = uri.toString(); + int k = uriString.lastIndexOf(":"); + int j = uriString.indexOf("/", k); + j = j < 0 ? uriString.length() : j; //Prevent failing if / is missing after the port + return uriString.substring(k + 1, j); + } +} diff --git a/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/package-info.java b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/package-info.java new file mode 100644 index 00000000000..510c7bd1a2b --- /dev/null +++ b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.tests.integration.resclient.connector; \ No newline at end of file diff --git a/tests/integration/restclient-connector/src/main/resources/META-INF/beans.xml b/tests/integration/restclient-connector/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..79fe65a8048 --- /dev/null +++ b/tests/integration/restclient-connector/src/main/resources/META-INF/beans.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/tests/integration/restclient-connector/src/test/java/io/helidon/tests/integration/restclient/connector/TlsTest.java b/tests/integration/restclient-connector/src/test/java/io/helidon/tests/integration/restclient/connector/TlsTest.java new file mode 100644 index 00000000000..2e21aeac8d0 --- /dev/null +++ b/tests/integration/restclient-connector/src/test/java/io/helidon/tests/integration/restclient/connector/TlsTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.tests.integration.restclient.connector; + +import java.net.URI; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import javax.net.ssl.SSLContext; + +import io.helidon.common.context.Contexts; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.jersey.connector.HelidonProperties; +import io.helidon.microprofile.tests.junit5.Configuration; +import io.helidon.microprofile.tests.junit5.HelidonTest; +import io.helidon.tests.integration.resclient.connector.GreetResourceClient; +import io.helidon.tests.integration.resclient.connector.GreetResourceFilter; + +import jakarta.json.JsonObject; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.WebTarget; +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@HelidonTest +@Configuration(configSources = "tls-config.properties") +public class TlsTest { + + @Test + void testHelloWorld(WebTarget target) { + Config config = Config.create(ConfigSources.create(Map.of("tls.server.trust-all", "true"))); + Contexts.globalContext().register(GreetResourceFilter.class, target.getUri()); + + GreetResourceClient client = RestClientBuilder.newBuilder() + .baseUri(URI.create("https://localhost:8080")) + .property(HelidonProperties.CONFIG, config) + .build(GreetResourceClient.class); + + JsonObject defaultMessage = client.getDefaultMessage(); + assertThat(defaultMessage.toString(), is("{\"message\":\"Hello World!\"}")); + } + + @Test + void restClientSslContextPriority(WebTarget target) throws NoSuchAlgorithmException { + Config config = Config.create(ConfigSources.create(Map.of("tls.server.trust-all", "true"))); + Contexts.globalContext().register(GreetResourceFilter.class, target.getUri()); + + GreetResourceClient client = RestClientBuilder.newBuilder() + .baseUri(URI.create("https://localhost:8080")) + .property(HelidonProperties.CONFIG, config) + .sslContext(SSLContext.getDefault()) + .build(GreetResourceClient.class); + + ProcessingException exception = assertThrows(ProcessingException.class, client::getDefaultMessage); + assertThat(exception.getCause(), instanceOf(ExecutionException.class)); + assertThat(exception.getCause().getMessage(), endsWith("unable to find valid certification path to requested target")); + } + + +} diff --git a/tests/integration/restclient-connector/src/test/resources/server.p12 b/tests/integration/restclient-connector/src/test/resources/server.p12 new file mode 100644 index 00000000000..c5e409b4433 Binary files /dev/null and b/tests/integration/restclient-connector/src/test/resources/server.p12 differ diff --git a/tests/integration/restclient-connector/src/test/resources/tls-config.properties b/tests/integration/restclient-connector/src/test/resources/tls-config.properties new file mode 100644 index 00000000000..614d80b6381 --- /dev/null +++ b/tests/integration/restclient-connector/src/test/resources/tls-config.properties @@ -0,0 +1,27 @@ +# +# Copyright (c) 2024 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +server.port=8080 +server.host=0.0.0.0 + +#Truststore setup +server.tls.trust.keystore.resource.resource-path=server.p12 +server.tls.trust.keystore.passphrase=toChange +server.tls.trust.keystore.trust-store=true + +#Keystore with private key and server certificate +server.tls.private-key.keystore.resource.resource-path=server.p12 +server.tls.private-key.keystore.passphrase=toChange \ No newline at end of file