diff --git a/docs/modules/ROOT/examples/calculator-client/application.properties b/docs/modules/ROOT/examples/calculator-client/application.properties index 85cac11f9..9a25f82f9 100644 --- a/docs/modules/ROOT/examples/calculator-client/application.properties +++ b/docs/modules/ROOT/examples/calculator-client/application.properties @@ -45,3 +45,10 @@ quarkus.cxf.client.clientWithRuntimeInitializedPayload.native.runtime-initialize quarkus.native.additional-build-args = --initialize-at-run-time=io.quarkiverse.cxf.client.it.rtinit.Operands\\,io.quarkiverse.cxf.client.it.rtinit.Result quarkus.cxf.codegen.wsdl2java.includes = wsdl/*.wsdl + +quarkus.cxf.client.proxiedCalculator.wsdl = wsdl/CalculatorService.wsdl +quarkus.cxf.client.proxiedCalculator.client-endpoint-url = ${cxf.it.calculator.hostNameUri}/calculator-ws/CalculatorService +quarkus.cxf.client.proxiedCalculator.proxy-server = ${cxf.it.calculator.proxy.host} +quarkus.cxf.client.proxiedCalculator.proxy-server-port = ${cxf.it.calculator.proxy.port} +quarkus.cxf.client.proxiedCalculator.proxy-username = ${cxf.it.calculator.proxy.user} +quarkus.cxf.client.proxiedCalculator.proxy-password = ${cxf.it.calculator.proxy.password} diff --git a/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc b/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc index bb516f51a..4107e6922 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc @@ -1295,4 +1295,38 @@ endif::add-copy-button-to-env-var[] `http`, `socks` |`http` + +a| [[quarkus-cxf_quarkus.cxf.client.-clients-.proxy-username]]`link:#quarkus-cxf_quarkus.cxf.client.-clients-.proxy-username[quarkus.cxf.client."clients".proxy-username]` + + +[.description] +-- +Username for the proxy authentication + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_CLIENT__CLIENTS__PROXY_USERNAME+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_CXF_CLIENT__CLIENTS__PROXY_USERNAME+++` +endif::add-copy-button-to-env-var[] +--|string +| + + +a| [[quarkus-cxf_quarkus.cxf.client.-clients-.proxy-password]]`link:#quarkus-cxf_quarkus.cxf.client.-clients-.proxy-password[quarkus.cxf.client."clients".proxy-password]` + + +[.description] +-- +Password for the proxy authentication + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_CLIENT__CLIENTS__PROXY_PASSWORD+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_CXF_CLIENT__CLIENTS__PROXY_PASSWORD+++` +endif::add-copy-button-to-env-var[] +--|string +| + |=== \ No newline at end of file diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java index a75ab06e0..91d8f4a9e 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java @@ -152,6 +152,14 @@ public class CXFClientInfo { * Specifies the type of the proxy server. Can be either HTTP or SOCKS. */ private ProxyServerType proxyServerType; + /** + * Username for the proxy authentication + */ + private String proxyUsername; + /** + * Password for the proxy authentication + */ + private String proxyPassword; public CXFClientInfo() { } @@ -212,6 +220,8 @@ public CXFClientInfo(CXFClientInfo other) { this.proxyServerPort = other.proxyServerPort; this.nonProxyHosts = other.nonProxyHosts; this.proxyServerType = other.proxyServerType; + this.proxyUsername = other.proxyUsername; + this.proxyPassword = other.proxyPassword; } public CXFClientInfo withConfig(CxfClientConfig config) { @@ -248,6 +258,8 @@ public CXFClientInfo withConfig(CxfClientConfig config) { this.proxyServerPort = config.proxyServerPort.orElse(null); this.nonProxyHosts = config.nonProxyHosts.orElse(null); this.proxyServerType = config.proxyServerType; + this.proxyUsername = config.proxyUsername.orElse(null); + this.proxyPassword = config.proxyPassword.orElse(null); return this; } @@ -440,4 +452,12 @@ public String getNonProxyHosts() { public ProxyServerType getProxyServerType() { return proxyServerType; } + + public String getProxyUsername() { + return proxyUsername; + } + + public String getProxyPassword() { + return proxyPassword; + } } diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java index e8ff79357..14f2516c6 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java @@ -225,22 +225,22 @@ public class CxfClientConfig { * also be used to optimize for different SOAP stacks. */ @ConfigItem - protected Optional browserType; + public Optional browserType; /** * Specifies the URL of a decoupled endpoint for the receipt of responses over a separate provider->consumer connection. */ @ConfigItem - protected Optional decoupledEndpoint; + public Optional decoupledEndpoint; /** * Specifies the address of proxy server if one is used. */ @ConfigItem - protected Optional proxyServer; + public Optional proxyServer; /** * Specifies the port number used by the proxy server. */ @ConfigItem - protected Optional proxyServerPort; + public Optional proxyServerPort; /** * Specifies the list of hostnames that will not use the proxy configuration. * Examples of value: @@ -249,11 +249,21 @@ public class CxfClientConfig { * * "localhost|www.google.*|*.apache.org" -> It's also possible to use a pattern-like value */ @ConfigItem - protected Optional nonProxyHosts; + public Optional nonProxyHosts; /** * Specifies the type of the proxy server. Can be either HTTP or SOCKS. */ @ConfigItem(defaultValue = "HTTP") - protected ProxyServerType proxyServerType; + public ProxyServerType proxyServerType; + /** + * Username for the proxy authentication + */ + @ConfigItem + public Optional proxyUsername; + /** + * Password for the proxy authentication + */ + @ConfigItem + public Optional proxyPassword; } diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java index 0ae647b44..0832056cb 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java @@ -14,6 +14,7 @@ import jakarta.xml.ws.BindingProvider; import jakarta.xml.ws.handler.Handler; +import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy; import org.apache.cxf.endpoint.Client; import org.apache.cxf.feature.Feature; import org.apache.cxf.frontend.ClientProxy; @@ -208,6 +209,16 @@ private Object produceCxfClient(CXFClientInfo cxfClientInfo) { } } policy.setProxyServerType(cxfClientInfo.getProxyServerType()); + + final String proxyUsername = cxfClientInfo.getProxyUsername(); + if (proxyUsername != null) { + final String proxyPassword = cxfClientInfo.getProxyPassword(); + final ProxyAuthorizationPolicy proxyAuth = new ProxyAuthorizationPolicy(); + proxyAuth.setUserName(proxyUsername); + proxyAuth.setPassword(proxyPassword); + httpConduit.setProxyAuthorization(proxyAuth); + } + return result; } diff --git a/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/CxfClientResource.java b/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/CxfClientResource.java index 207311c21..093099093 100644 --- a/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/CxfClientResource.java +++ b/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/CxfClientResource.java @@ -57,6 +57,9 @@ public class CxfClientResource { @CXFClient("clientWithRuntimeInitializedPayload") // name used in application.properties ClientWithRuntimeInitializedPayload clientWithRuntimeInitializedPayload; + @CXFClient("proxiedCalculator") + CalculatorService proxiedCalculator; + @GET @Path("/calculator/{client}/multiply") @Produces(MediaType.TEXT_PLAIN) @@ -130,6 +133,8 @@ private CalculatorService getClient(String client) { return myFaultyCalculator; case "mySkewedCalculator": return mySkewedCalculator; + case "proxiedCalculator": + return proxiedCalculator; default: throw new IllegalStateException("Unexpected client key " + client); } diff --git a/integration-tests/client/src/main/resources/application.properties b/integration-tests/client/src/main/resources/application.properties index 85cac11f9..9a25f82f9 100644 --- a/integration-tests/client/src/main/resources/application.properties +++ b/integration-tests/client/src/main/resources/application.properties @@ -45,3 +45,10 @@ quarkus.cxf.client.clientWithRuntimeInitializedPayload.native.runtime-initialize quarkus.native.additional-build-args = --initialize-at-run-time=io.quarkiverse.cxf.client.it.rtinit.Operands\\,io.quarkiverse.cxf.client.it.rtinit.Result quarkus.cxf.codegen.wsdl2java.includes = wsdl/*.wsdl + +quarkus.cxf.client.proxiedCalculator.wsdl = wsdl/CalculatorService.wsdl +quarkus.cxf.client.proxiedCalculator.client-endpoint-url = ${cxf.it.calculator.hostNameUri}/calculator-ws/CalculatorService +quarkus.cxf.client.proxiedCalculator.proxy-server = ${cxf.it.calculator.proxy.host} +quarkus.cxf.client.proxiedCalculator.proxy-server-port = ${cxf.it.calculator.proxy.port} +quarkus.cxf.client.proxiedCalculator.proxy-username = ${cxf.it.calculator.proxy.user} +quarkus.cxf.client.proxiedCalculator.proxy-password = ${cxf.it.calculator.proxy.password} diff --git a/integration-tests/client/src/test/java/io/quarkiverse/cxf/client/it/CxfClientTest.java b/integration-tests/client/src/test/java/io/quarkiverse/cxf/client/it/CxfClientTest.java index d22e99ed6..39f3f3dcb 100644 --- a/integration-tests/client/src/test/java/io/quarkiverse/cxf/client/it/CxfClientTest.java +++ b/integration-tests/client/src/test/java/io/quarkiverse/cxf/client/it/CxfClientTest.java @@ -56,6 +56,22 @@ void multiply(String clientKey) { .body(is(String.valueOf(expected))); } + /** + * Test whether a client with proxy and proxy auth set works properly + * + * @param clientKey + */ + @Test + void multiplyProxy() { + RestAssured.given() + .queryParam("a", 4) + .queryParam("b", 5) + .get("/cxf/client/calculator/proxiedCalculator/multiply") + .then() + .statusCode(200) + .body(is(String.valueOf(20))); + } + /** * Test whether passing a complex object to the client and receiving a complex object from the client works properly * diff --git a/integration-tests/client/src/test/java/io/quarkiverse/cxf/client/it/CxfClientTestResource.java b/integration-tests/client/src/test/java/io/quarkiverse/cxf/client/it/CxfClientTestResource.java index 86a51c23e..a4dc0afdd 100644 --- a/integration-tests/client/src/test/java/io/quarkiverse/cxf/client/it/CxfClientTestResource.java +++ b/integration-tests/client/src/test/java/io/quarkiverse/cxf/client/it/CxfClientTestResource.java @@ -17,29 +17,42 @@ package io.quarkiverse.cxf.client.it; +import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; import org.testcontainers.containers.wait.strategy.Wait; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; public class CxfClientTestResource implements QuarkusTestResourceLifecycleManager { - private static final int WILDFLY_PORT = 8080; + private static final int PROXY_PORT = 8080; + private static final String SOAP_SERVICE_HOST = "calculator-service"; + private static final int SOAP_SERVICE_PORT = 8080; + private GenericContainer proxy; private GenericContainer calculatorContainer; + private Network network; private GenericContainer skewedCalculatorContainer; @Override public Map start() { + network = Network.newNetwork(); + + final String PROXY_USER = "proxyuser"; + final String PROXY_PASSWORD = UUID.randomUUID().toString(); + final String BASIC_AUTH_USER = "tester"; final String BASIC_AUTH_PASSWORD = UUID.randomUUID().toString(); try { calculatorContainer = new GenericContainer<>("quay.io/l2x6/calculator-ws:1.3") - .withExposedPorts(WILDFLY_PORT) + .withExposedPorts(SOAP_SERVICE_PORT) + .withNetworkAliases(SOAP_SERVICE_HOST) + .withNetwork(network) .withEnv("BASIC_AUTH_USER", BASIC_AUTH_USER) .withEnv("BASIC_AUTH_PASSWORD", BASIC_AUTH_PASSWORD) .waitingFor(Wait.forHttp("/calculator-ws/CalculatorService?wsdl")); @@ -48,19 +61,37 @@ public Map start() { skewedCalculatorContainer = new GenericContainer<>("quay.io/l2x6/calculator-ws:1.3") .withEnv("ADD_TO_RESULT", "100") - .withExposedPorts(WILDFLY_PORT) + .withExposedPorts(SOAP_SERVICE_PORT) .waitingFor(Wait.forHttp("/calculator-ws/CalculatorService?wsdl")); skewedCalculatorContainer.start(); - return Map.of( - "cxf.it.calculator.baseUri", - "http://" + calculatorContainer.getHost() + ":" + calculatorContainer.getMappedPort(WILDFLY_PORT), - "cxf.it.skewed-calculator.baseUri", + proxy = new GenericContainer<>("mitmproxy/mitmproxy:10.0.0") + .withCommand("mitmdump", "--set", "proxyauth=" + PROXY_USER + ":" + PROXY_PASSWORD) + .withExposedPorts(PROXY_PORT) + .withNetwork(network) + .waitingFor(Wait.forListeningPort()); + proxy.start(); + + final Map props = new LinkedHashMap<>(); + props.put("cxf.it.calculator.baseUri", + "http://" + calculatorContainer.getHost() + ":" + calculatorContainer.getMappedPort(SOAP_SERVICE_PORT)); + props.put("cxf.it.skewed-calculator.baseUri", "http://" + skewedCalculatorContainer.getHost() + ":" - + skewedCalculatorContainer.getMappedPort(WILDFLY_PORT), - "cxf.it.calculator.auth.basic.user", BASIC_AUTH_USER, - "cxf.it.calculator.auth.basic.password", BASIC_AUTH_PASSWORD); + + skewedCalculatorContainer.getMappedPort(SOAP_SERVICE_PORT)); + props.put("cxf.it.calculator.auth.basic.user", BASIC_AUTH_USER); + props.put("cxf.it.calculator.auth.basic.password", BASIC_AUTH_PASSWORD); + + props.put( + "cxf.it.calculator.hostNameUri", + "http://" + SOAP_SERVICE_HOST + ":" + SOAP_SERVICE_PORT); + + props.put("cxf.it.calculator.proxy.host", proxy.getHost()); + props.put("cxf.it.calculator.proxy.port", String.valueOf(proxy.getMappedPort(PROXY_PORT))); + props.put("cxf.it.calculator.proxy.user", PROXY_USER); + props.put("cxf.it.calculator.proxy.password", PROXY_PASSWORD); + + return props; } catch (Exception e) { throw new RuntimeException(e); } @@ -82,5 +113,19 @@ public void stop() { } catch (Exception e) { // ignored } + try { + if (proxy != null) { + proxy.stop(); + } + } catch (Exception e) { + // ignored + } + try { + if (network != null) { + network.close(); + } + } catch (Exception e) { + // ignored + } } }