Skip to content

Commit

Permalink
Support & test the case when a remote WSDL URL requires basic authent…
Browse files Browse the repository at this point in the history
…ication fix #969
  • Loading branch information
ppalaga committed Dec 24, 2023
1 parent 0828b98 commit e4bd367
Show file tree
Hide file tree
Showing 14 changed files with 364 additions and 16 deletions.
21 changes: 19 additions & 2 deletions docs/modules/ROOT/pages/includes/quarkus-cxf.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1766,7 +1766,7 @@ a| [[quarkus-cxf_quarkus.cxf.client.-clients-.username]]`link:#quarkus-cxf_quark

[.description]
--
The username for HTTP Basic auth
The username for HTTP Basic authentication

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_CLIENT__CLIENTS__USERNAME+++[]
Expand All @@ -1783,7 +1783,7 @@ a| [[quarkus-cxf_quarkus.cxf.client.-clients-.password]]`link:#quarkus-cxf_quark

[.description]
--
The password for HTTP Basic auth
The password for HTTP Basic authentication

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_CLIENT__CLIENTS__PASSWORD+++[]
Expand All @@ -1795,6 +1795,23 @@ endif::add-copy-button-to-env-var[]
|


a| [[quarkus-cxf_quarkus.cxf.client.-clients-.secure-wsdl-access]]`link:#quarkus-cxf_quarkus.cxf.client.-clients-.secure-wsdl-access[quarkus.cxf.client."clients".secure-wsdl-access]`


[.description]
--
If `true`, then the `Authentication` header will be sent preemptively when requesting the WSDL, as long as the `username` is set; otherwise the WSDL will be requested anonymously.

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_CLIENT__CLIENTS__SECURE_WSDL_ACCESS+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++QUARKUS_CXF_CLIENT__CLIENTS__SECURE_WSDL_ACCESS+++`
endif::add-copy-button-to-env-var[]
--|boolean
|`false`


a| [[quarkus-cxf_quarkus.cxf.client.-clients-.logging.enabled]]`link:#quarkus-cxf_quarkus.cxf.client.-clients-.logging.enabled[quarkus.cxf.client."clients".logging.enabled]`


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
import io.quarkiverse.cxf.CxfFixedConfig;
import io.quarkiverse.cxf.CxfFixedConfig.ClientFixedConfig;
import io.quarkiverse.cxf.HttpClientHTTPConduitFactory;
import io.quarkiverse.cxf.QuarkusHttpConduitConfigurer;
import io.quarkiverse.cxf.annotation.CXFClient;
import io.quarkiverse.cxf.graal.QuarkusCxfFeature;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
Expand Down Expand Up @@ -523,4 +525,10 @@ private ProxyInfo(List<String> interfaces, boolean isRuntimeInitialized) {

}

