/** * Copyright (c) Codice Foundation * * This 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 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 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. * **/ package org.codice.ddf.spatial.ogc.catalog.common; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.lang.StringUtils; import org.apache.cxf.Bus; import org.apache.cxf.BusException; import org.apache.cxf.common.util.CollectionUtils; import org.apache.cxf.configuration.jsse.TLSClientParameters; import org.apache.cxf.endpoint.EndpointException; import org.apache.cxf.interceptor.Interceptor; import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; import org.apache.cxf.jaxrs.client.Client; import org.apache.cxf.jaxrs.client.ClientConfiguration; import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.cxf.message.Message; import org.apache.cxf.transport.http.HTTPConduit; import org.apache.cxf.transport.https.HttpsURLConnectionFactory; import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; import org.apache.cxf.ws.security.SecurityConstants; import org.apache.cxf.ws.security.trust.STSClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.security.PropertiesLoader; import ddf.security.service.SecurityManager; import ddf.security.settings.SecuritySettingsService; import ddf.security.sts.client.configuration.STSClientConfiguration; public abstract class TrustedRemoteSource { public static final String DISABLE_CN_CHECK_PROPERTY = "disableCnCheck"; public static final Integer DEFAULT_CONNECTION_TIMEOUT = 30000; public static final Integer DEFAULT_RECEIVE_TIMEOUT = 60000; protected static final String ADDRESSING_NAMESPACE = "http://www.w3.org/2005/08/addressing"; protected static final int HTTP_STATUS_CODE_OK = 200; protected static final int CONNECTION_TIMEOUT_INTERVAL = 3000; protected static final String QUESTION_MARK_WSDL = "?wsdl"; protected static final String DOT_WSDL = ".wsdl"; private static final Logger LOGGER = LoggerFactory.getLogger(TrustedRemoteSource.class); protected HashMap<String, String> wsdlSuffixMap = new HashMap<String, String>(); protected SecuritySettingsService securitySettingsService; protected SecurityManager securityManager; /** * Configures the connection and receive timeouts. If any of the parameters are null, the timeouts * will be set to the system default. * * @param client Client used for outgoing requests. * @param connectionTimeout Connection timeout in milliseconds. * @param receiveTimeout Receive timeout in milliseconds. */ protected void configureTimeouts(Client client, Integer connectionTimeout, Integer receiveTimeout) { ClientConfiguration clientConfiguration = WebClient.getConfig(client); HTTPConduit httpConduit = clientConfiguration.getHttpConduit(); if (httpConduit == null) { LOGGER.info("HTTPConduit was null for {}. Unable to configure timeouts", client); return; } HTTPClientPolicy httpClientPolicy = httpConduit.getClient(); if (httpClientPolicy == null) { httpClientPolicy = new HTTPClientPolicy(); } if (connectionTimeout != null) { httpClientPolicy.setConnectionTimeout(connectionTimeout); } else { httpClientPolicy.setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT); } if (receiveTimeout != null) { httpClientPolicy.setReceiveTimeout(receiveTimeout); } else { httpClientPolicy.setReceiveTimeout(DEFAULT_RECEIVE_TIMEOUT); } if (httpClientPolicy.isSetConnectionTimeout()) { LOGGER.debug("Connection timeout has been set."); } else { LOGGER.error("Connection timeout has NOT been set."); } if (httpClientPolicy.isSetReceiveTimeout()) { LOGGER.debug("Receive timeout has been set."); } else { LOGGER.error("Receive timeout has NOT been set."); } httpConduit.setClient(httpClientPolicy); } /** * Creates the JAX-RS client based the information provided. * * @param clazz * - the interface this client implements * @param url * - the URL of the server to connect to * @param username * - username for basic auth (can be null or empty) * @param password * - password for basic auth (can be null or empty) * @param disableCnCheck * - flag to disable CN check when using HTTPS (Should only be used for testing) * @param providers * - list of providers * @param classLoader * - the classloader of the client. Ensures the interface is on the classpath. * @return - the client */ protected <T> T createClientBean(Class<T> clazz, String url, String username, String password, boolean disableCnCheck, List<? extends Object> providers, ClassLoader classLoader) { JAXRSClientFactoryBean clientFactoryBean = initClientBean(clazz, url, classLoader, providers, username, password); T client = clientFactoryBean.create(clazz); if (disableCnCheck) { disableCnCheck(client); } return client; } /** * Creates the JAX-RS client based the information provided. * * @param clazz * - the interface this client implements * @param url * - the URL of the server to connect to * @param username * - username for basic auth (can be null or empty) * @param password * - password for basic auth (can be null or empty) * @param disableCnCheck * - flag to disable CN check when using HTTPS (Should only be used for testing) * @param providers * - list of providers * @param classLoader * - the classloader of the client. Ensures the interface is on the classpath. * @param interceptor * - a custom InInterceptor * @return - the client */ protected <T> T createClientBean(Class<T> clazz, String url, String username, String password, boolean disableCnCheck, List<? extends Object> providers, ClassLoader classLoader, Interceptor<? extends Message> interceptor) { JAXRSClientFactoryBean clientFactoryBean = initClientBean(clazz, url, classLoader, providers, username, password); if (interceptor != null) { clientFactoryBean.getInInterceptors().add(interceptor); } T client = clientFactoryBean.create(clazz); if (disableCnCheck) { disableCnCheck(client); } return client; } private JAXRSClientFactoryBean initClientBean(Class clazz, String url, ClassLoader classLoader, List<? extends Object> providers, String username, String password) { if (StringUtils.isEmpty(url)) { final String errMsg = TrustedRemoteSource.class.getSimpleName() + " was called without a valid URL. " + TrustedRemoteSource.class.getSimpleName() + " will not be able to connect."; LOGGER.error(errMsg); throw new IllegalArgumentException(errMsg); } JAXRSClientFactoryBean clientFactoryBean = new JAXRSClientFactoryBean(); clientFactoryBean.setServiceClass(clazz); clientFactoryBean.setAddress(url); clientFactoryBean.setClassLoader(classLoader); clientFactoryBean.getInInterceptors().add(new LoggingInInterceptor()); clientFactoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); if (!CollectionUtils.isEmpty(providers)) { clientFactoryBean.setProviders(providers); } if ((StringUtils.isNotEmpty(username)) && (StringUtils.isNotEmpty(password))) { clientFactoryBean.setUsername(username); clientFactoryBean.setPassword(password); } return clientFactoryBean; } private void disableCnCheck(Object client) { ClientConfiguration config = WebClient.getConfig(client); HTTPConduit conduit = config.getHttpConduit(); if (conduit == null) { LOGGER.info("HTTPConduit was null for {}. Unable to disable CN Check", client); return; } TLSClientParameters params = conduit.getTlsClientParameters(); if (params == null) { params = new TLSClientParameters(); conduit.setTlsClientParameters(params); } params.setDisableCNCheck(true); } /** * Returns a new STSClient object configured with the properties that have * been set. * * @param bus - CXF bus to initialize STSClient with * @return STSClient */ protected STSClient configureSTSClient(Bus bus, STSClientConfiguration stsClientConfig) { final String methodName = "configureSTSClient"; LOGGER.debug("ENTERING: {}", methodName); String stsAddress = stsClientConfig.getAddress(); String stsServiceName = stsClientConfig.getServiceName(); String stsEndpointName = stsClientConfig.getEndpointName(); String signaturePropertiesPath = stsClientConfig.getSignatureProperties(); String encryptionPropertiesPath = stsClientConfig.getEncryptionProperties(); String stsPropertiesPath = stsClientConfig.getTokenProperties(); STSClient stsClient = new STSClient(bus); if (StringUtils.isBlank(stsAddress)) { LOGGER.debug("STS address is null, unable to create STS Client"); LOGGER.debug("EXITING: {}", methodName); return stsClient; } LOGGER.debug("Setting WSDL location (stsAddress) on STSClient: " + stsAddress); stsClient.setWsdlLocation(stsAddress); LOGGER.debug("Setting service name on STSClient: " + stsServiceName); stsClient.setServiceName(stsServiceName); LOGGER.debug("Setting endpoint name on STSClient: " + stsEndpointName); stsClient.setEndpointName(stsEndpointName); LOGGER.debug("Setting addressing namespace on STSClient: " + ADDRESSING_NAMESPACE); stsClient.setAddressingNamespace(ADDRESSING_NAMESPACE); Map<String, Object> map = new HashMap<String, Object>(); // Properties loader should be able to find the properties file no // matter where it is if (signaturePropertiesPath != null && !signaturePropertiesPath.isEmpty()) { LOGGER.debug("Setting signature properties on STSClient: " + signaturePropertiesPath); Properties signatureProperties = PropertiesLoader .loadProperties(signaturePropertiesPath); map.put(SecurityConstants.SIGNATURE_PROPERTIES, signatureProperties); } if (encryptionPropertiesPath != null && !encryptionPropertiesPath.isEmpty()) { LOGGER.debug("Setting encryption properties on STSClient: " + encryptionPropertiesPath); Properties encryptionProperties = PropertiesLoader .loadProperties(encryptionPropertiesPath); map.put(SecurityConstants.ENCRYPT_PROPERTIES, encryptionProperties); } if (stsPropertiesPath != null && !stsPropertiesPath.isEmpty()) { LOGGER.debug("Setting sts properties on STSClient: " + stsPropertiesPath); Properties stsProperties = PropertiesLoader.loadProperties(stsPropertiesPath); map.put(SecurityConstants.STS_TOKEN_PROPERTIES, stsProperties); } //DDF-733 LOGGER.debug("Setting callback handler on STSClient"); //DDF-733 map.put(SecurityConstants.CALLBACK_HANDLER, new CommonCallbackHandler()); LOGGER.debug("Setting STS TOKEN USE CERT FOR KEY INFO to \"true\""); map.put(SecurityConstants.STS_TOKEN_USE_CERT_FOR_KEYINFO, Boolean.TRUE.toString()); stsClient.setProperties(map); if (stsClient.getWsdlLocation() .startsWith(HttpsURLConnectionFactory.HTTPS_URL_PROTOCOL_ID)) { try { LOGGER.debug("Setting up SSL on the STSClient HTTP Conduit"); HTTPConduit httpConduit = (HTTPConduit) stsClient.getClient().getConduit(); if (httpConduit == null) { LOGGER.info( "HTTPConduit was null for stsClient. Unable to configure keystores for stsClient."); } else { if (securitySettingsService != null) { httpConduit .setTlsClientParameters(securitySettingsService.getTLSParameters()); } else { LOGGER.debug( "Could not get reference to security settings, SSL communications will use system defaults."); } } } catch (BusException e) { LOGGER.error("Unable to create sts client.", e); } catch (EndpointException e) { LOGGER.error("Unable to create sts client endpoint.", e); } } LOGGER.debug("EXITING: {}", methodName); return stsClient; } /** * This method attempts to figure out which wsdl suffix should be appended to the addresses. * If it can find the right address using a simulated "ping," then it uses that. * Otherwise, it uses .wsdl by default. * * @param address * @return */ protected String retrieveWsdlSuffix(String address) { if (address != null && !address.isEmpty()) { if (!wsdlSuffixMap.containsKey(address)) { String url = address + DOT_WSDL; try { final HttpURLConnection connection = (HttpURLConnection) new URL(url) .openConnection(); connection.setConnectTimeout(CONNECTION_TIMEOUT_INTERVAL); connection.connect(); if (connection.getResponseCode() == HTTP_STATUS_CODE_OK) { wsdlSuffixMap.put(address, DOT_WSDL); return DOT_WSDL; } } catch (final MalformedURLException e) { LOGGER.info("Bad URL: " + url, e); } catch (final IOException e) { LOGGER.info("Service " + url + " is not available.", e); } url = address + QUESTION_MARK_WSDL; try { final HttpURLConnection connection = (HttpURLConnection) new URL(url) .openConnection(); connection.setConnectTimeout(CONNECTION_TIMEOUT_INTERVAL); connection.connect(); if (connection.getResponseCode() == HTTP_STATUS_CODE_OK) { wsdlSuffixMap.put(address, QUESTION_MARK_WSDL); return QUESTION_MARK_WSDL; } } catch (final MalformedURLException e) { LOGGER.info("Bad URL: " + url, e); } catch (final IOException e) { LOGGER.info("Service " + url + " is not available.", e); } // if we can recognize that this is a DDF address, then we know it uses ?wsdl if (url.contains("/services")) { wsdlSuffixMap.put(address, QUESTION_MARK_WSDL); return QUESTION_MARK_WSDL; } } else { return wsdlSuffixMap.get(address); } } return QUESTION_MARK_WSDL; } public void setSecuritySettings(SecuritySettingsService securitySettings) { this.securitySettingsService = securitySettings; } public void setSecurityManager(SecurityManager securityManager) { this.securityManager = securityManager; } /** * Set current system TLS Parameters on incoming client * @param client - Client to set the TLS parameters on */ protected void setTlsParameters(Client client) { ClientConfiguration clientConfiguration = WebClient.getConfig(client); HTTPConduit httpConduit = clientConfiguration.getHttpConduit(); if (securitySettingsService != null && httpConduit != null) { TLSClientParameters origParameters = httpConduit.getTlsClientParameters(); TLSClientParameters tlsClientParameters = securitySettingsService.getTLSParameters(); if (origParameters != null) { tlsClientParameters.setDisableCNCheck(origParameters.isDisableCNCheck()); } httpConduit.setTlsClientParameters(tlsClientParameters); } else { LOGGER.debug( "Could not get a reference to security settings service or reference to client http conduit, using system defaults."); } } }