From 07f7c885967bd950e16cb586e2a7279d7c7c7d59 Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Fri, 19 Aug 2022 14:56:14 +0200 Subject: [PATCH] Test WS Security client #498 --- integration-tests/pom.xml | 1 + .../ws-security-client/README.adoc | 29 ++++ integration-tests/ws-security-client/pom.xml | 130 ++++++++++++++++++ .../wsdl/WssCalculatorService.wsdl | 57 ++++++++ .../it/wss/client/CxfWssClientResource.java | 28 ++++ .../client/WSS4JOutInterceptorProducer.java | 58 ++++++++ .../src/main/resources/application.properties | 6 + .../cxf/it/wss/client/CxfWssClientIT.java | 8 ++ .../cxf/it/wss/client/CxfWssClientTest.java | 71 ++++++++++ .../wss/client/CxfWssClientTestResource.java | 54 ++++++++ 10 files changed, 442 insertions(+) create mode 100644 integration-tests/ws-security-client/README.adoc create mode 100644 integration-tests/ws-security-client/pom.xml create mode 100644 integration-tests/ws-security-client/src/main/cxf-codegen-resources/wsdl/WssCalculatorService.wsdl create mode 100644 integration-tests/ws-security-client/src/main/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientResource.java create mode 100644 integration-tests/ws-security-client/src/main/java/io/quarkiverse/cxf/it/wss/client/WSS4JOutInterceptorProducer.java create mode 100644 integration-tests/ws-security-client/src/main/resources/application.properties create mode 100644 integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientIT.java create mode 100644 integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientTest.java create mode 100644 integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientTestResource.java diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index dad29fee5..26db3cb4a 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -17,6 +17,7 @@ client server logging + ws-security-client diff --git a/integration-tests/ws-security-client/README.adoc b/integration-tests/ws-security-client/README.adoc new file mode 100644 index 000000000..15ed2c3a2 --- /dev/null +++ b/integration-tests/ws-security-client/README.adoc @@ -0,0 +1,29 @@ += Quarkus CXF client tests + +These are pure client tests - i.e. there are intentionally no services implemented in the test application. +All clients access services running in containers. + +== Maintenenance notes + +=== `CalculatorService.wsdl` + +`src/main/cxf-codegen-resources/wsdl/CalculatorService.wsdl` is a static copy of the WSDL served by the testing container. +It is used solely by `cxf-codegen-plugin`. +It would be too complicated to start the container before running the plugin, so we rather keep the static copy. + +There is `io.quarkiverse.cxf.client.it.CxfClientTest.wsdlUpToDate()` to ensure that it is up to date. + +To update `CalculatorService.wsdl` manually, first start the container + +[shource,shell] +---- +$ docker pull quay.io/l2x6/calculator-ws:1.0 +$ docker run -p 8080:8080 quay.io/l2x6/calculator-ws:1.0 +---- + +And then overwrite the existing file with the new content from the container: + +[shource,shell] +---- +curl http://localhost:8080/calculator-ws/CalculatorService?wsdl --output src/main/cxf-codegen-resources/wsdl/CalculatorService.wsdl +---- diff --git a/integration-tests/ws-security-client/pom.xml b/integration-tests/ws-security-client/pom.xml new file mode 100644 index 000000000..0fa507091 --- /dev/null +++ b/integration-tests/ws-security-client/pom.xml @@ -0,0 +1,130 @@ + + + 4.0.0 + + io.quarkiverse.cxf + quarkus-cxf-integration-tests + 1.5.0-SNAPSHOT + ../pom.xml + + + quarkus-cxf-integration-test-ws-security-client + + Quarkus CXF Extension - Integration Test - WS Security Client + + + + io.quarkiverse.cxf + quarkus-cxf + + + io.quarkiverse.cxf + quarkus-cxf-rt-ws-security + + + io.quarkus + quarkus-resteasy + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.assertj + assertj-core + test + + + org.testcontainers + testcontainers + test + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock + test + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + org.apache.cxf + cxf-codegen-plugin + + + + wsdl2java + + + + + ${basedir}/src/main/cxf-codegen-resources/wsdl/WssCalculatorService.wsdl + + + + + + + + + + + + native + + false + + + native + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/ws-security-client/src/main/cxf-codegen-resources/wsdl/WssCalculatorService.wsdl b/integration-tests/ws-security-client/src/main/cxf-codegen-resources/wsdl/WssCalculatorService.wsdl new file mode 100644 index 000000000..46fe5d994 --- /dev/null +++ b/integration-tests/ws-security-client/src/main/cxf-codegen-resources/wsdl/WssCalculatorService.wsdl @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/ws-security-client/src/main/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientResource.java b/integration-tests/ws-security-client/src/main/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientResource.java new file mode 100644 index 000000000..fd8f6b2e6 --- /dev/null +++ b/integration-tests/ws-security-client/src/main/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientResource.java @@ -0,0 +1,28 @@ +package io.quarkiverse.cxf.it.wss.client; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.jboss.eap.quickstarts.wscalculator.wsscalculator.WssCalculatorService; + +import io.quarkiverse.cxf.annotation.CXFClient; + +@Path("/cxf/wss-client") +public class CxfWssClientResource { + + @Inject + @CXFClient("wss-client") // name used in application.properties + WssCalculatorService calculator; + + @GET + @Path("/calculator/modulo") + @Produces(MediaType.TEXT_PLAIN) + public int modulo(@QueryParam("a") int a, @QueryParam("b") int b) { + return calculator.modulo(a, b); + } + +} diff --git a/integration-tests/ws-security-client/src/main/java/io/quarkiverse/cxf/it/wss/client/WSS4JOutInterceptorProducer.java b/integration-tests/ws-security-client/src/main/java/io/quarkiverse/cxf/it/wss/client/WSS4JOutInterceptorProducer.java new file mode 100644 index 000000000..0cb75c0a7 --- /dev/null +++ b/integration-tests/ws-security-client/src/main/java/io/quarkiverse/cxf/it/wss/client/WSS4JOutInterceptorProducer.java @@ -0,0 +1,58 @@ +package io.quarkiverse.cxf.it.wss.client; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor; +import org.apache.wss4j.common.ConfigurationConstants; +import org.apache.wss4j.common.ext.WSPasswordCallback; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.arc.Unremovable; + +@ApplicationScoped +public class WSS4JOutInterceptorProducer { + + /** Produced in CxfWssClientTestResource */ + @ConfigProperty(name = "wss.username") + String username; + + /** Produced in CxfWssClientTestResource */ + @ConfigProperty(name = "wss.password") + String password; + + @Produces + @Unremovable + @ApplicationScoped + WSS4JOutInterceptor wssInterceptor() { + + final CallbackHandler passwordCallback = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof WSPasswordCallback) { + ((WSPasswordCallback) callback).setPassword(password); + break; + } + } + } + }; + + final Map props = new HashMap<>(); + props.put(ConfigurationConstants.ACTION, "UsernameToken"); + props.put(ConfigurationConstants.PASSWORD_TYPE, "PasswordText"); + props.put(ConfigurationConstants.USER, username); + props.put(ConfigurationConstants.PW_CALLBACK_REF, passwordCallback); + props.put(ConfigurationConstants.ADD_USERNAMETOKEN_NONCE, "true"); + props.put(ConfigurationConstants.ADD_USERNAMETOKEN_CREATED, "true"); + return new WSS4JOutInterceptor(props); + } + +} diff --git a/integration-tests/ws-security-client/src/main/resources/application.properties b/integration-tests/ws-security-client/src/main/resources/application.properties new file mode 100644 index 000000000..cb67955eb --- /dev/null +++ b/integration-tests/ws-security-client/src/main/resources/application.properties @@ -0,0 +1,6 @@ +quarkus.cxf.client."wss-client".wsdl=${cxf.it.calculator.baseUri}/calculator-ws/WssCalculatorService?wsdl +quarkus.cxf.client."wss-client".client-endpoint-url=${cxf.it.calculator.baseUri}/calculator-ws/WssCalculatorService +quarkus.cxf.client."wss-client".service-interface=org.jboss.as.quickstarts.wsscalculator.WssCalculatorService +quarkus.cxf.client."wss-client".endpoint-namespace=http://www.jboss.org/eap/quickstarts/wscalculator/WssCalculator +quarkus.cxf.client."wss-client".endpoint-name=WssCalculator +quarkus.cxf.client."wss-client".out-interceptors=org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor diff --git a/integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientIT.java b/integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientIT.java new file mode 100644 index 000000000..58e37324f --- /dev/null +++ b/integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientIT.java @@ -0,0 +1,8 @@ +package io.quarkiverse.cxf.it.wss.client; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class CxfWssClientIT extends CxfWssClientTest { + +} diff --git a/integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientTest.java b/integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientTest.java new file mode 100644 index 000000000..4042c1e6f --- /dev/null +++ b/integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientTest.java @@ -0,0 +1,71 @@ +package io.quarkiverse.cxf.it.wss.client; + +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.assertj.core.api.Assertions; +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +@QuarkusTest +@QuarkusTestResource(CxfWssClientTestResource.class) +public class CxfWssClientTest { + + /** + * Test whether the traffic logging is present in Quarkus log file + * + * @throws IOException + */ + @Test + void loggingClient() throws IOException { + + /* Now perform a call that should log something */ + RestAssured.given() + .queryParam("a", 12) + .queryParam("b", 8) + .get("/cxf/wss-client/calculator/modulo") + .then() + .statusCode(200) + .body(is("4")); + } + + /** + * Make sure that our static copy is the same as the WSDL served by the container + * + * @throws IOException + */ + @Test + void wsdlUpToDate() throws IOException { + final String wsdlUrl = ConfigProvider.getConfig() + .getValue("quarkus.cxf.client.\"wss-client\".wsdl", String.class); + + final String staticCopyPath = "src/main/cxf-codegen-resources/wsdl/WssCalculatorService.wsdl"; + /* The changing Docker IP address in the WSDL should not matter */ + final String sanitizerRegex = ""; + final String staticCopyContent = Files + .readString(Paths.get(staticCopyPath), StandardCharsets.UTF_8) + .replaceAll(sanitizerRegex, ""); + + final String expected = RestAssured.given() + .get(wsdlUrl) + .then() + .statusCode(200) + .extract().body().asString(); + + if (!expected.replaceAll(sanitizerRegex, "").equals(staticCopyContent)) { + Files.writeString(Paths.get(staticCopyPath), expected, StandardCharsets.UTF_8); + Assertions.fail("The static WSDL copy in " + staticCopyPath + + " went out of sync with the WSDL served by the container. The content was updated by the test, you just need to review and commit the changes."); + } + + } + +} diff --git a/integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientTestResource.java b/integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientTestResource.java new file mode 100644 index 000000000..001b69f95 --- /dev/null +++ b/integration-tests/ws-security-client/src/test/java/io/quarkiverse/cxf/it/wss/client/CxfWssClientTestResource.java @@ -0,0 +1,54 @@ +package io.quarkiverse.cxf.it.wss.client; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class CxfWssClientTestResource implements QuarkusTestResourceLifecycleManager { + private static final Logger log = LoggerFactory.getLogger(CxfWssClientTestResource.class); + + private static final int WILDFLY_PORT = 8080; + private GenericContainer calculatorContainer; + + @Override + public Map start() { + + final String user = "cxf-user"; + final String password = "secret-password"; + try { + calculatorContainer = new GenericContainer<>("quay.io/l2x6/calculator-ws:1.0") + .withEnv("WSS_USER", user) + .withEnv("WSS_PASSWORD", password) + .withLogConsumer(new Slf4jLogConsumer(log)) + .withExposedPorts(WILDFLY_PORT) + .waitingFor(Wait.forHttp("/calculator-ws/CalculatorService?wsdl")); + + calculatorContainer.start(); + + return Map.of( + "cxf.it.calculator.baseUri", + "http://" + calculatorContainer.getHost() + ":" + calculatorContainer.getMappedPort(WILDFLY_PORT), + "wss.username", user, + "wss.password", password); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void stop() { + try { + if (calculatorContainer != null) { + calculatorContainer.stop(); + } + } catch (Exception e) { + // ignored + } + } +}