@BuildStep
void additionalBeans(BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
additionalBeans.produce(
new AdditionalBeanBuildItem(QuarkusHttpConduitConfigurer.class));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ public class CXFClientInfo {

private final SchemaValidationType schemaValidationEnabledFor;

private final boolean secureWsdlAccess;

public CXFClientInfo(CXFClientData other, CxfClientConfig config, String configKey) {
Objects.requireNonNull(config);
this.sei = other.getSei();
Expand All @@ -205,6 +207,7 @@ public CXFClientInfo(CXFClientData other, CxfClientConfig config, String configK
this.epName = config.endpointName().orElse(null);
this.username = config.username().orElse(null);
this.password = config.password().orElse(null);
this.secureWsdlAccess = config.secureWsdlAccess();
this.endpointAddress = config.clientEndpointUrl().orElse(DEFAULT_EP_ADDR + "/" + this.sei.toLowerCase());
this.wsdlUrl = config.wsdlPath().orElse(null);
addFeatures(config);
Expand Down Expand Up @@ -478,4 +481,8 @@ public SchemaValidationType getSchemaValidationEnabledFor() {
return schemaValidationEnabledFor;
}

public boolean isSecureWsdlAccess() {
return secureWsdlAccess;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,24 @@ public interface CxfClientConfig {
public Optional<String> endpointName();

/**
* The username for HTTP Basic auth
* The username for HTTP Basic authentication
*/
public Optional<String> username();

/**
* The password for HTTP Basic auth
* The password for HTTP Basic authentication
*/
public Optional<String> password();

/**
* If {@code true}, then the {@code Authentication} header will be sent preemptively when requesting the WSDL, as
* long as the {@code username} is set; otherwise the WSDL will be requested anonymously.
*
* @since 2.7.0
*/
@WithDefault("false")
public boolean secureWsdlAccess();

/**
* Logging related configuration
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import org.apache.cxf.annotations.SchemaValidation.SchemaValidationType;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
Expand Down Expand Up @@ -66,6 +67,9 @@ public abstract class CxfClientProducer {
@Inject
CxfFixedConfig fixedConfig;

@Inject
QuarkusHttpConduitConfigurer httpConduitConfigurer;

@Inject
@Any
Instance<ClientFactoryCustomizer> customizers;
Expand Down Expand Up @@ -140,6 +144,8 @@ private Object produceCxfClient(CXFClientInfo cxfClientInfo) {
}
QuarkusClientFactoryBean quarkusClientFactoryBean = new QuarkusClientFactoryBean();
QuarkusJaxWsProxyFactoryBean factory = new QuarkusJaxWsProxyFactoryBean(quarkusClientFactoryBean, interfaces);
final Map<String, Object> props = new LinkedHashMap<>();
factory.setProperties(props);
factory.setServiceClass(seiClass);
LOGGER.debugf("using servicename {%s}%s", cxfClientInfo.getWsNamespace(), cxfClientInfo.getWsName());
factory.setServiceName(new QName(cxfClientInfo.getWsNamespace(), cxfClientInfo.getWsName()));
Expand All @@ -153,11 +159,25 @@ private Object produceCxfClient(CXFClientInfo cxfClientInfo) {
if (cxfClientInfo.getWsdlUrl() != null && !cxfClientInfo.getWsdlUrl().isEmpty()) {
factory.setWsdlURL(cxfClientInfo.getWsdlUrl());
}
if (cxfClientInfo.getUsername() != null) {
factory.setUsername(cxfClientInfo.getUsername());
}
if (cxfClientInfo.getPassword() != null) {
factory.setPassword(cxfClientInfo.getPassword());
final String username = cxfClientInfo.getUsername();
if (username != null) {
final String password = cxfClientInfo.getPassword();
final AuthorizationPolicy authPolicy = new AuthorizationPolicy();
authPolicy.setUserName(username);
if (password != null) {
authPolicy.setPassword(password);
}
if (cxfClientInfo.isSecureWsdlAccess()) {
/*
* This is the only way how the AuthorizationPolicy can be set early enough to be effective for the WSDL
* GET request. We do not do it by default because of backwards compatibility and for the user to think
* twice whether his WSDL URL uses HTTPS and only then enable secureWsdlAccess
*/
httpConduitConfigurer.addConfigurer(cxfClientInfo.getEndpointAddress(),
conduit -> conduit.setAuthorization(authPolicy));
} else {
props.put(AuthorizationPolicy.class.getName(), authPolicy);
}
}
final String clientString = "client"
+ (cxfClientInfo.getConfigKey() != null ? (" " + cxfClientInfo.getConfigKey()) : "");
Expand All @@ -171,8 +191,6 @@ private Object produceCxfClient(CXFClientInfo cxfClientInfo) {
factory.getOutFaultInterceptors());
CXFRuntimeUtils.addBeans(cxfClientInfo.getInFaultInterceptors(), "inFaultInterceptor", clientString, sei,
factory.getInFaultInterceptors());
final Map<String, Object> props = new LinkedHashMap<>();
factory.setProperties(props);

final HTTPConduitImpl httpConduitImpl = cxfClientInfo.getHttpConduitImpl();
if (httpConduitImpl != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.quarkiverse.cxf;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;

import org.apache.cxf.BusFactory;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.http.HTTPConduitConfigurer;

/**
* A {@link HTTPConduitConfigurer} able to configure conduits by address.
*/
@ApplicationScoped
public class QuarkusHttpConduitConfigurer implements HTTPConduitConfigurer {
private final Map<String, List<Consumer<HTTPConduit>>> configurersByAddress = new ConcurrentHashMap<>();

@PostConstruct
void init() {
BusFactory.getDefaultBus().setExtension(this, HTTPConduitConfigurer.class);
}

@Override
public void configure(String name, String address, HTTPConduit conduit) {
final List<Consumer<HTTPConduit>> configurers = configurersByAddress.get(address);
if (configurers != null) {
configurers.forEach(configurer -> configurer.accept(conduit));
}
}

/**
* Add a {@code configurer} that will be applied only to the conduit associated with the given {@code address}.
*
* @param address the endpoint address for which the give {@code configurer} should be registered
* @param configurer the {@code configurer} to apply to the conduit associated with the given {@code address}.
*/
public void addConfigurer(String address, Consumer<HTTPConduit> configurer) {
configurersByAddress.compute(address, (k, v) -> {
if (v == null) {
v = new ArrayList<>();
}
v.add(configurer);
return v;
});
}

}
4 changes: 4 additions & 0 deletions integration-tests/client-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-properties-file</artifactId>
</dependency>

<dependency>
<groupId>io.rest-assured</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkiverse.cxf.it;

import jakarta.jws.WebMethod;
import jakarta.jws.WebService;

@WebService(serviceName = "HelloService", targetNamespace = HelloService.NS)
public interface HelloService {
public static final String NS = "https://quarkiverse.github.io/quarkiverse-docs/quarkus-cxf/test";

@WebMethod
public String hello(String person);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkiverse.cxf.it.auth.basic;

import jakarta.annotation.security.RolesAllowed;
import jakarta.jws.WebService;

import io.quarkiverse.cxf.it.HelloService;

@WebService(serviceName = "HelloService", targetNamespace = HelloService.NS)
@RolesAllowed("app-user")
public class BasicAuthHelloServiceImpl implements HelloService {

@Override
public String hello(String person) {
return "Hello " + person + "!";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkiverse.cxf.it.auth.basic;

import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import io.quarkiverse.cxf.annotation.CXFClient;
import io.quarkiverse.cxf.it.HelloService;

@Path("/client-server/basic-auth")
public class BasicAuthResource {

@CXFClient("basicAuth")
HelloService basicAuth;

@CXFClient("basicAuthSecureWsdl")
HelloService basicAuthSecureWsdl;

@POST
@Path("/{client}/hello")
@Produces(MediaType.TEXT_PLAIN)
public Response hello(String body, @PathParam("client") String client) {
final HelloService helloService = switch (client) {
case "basicAuth": {
yield basicAuth;
}
case "basicAuthSecureWsdl": {
yield basicAuthSecureWsdl;
}
default:
throw new IllegalArgumentException("Unexpected client: " + client);
};

try {
return Response.ok(helloService.hello(body)).build();
} catch (Exception e) {
return Response.serverError().entity(e.getMessage()).build();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.quarkiverse.cxf.it.auth.basic;

import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.transport.http.AbstractHTTPDestination;

@ApplicationScoped
@Named("wsdlBasicAuthInterceptor")
public class WsdlBasicAuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> {

public WsdlBasicAuthInterceptor() {
super(Phase.RECEIVE);
}

@Override
public void handleMessage(SoapMessage message) throws Fault {

final HttpServletRequest req = (HttpServletRequest) message.getExchange().getInMessage()
.get(AbstractHTTPDestination.HTTP_REQUEST);
if ("GET".equals(req.getMethod())) {
/* WSDL is the only thing served through GET */
try {
auth();
} catch (io.quarkus.security.UnauthorizedException e) {
handle40x(message, HttpServletResponse.SC_UNAUTHORIZED);
} catch (io.quarkus.security.ForbiddenException e) {
handle40x(message, HttpServletResponse.SC_FORBIDDEN);
}
}
}

private void handle40x(SoapMessage message, int code) {
HttpServletResponse response = (HttpServletResponse) message.getExchange().getInMessage()
.get(AbstractHTTPDestination.HTTP_RESPONSE);
response.setStatus(code);
message.getInterceptorChain().abort();
}

@RolesAllowed("app-user")
void auth() {

}
}
Loading

0 comments on commit e4bd367

Please sign in to comment.