package org.mqnaas.client.cxf;
/*
* #%L
* MQNaaS :: Client Provider
* %%
* Copyright (C) 2007 - 2015 Fundació Privada i2CAT, Internet i Innovació a Catalunya
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.KeyStore;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.apache.cxf.common.util.ProxyClassLoader;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.mqnaas.clientprovider.api.apiclient.IInternalAPIClientProvider;
import org.mqnaas.clientprovider.exceptions.ClientConfigurationException;
import org.mqnaas.core.api.Endpoint;
import org.mqnaas.core.api.credentials.Credentials;
import org.mqnaas.core.api.credentials.TrustoreKeystoreCredentials;
import org.mqnaas.core.api.credentials.UsernamePasswordCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is an example implementation of how to implement a specific api provider
*
* @param <CC>
* Specific {@link CXFConfiguration} for the client being created. The goal of this parameterization is to allow specific client providers
* extending this class to initialize client configuration by extending the {@link CXFConfiguration} class.
*
* @author Adrian Rosello Rey (i2CAT) - authentication and async http conduit.
*
*/
public class InternalCXFClientProvider<CC extends CXFConfiguration> implements IInternalAPIClientProvider<CC> {
@Override
public String[] getProtocols() {
// HTTP and secured HTTP endpoints
return new String[] { "http", "https" };
}
private static final Logger log = LoggerFactory.getLogger(InternalCXFClientProvider.class);
@Override
public <API> API getClient(Class<API> apiClass, Endpoint ep, Credentials c) throws ClientConfigurationException {
return getClient(apiClass, ep, c, null, null);
}
@Override
public <API> API getClient(Class<API> apiClass, Endpoint ep, Credentials c, CC configuration) throws ClientConfigurationException {
return getClient(apiClass, ep, c, configuration, null);
}
@Override
public <API> API getClient(Class<API> apiClass, Endpoint ep, Credentials c, CC configuration,
Object applicationSpecificConfiguration) throws ClientConfigurationException {
if (configuration != null) {
if (configuration.getUseDummyClient())
return createDummyClient(apiClass);
}
if (ep == null || ep.getUri() == null) {
// FIXME fail gracefully
log.warn("Attempt to create JAX-RS client without target address. Using dummyClient instead");
return createDummyClient(apiClass);
}
// String switchId = (String) sessionContext.getSessionParameters().get(FloodlightProtocolSession.SWITCHID_CONTEXT_PARAM_NAME);
// TODO use switch id to instantiate the client
// create CXF client
ProxyClassLoader classLoader = new ProxyClassLoader(apiClass.getClassLoader());
classLoader.addLoader(JAXRSClientFactoryBean.class.getClassLoader());
JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
bean.setAddress(ep.getUri().toString());
if (configuration != null && !configuration.getProviders().isEmpty())
bean.setProviders(configuration.getProviders());
bean.setResourceClass(apiClass);
bean.setClassLoader(classLoader);
API api = bean.create(apiClass);
// disable CN check
if (configuration != null && !configuration.isCNCheckEnabled()) {
TLSClientParameters clientParams = new TLSClientParameters();
clientParams.setDisableCNCheck(true);
WebClient.getConfig(api).getHttpConduit().setTlsClientParameters(clientParams);
}
// authentication system
if (c != null) {
if (c instanceof TrustoreKeystoreCredentials) {
// get TLSClientParameters from cxf bean. If not exists, create it
TLSClientParameters clientParams = WebClient.getConfig(api).getHttpConduit().getTlsClientParameters();
fillClientParamsWithTrustoreKeystoreInformation(clientParams, (TrustoreKeystoreCredentials) c);
// set tlsclientparameters with certificates information into cxf bean
WebClient.getConfig(api).getHttpConduit().setTlsClientParameters(clientParams);
} else if (c instanceof UsernamePasswordCredentials) {
bean.setUsername(((UsernamePasswordCredentials) c).getUsername());
bean.setPassword(((UsernamePasswordCredentials) c).getPassword());
api = bean.create(apiClass);
}
else {
log.warn("Unkown credentials type. Ignoring it. Client won't contain any authentication information.");
}
}
if (configuration != null && configuration.isUsingAsyncHttpConduit())
// By enabling async http conduit, as side-effect, support for @Delete methods with body is available.
// https://issues.apache.org/jira/browse/CXF-5337
WebClient.getConfig(api).getRequestContext().put("use.async.http.conduit", true);
return api;
}
/**
* Sets the {@link TrustoreKeystoreCredentials} information into the list of trustore and keystore of the {@link TLSClientParameters} object.
*
* @param clientParams
* Current TSL client parameters of the web client.
* @param credentials
* Credentials containing the keystore uri and password, as well as the trustore file uri and password.
*
* @throws ClientConfigurationException
* If trustore and keystore list of the <code>clientParams</code> could not be instantiated and/or updated.
*/
private void fillClientParamsWithTrustoreKeystoreInformation(TLSClientParameters clientParams, TrustoreKeystoreCredentials credentials)
throws ClientConfigurationException {
FileInputStream keystoreFis = null;
FileInputStream truststoreFis = null;
try {
if (clientParams == null)
clientParams = new TLSClientParameters();
// load keystore
KeyStore keyStore = KeyStore.getInstance("JKS");
File keyStoreFile = new File(credentials.getKeystoreUri().toString());
keystoreFis = new FileInputStream(keyStoreFile);
keyStore.load(keystoreFis, credentials.getKeystorePassword().toCharArray());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore, credentials.getKeystorePassword().toCharArray());
KeyManager[] km = keyFactory.getKeyManagers();
clientParams.setKeyManagers(km);
// load truststore
File truststore = new File(credentials.getTrustoreUri().toString());
truststoreFis = new FileInputStream(truststore);
keyStore.load(truststoreFis, credentials.getTrustorePassword().toCharArray());
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(keyStore);
TrustManager[] tm = trustFactory.getTrustManagers();
clientParams.setTrustManagers(tm);
} catch (Exception e) {
log.error("Error creating CXF client with Trustore/Keystore authentication.", e);
throw new ClientConfigurationException(e);
} finally {
try {
if (keystoreFis != null)
keystoreFis.close();
if (truststoreFis != null)
truststoreFis.close();
} catch (IOException e) {
log.warn("Failed to close FileInputStream.", e);
}
}
}
private <API> API createDummyClient(Class<API> apiClass) {
ProxyClassLoader classLoader = new ProxyClassLoader(apiClass.getClassLoader());
// It is safe to cast returned proxy to one of the interfaces given to newProxyInstance method, according to its contract:
// Proxy.newProxyInstance javadoc:
// @return a proxy instance with the specified invocation handler of a
// proxy class that is defined by the specified class loader
// and that implements the specified interfaces
@SuppressWarnings("unchecked")
API dummyClient = (API) Proxy.newProxyInstance(classLoader, new Class[] { apiClass }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
log.debug("Invoking cxf api method " + method + " with args " + args);
return null;
}
});
return dummyClient;
}
}