diff options
author | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2020-04-01 17:24:53 +0200 |
---|---|---|
committer | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2020-04-01 17:24:53 +0200 |
commit | c972a8106bbff5dea9fecc76864be9a99a868d78 (patch) | |
tree | 6c9cfca3a7cd002d5fe6e4bbaf884b877ecaf5bf /eaaf_core_utils | |
parent | f4a941a0c4bbe6251a108612a4ee49607d6951fc (diff) | |
parent | 5945c62128c2cb9d552ad7b4c085c09d046d2d56 (diff) | |
download | EAAF-Components-c972a8106bbff5dea9fecc76864be9a99a868d78.tar.gz EAAF-Components-c972a8106bbff5dea9fecc76864be9a99a868d78.tar.bz2 EAAF-Components-c972a8106bbff5dea9fecc76864be9a99a868d78.zip |
Merge branch 'nightlyBuild'
Diffstat (limited to 'eaaf_core_utils')
12 files changed, 955 insertions, 245 deletions
diff --git a/eaaf_core_utils/pom.xml b/eaaf_core_utils/pom.xml index fa5fa412..eb65b01e 100644 --- a/eaaf_core_utils/pom.xml +++ b/eaaf_core_utils/pom.xml @@ -7,7 +7,7 @@ <parent> <groupId>at.gv.egiz</groupId> <artifactId>eaaf</artifactId> - <version>1.1.0</version> + <version>1.1.1</version> </parent> <groupId>at.gv.egiz.eaaf</groupId> <artifactId>eaaf_core_utils</artifactId> @@ -79,6 +79,13 @@ <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> </dependency> + + + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-webmvc</artifactId> + <scope>provided</scope> + </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> @@ -100,6 +107,16 @@ <artifactId>guava</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>mockwebserver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>okhttp-tls</artifactId> + <scope>test</scope> + </dependency> </dependencies> diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java new file mode 100644 index 00000000..1e1e2137 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java @@ -0,0 +1,50 @@ +package at.gv.egiz.eaaf.core.impl.http; + +import java.net.Socket; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.ssl.PrivateKeyDetails; +import org.apache.http.ssl.PrivateKeyStrategy; + +import lombok.extern.slf4j.Slf4j; + +/** + * Private Key selection implementation for Apache HTTP clients. + * + * @author tlenz + * + */ +@Slf4j +public class EaafSslKeySelectionStrategy implements PrivateKeyStrategy { + + private final String keyAlias; + + /** + * Private Key selection implementation for Apache HTTP clients. + * + * @param alias Alias of the Key that should be used for SSL client authentication. + */ + public EaafSslKeySelectionStrategy(String alias) { + this.keyAlias = alias; + + } + + @Override + public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) { + log.trace("Selection SSL client-auth key for alias: {}", keyAlias); + final PrivateKeyDetails selected = aliases.get(keyAlias); + if (selected != null) { + log.trace("Select SL client-auth key with type:", selected.getType()); + return keyAlias; + + } else { + log.warn("KeyStore contains NO key with alias: {}. Using first key from keystore", keyAlias); + log.info("Available aliases: {}", StringUtils.join(aliases.keySet(), ", ")); + return aliases.keySet().iterator().next(); + + } + + } + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java new file mode 100644 index 00000000..582ad545 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java @@ -0,0 +1,191 @@ +package at.gv.egiz.eaaf.core.impl.http; + +import java.text.MessageFormat; +import java.util.UUID; + +import javax.annotation.Nonnull; + +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; + +import org.apache.commons.lang3.StringUtils; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +public class HttpClientConfiguration { + + private static final String MSG_KEYSTORE_NAME = "KeyStore for httpClient: {0}"; + + private static final String ERROR_00 = "internal.httpclient.00"; + private static final String ERROR_01 = "internal.httpclient.01"; + private static final String ERROR_02 = "internal.httpclient.02"; + + @Nonnull + private final String friendlyName; + + @Nonnull + private final String uuid; + + @Nonnull + private ClientAuthMode authMode = ClientAuthMode.NONE; + + @Setter + private String username; + + @Setter + private String password; + + @Setter + boolean disableHostnameValidation = false; + + @Setter + boolean disableTlsHostCertificateValidation = false; + + + private KeyStoreConfiguration keyStoreConfig; + + @Setter + private String sslKeyAlias; + + @Setter + private String sslKeyPassword; + + @Setter + private boolean followHttpRedirects = true; + + /** + * Get a new HTTP-client configuration object. + * + * @param name FriendlyName of this http client for logging purposes. + */ + public HttpClientConfiguration(String name) { + this.friendlyName = name; + this.uuid = UUID.randomUUID().toString(); + + } + + /** + * Set Client authentication-mode from configuration property. + * + * <p>If the mode is unknown than the {@link ClientAuthMode} is set to <code>NONE</code> </p> + * + * @param authModeString Modes from {@link ClientAuthMode} + */ + public void setAuthMode(String authModeString) { + final ClientAuthMode configAuthMode = HttpClientConfiguration.ClientAuthMode.fromString(authModeString); + if (configAuthMode != null) { + authMode = configAuthMode; + + } else { + log.warn("Can Not parse ClientAuthMode for client: {}! Set mode to default value", + friendlyName); + + } + } + + + /** + * Validate the internal state of this configuration object. + * + * @throws EaafConfigurationException In case of a configuration error + */ + public void validate() throws EaafConfigurationException { + log.trace("Validating http-client: {}", this.friendlyName); + if (this.authMode.equals(ClientAuthMode.PASSWORD)) { + if (StringUtils.isEmpty(this.username)) { + throw new EaafConfigurationException(ERROR_00, new Object[] {this.friendlyName}); + + } + + if (StringUtils.isEmpty(this.password)) { + log.warn("Http basic authentication was activated but NOT username was set!"); + + } + + } else if (this.authMode.equals(ClientAuthMode.SSL)) { + if (this.keyStoreConfig == null) { + throw new EaafConfigurationException(ERROR_01, new Object[] {this.friendlyName}); + + } else { + log.trace("Validating KeyStore: {} for http-client: {} ...", + this.keyStoreConfig.getFriendlyName(), this.friendlyName); + this.keyStoreConfig.validate(); + + } + + if (StringUtils.isEmpty(this.sslKeyPassword)) { + throw new EaafConfigurationException(ERROR_02, new Object[] { + this.friendlyName, this.keyStoreConfig.getFriendlyName()}); + + } + } + + } + + /** + * Build a {@link KeyStoreConfiguration} object from configuration parameters. + * + * @param keyStoreType String based KeyStore type + * @param keyStorePath Path to KeyStore in case of a software based KeyStore + * @param keyStorePassword Password in case of a software based KeyStore + * @param keyStoreName Name of the KeyStore in case of a named KeyStore like HSM-Facade + * @throws EaafConfigurationException In case of a configuration error + */ + public void buildKeyStoreConfig(String keyStoreType, String keyStorePath, + String keyStorePassword, String keyStoreName) throws EaafConfigurationException { + final KeyStoreConfiguration config = new KeyStoreConfiguration(); + config.setKeyStoreType(keyStoreType); + config.setFriendlyName(MessageFormat.format(MSG_KEYSTORE_NAME, friendlyName)); + config.setSoftKeyStoreFilePath(keyStorePath); + config.setSoftKeyStorePassword(keyStorePassword); + config.setKeyStoreName(keyStoreName); + this.keyStoreConfig = config; + + } + + public enum ClientAuthMode { + NONE("none"), PASSWORD("password"), SSL("ssl"); + + private final String mode; + + ClientAuthMode(final String mode) { + this.mode = mode; + } + + /** + * Get the PVP mode. + * + * @return + */ + public String getMode() { + return this.mode; + } + + /** + * Get http-client authentication mode from String representation. + * + * @param s Config parameter + * @return + */ + public static ClientAuthMode fromString(final String s) { + try { + return ClientAuthMode.valueOf(s.toUpperCase()); + + } catch (IllegalArgumentException | NullPointerException e) { + return null; + } + } + + @Override + public String toString() { + return getMode(); + + } + + } + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/HttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java index e681e705..00d5891a 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/HttpClientFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java @@ -1,22 +1,14 @@ -package at.gv.egiz.eaaf.core.impl.utils; +package at.gv.egiz.eaaf.core.impl.http; -import java.security.KeyManagementException; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import at.gv.egiz.eaaf.core.api.idp.IConfiguration; -import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; -import at.gv.egiz.eaaf.core.exceptions.EaafException; -import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; -import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; -import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; - import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; @@ -29,6 +21,7 @@ import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; +import org.apache.http.config.SocketConfig; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.LayeredConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; @@ -43,20 +36,21 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContexts; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ResourceLoader; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; import lombok.extern.slf4j.Slf4j; @Slf4j public class HttpClientFactory implements IHttpClientFactory { - @Autowired(required = true) + @Autowired private IConfiguration basicConfig; - - @Autowired(required = true) - ResourceLoader resourceLoader; - - @Autowired private EaafKeyStoreFactory keyStoreFactory; + @Autowired + private EaafKeyStoreFactory keyStoreFactory; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE = "client.http.connection.pool.use"; @@ -84,9 +78,10 @@ public class HttpClientFactory implements IHttpClientFactory { "client.auth.ssl.keystore.name"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE = "client.auth.ssl.keystore.type"; + public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_ALIAS = + "client.auth.ssl.key.alias"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD = "client.auth.ssl.key.password"; - public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_ALIAS = "client.auth.ssl.key.alias"; // default configuration values public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET = "15"; @@ -95,48 +90,8 @@ public class HttpClientFactory implements IHttpClientFactory { public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL = "500"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE = "100"; - public enum ClientAuthMode { - NONE("none"), PASSWORD("password"), SSL("ssl"); - - private final String mode; - - ClientAuthMode(final String mode) { - this.mode = mode; - } - - /** - * Get the PVP mode. - * - * @return - */ - public String getMode() { - return this.mode; - } - - /** - * Get http-client authentication mode from String representation. - * - * @param s Config parameter - * @return - */ - public static ClientAuthMode fromString(final String s) { - try { - return ClientAuthMode.valueOf(s.toUpperCase()); - - } catch (IllegalArgumentException | NullPointerException e) { - return null; - } - } - - @Override - public String toString() { - return getMode(); - - } - - } - - private HttpClientBuilder httpClientBuilder = null; + private String defaultConfigurationId = null; + private final Map<String, HttpClientBuilder> availableBuilders = new HashMap<>(); /* * (non-Javadoc) @@ -151,185 +106,168 @@ public class HttpClientFactory implements IHttpClientFactory { @Override public CloseableHttpClient getHttpClient(final boolean followRedirects) { - RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - if (!followRedirects) { - redirectStrategy = new RedirectStrategy() { + return availableBuilders.get(defaultConfigurationId).setRedirectStrategy( + buildRedirectStrategy(followRedirects)).build(); - @Override - public boolean isRedirected(final HttpRequest request, final HttpResponse response, - final HttpContext context) throws ProtocolException { - return false; - } + } - @Override - public HttpUriRequest getRedirect(final HttpRequest request, final HttpResponse response, - final HttpContext context) throws ProtocolException { - return null; - } - }; - } + @Override + public CloseableHttpClient getHttpClient(@Nonnull HttpClientConfiguration config) throws EaafException { + log.trace("Build http client for: {}", config.getFriendlyName()); + HttpClientBuilder builder = null; + if (availableBuilders.containsKey(config.getUuid())) { + builder = availableBuilders.get(config.getUuid()); - return httpClientBuilder.setRedirectStrategy(redirectStrategy).build(); + } else { + log.debug("Initialize new http-client builder for: {}", config.getFriendlyName()); - } + //validate configuration object + config.validate(); - @PostConstruct - private void initalize() { - // initialize http client - log.trace("Initializing HTTP Client-builder ... "); - httpClientBuilder = HttpClients.custom(); + builder = HttpClients.custom(); + builder.setDefaultRequestConfig(buildDefaultRequestConfig()); - // set default request configuration - final RequestConfig requestConfig = - RequestConfig.custom() - .setConnectTimeout( - Integer.parseInt(basicConfig.getBasicConfiguration( - PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION, - DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION)) * 1000) - .setConnectionRequestTimeout(Integer.parseInt(basicConfig.getBasicConfiguration( - PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST, - DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST)) * 1000) - .setSocketTimeout(Integer.parseInt( - basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, - DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) - * 1000) - .build(); - httpClientBuilder.setDefaultRequestConfig(requestConfig); + //inject basic authentication infos + injectBasicAuthenticationIfRequired(builder, config); - ClientAuthMode clientAuthMode = ClientAuthMode.fromString( - basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_MODE, ClientAuthMode.NONE.getMode())); - if (clientAuthMode == null) { - log.warn("Can Not parse ClientAuthMode! Set mode to default value"); - clientAuthMode = ClientAuthMode.NONE; + //inject authentication if required + final LayeredConnectionSocketFactory sslConnectionFactory = getSslContext(config); - } + // set pool connection if required + injectDefaultConnectionPoolIfRequired(builder, sslConnectionFactory); - // inject basic http authentication if required - log.info("Client authentication-mode is set to: {}", clientAuthMode); - injectBasicAuthenticationIfRequired(clientAuthMode); + availableBuilders.put(config.getUuid(), builder); - // inject authentication if required - final LayeredConnectionSocketFactory sslConnectionFactory = getSslContext(clientAuthMode); + } - // set pool connection if required - injectConnectionPoolIfRequired(sslConnectionFactory); + return builder.setRedirectStrategy( + buildRedirectStrategy(config.isFollowHttpRedirects())).build(); } - private void injectBasicAuthenticationIfRequired(final ClientAuthMode clientAuthMode) { - if (clientAuthMode.equals(ClientAuthMode.PASSWORD)) { - final CredentialsProvider provider = new BasicCredentialsProvider(); + @PostConstruct + private void initalize() throws EaafException { + final HttpClientConfiguration defaultHttpClientConfig = buildDefaultHttpClientConfiguration(); - final String username = - basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME); - final String password = - basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD); + // initialize http client + log.trace("Initializing default HTTP-Client builder ... "); + final HttpClientBuilder defaultHttpClientBuilder = HttpClients.custom(); - if (StringUtils.isEmpty(username)) { - log.warn("Http basic authentication was activated but NOT username was set!"); + // set default request configuration + defaultHttpClientBuilder.setDefaultRequestConfig(buildDefaultRequestConfig()); - } + //inject http basic authentication + injectBasicAuthenticationIfRequired(defaultHttpClientBuilder, defaultHttpClientConfig); - log.trace("Injecting basic authentication with username: {} and password: {}", username, - password); - final UsernamePasswordCredentials credentials = - new UsernamePasswordCredentials(username, password); - provider.setCredentials(AuthScope.ANY, credentials); - httpClientBuilder.setDefaultCredentialsProvider(provider); - log.info("Basic http authentication was injected with username: {}", username); + // inject authentication if required + final LayeredConnectionSocketFactory sslConnectionFactory = + getSslContext(defaultHttpClientConfig); - } else { - log.trace("Injection of Http Basic authentication was skipped"); + // set pool connection if required + injectDefaultConnectionPoolIfRequired(defaultHttpClientBuilder, sslConnectionFactory); - } + //set default http client builder + defaultConfigurationId = defaultHttpClientConfig.getUuid(); + availableBuilders.put(defaultConfigurationId, defaultHttpClientBuilder); } - private SSLContext buildSslContextWithSslClientAuthentication() - throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, - KeyStoreException, EaafConfigurationException { - log.trace("Injecting SSL client-authentication into http client ... "); - final KeyStore keystore = getSslAuthKeyStore(); - final String keyPasswordString = - basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD); - log.trace("Open SSL Client-Auth keystore with password: {}", keyPasswordString); - final char[] keyPassword = keyPasswordString == null ? StringUtils.EMPTY.toCharArray() - : keyPasswordString.toCharArray(); - return SSLContexts.custom().loadKeyMaterial(keystore, keyPassword).build(); + private HttpClientConfiguration buildDefaultHttpClientConfiguration() throws EaafConfigurationException { + final HttpClientConfiguration config = new HttpClientConfiguration("Default"); + // inject basic http authentication if required + config.setAuthMode(basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_MODE, + HttpClientConfiguration.ClientAuthMode.NONE.getMode())); + log.info("Default client authentication-mode is set to: {}", config.getAuthMode()); + + // set Username and Password if available + config.setUsername(basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME)); + config.setPassword(basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD)); + + // set SSL Client auth. informations if available + config.buildKeyStoreConfig( + basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE, KeyStoreType.PKCS12.getKeyStoreType()), + basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PATH, StringUtils.EMPTY), + basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD, StringUtils.EMPTY), + basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_NAME, StringUtils.EMPTY)); + + config.setSslKeyAlias( + basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_ALIAS)); + config.setSslKeyPassword( + basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD)); + + config.setDisableHostnameValidation(basicConfig.getBasicConfigurationBoolean( + PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)); + + // validate configuration object + config.validate(); + + return config; } - private KeyStore getSslAuthKeyStore() throws EaafConfigurationException { - final String keyStoreType = basicConfig.getBasicConfiguration( - PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE, KeyStoreType.PKCS12.getKeyStoreType()); - final String localKeyStorePath = basicConfig - .getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PATH, StringUtils.EMPTY); - final String keyStorePassword = basicConfig - .getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD, StringUtils.EMPTY); - final String keyStoreName = basicConfig - .getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_NAME, StringUtils.EMPTY); - - try { - final KeyStoreConfiguration keyStoreConfig = new KeyStoreConfiguration(); - keyStoreConfig.setKeyStoreType(keyStoreType); - keyStoreConfig.setFriendlyName("HttpClient Keystore"); - keyStoreConfig.setSoftKeyStoreFilePath(localKeyStorePath); - keyStoreConfig.setSoftKeyStorePassword(keyStorePassword); - keyStoreConfig.setKeyStoreName(keyStoreName); - - log.debug("Open keyStore with type: {}", keyStoreType); - final KeyStore keyStore = keyStoreFactory.buildNewKeyStore(keyStoreConfig).getFirst(); - - return keyStore; - - } catch (final EaafException e) { - log.warn("Can NOT read keyStore: {} from filesystem", localKeyStorePath, null, e); - throw new EaafConfigurationException("Can NOT read keyStore: {} from filesystem", - new Object[] { localKeyStorePath }, e); + private void injectBasicAuthenticationIfRequired(HttpClientBuilder builder, + final HttpClientConfiguration httpClientConfig) { + if (httpClientConfig.getAuthMode().equals(HttpClientConfiguration.ClientAuthMode.PASSWORD)) { + final CredentialsProvider provider = new BasicCredentialsProvider(); + log.trace("Injecting basic authentication with username: {} and password: {}", + httpClientConfig.getUsername(), httpClientConfig.getPassword()); + final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials( + httpClientConfig.getUsername(), httpClientConfig.getPassword()); + + final AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM); + provider.setCredentials(scope, credentials); + builder.setDefaultCredentialsProvider(provider); + log.info("Basic http authentication was injected with username: {}", + httpClientConfig.getUsername()); + + } else { + log.trace("Injection of Http Basic authentication was skipped"); } } - private LayeredConnectionSocketFactory getSslContext(final ClientAuthMode clientAuthMode) { + @Nonnull + private LayeredConnectionSocketFactory getSslContext(final HttpClientConfiguration httpClientConfig) + throws EaafException { SSLContext sslContext = null; - try { - if (clientAuthMode.equals(ClientAuthMode.SSL)) { - sslContext = buildSslContextWithSslClientAuthentication(); - - } else { - log.trace("Initializing default SSL Context ... "); - sslContext = SSLContext.getDefault(); - - } - - // set hostname verifier - HostnameVerifier hostnameVerifier = null; - if (basicConfig.getBasicConfigurationBoolean( - PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)) { - hostnameVerifier = new NoopHostnameVerifier(); - log.warn("HTTP client-builder deactivates SSL Host-name verification!"); - - } + if (httpClientConfig.getAuthMode().equals(HttpClientConfiguration.ClientAuthMode.SSL)) { + log.debug("Open keyStore with type: {}", httpClientConfig.getKeyStoreConfig().getKeyStoreType()); + final KeyStore keyStore = keyStoreFactory.buildNewKeyStore(httpClientConfig.getKeyStoreConfig()) + .getFirst(); - final LayeredConnectionSocketFactory sslSocketFactory = - new SSLConnectionSocketFactory(sslContext, hostnameVerifier); + log.trace("Injecting SSL client-authentication into http client ... "); + sslContext = HttpUtils.buildSslContextWithSslClientAuthentication(keyStore, + httpClientConfig.getSslKeyAlias(), httpClientConfig.getSslKeyPassword(), + httpClientConfig.isDisableTlsHostCertificateValidation(), httpClientConfig.getFriendlyName()); - return sslSocketFactory; + } else { + log.trace("Initializing default SSL Context ... "); + sslContext = SSLContexts.createDefault(); + + } - } catch (final NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException - | KeyStoreException | EaafConfigurationException e) { - log.warn("HTTP client-builder can NOT initialze SSL-Context", e); + // set hostname verifier + HostnameVerifier hostnameVerifier = null; + if (httpClientConfig.isDisableHostnameValidation()) { + hostnameVerifier = new NoopHostnameVerifier(); + log.warn("HTTP client-builder deactivates SSL Host-name verification!"); } - log.info("HTTP client-builder successfuly initialized"); - return null; + final LayeredConnectionSocketFactory sslSocketFactory = + new SSLConnectionSocketFactory(sslContext, hostnameVerifier); + log.debug("HTTP client-builder successfuly initialized"); + return sslSocketFactory; } - private void injectConnectionPoolIfRequired( - final LayeredConnectionSocketFactory sslConnectionFactory) { + private void injectDefaultConnectionPoolIfRequired( + HttpClientBuilder builder, final LayeredConnectionSocketFactory sslConnectionFactory) { if (basicConfig.getBasicConfigurationBoolean(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE, true)) { PoolingHttpClientConnectionManager pool; @@ -347,7 +285,7 @@ public class HttpClientFactory implements IHttpClientFactory { pool = new PoolingHttpClientConnectionManager(); } - + pool.setDefaultMaxPerRoute(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE))); @@ -355,15 +293,61 @@ public class HttpClientFactory implements IHttpClientFactory { basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL))); - httpClientBuilder.setConnectionManager(pool); + pool.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(Integer.parseInt( + basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, + DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) + * 1000).build()); + + builder.setConnectionManager(pool); log.debug("Initalize http-client pool with, maxTotal: {} maxPerRoute: {}", pool.getMaxTotal(), pool.getDefaultMaxPerRoute()); } else if (sslConnectionFactory != null) { log.trace("Inject SSLSocketFactory without connection pool"); - httpClientBuilder.setSSLSocketFactory(sslConnectionFactory); + builder.setSSLSocketFactory(sslConnectionFactory); + + } + + } + private RequestConfig buildDefaultRequestConfig() { + final RequestConfig requestConfig = + RequestConfig.custom() + .setConnectTimeout( + Integer.parseInt(basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION, + DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION)) * 1000) + .setConnectionRequestTimeout(Integer.parseInt(basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST, + DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST)) * 1000) + .setSocketTimeout(Integer.parseInt( + basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, + DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) + * 1000) + .build(); + return requestConfig; + + } + + private static RedirectStrategy buildRedirectStrategy(final boolean followRedirects) { + RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + if (!followRedirects) { + redirectStrategy = new RedirectStrategy() { + + @Override + public boolean isRedirected(final HttpRequest request, final HttpResponse response, + final HttpContext context) throws ProtocolException { + return false; + } + + @Override + public HttpUriRequest getRedirect(final HttpRequest request, final HttpResponse response, + final HttpContext context) throws ProtocolException { + return null; + } + }; } + return redirectStrategy; } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/HttpUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java index 66356ba0..2d514912 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/HttpUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java @@ -16,14 +16,35 @@ * works that you distribute must include a readable copy of the "NOTICE" text file. */ -package at.gv.egiz.eaaf.core.impl.utils; +package at.gv.egiz.eaaf.core.impl.http; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; import javax.servlet.http.HttpServletRequest; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException; + import org.apache.commons.lang3.StringUtils; +import org.apache.http.conn.ssl.TrustAllStrategy; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.ssl.TrustStrategy; + +import lombok.extern.slf4j.Slf4j; +@Slf4j public class HttpUtils { + private static final String ERROR_03 = "internal.httpclient.03"; + /** * Helper method to retrieve server URL including context path. * @@ -115,4 +136,59 @@ public class HttpUtils { } } + /** + * Initialize a {@link SSLContext} with a {@link KeyStore} that uses X509 Client + * authentication. + * + * @param keyStore KeyStore with private keys that should be + * used + * @param keyAlias Alias of the key that should be used. If + * the alias is null, than the first key that + * is found will be selected. + * @param keyPasswordString Password of the Key in this keystore + * @param trustAllServerCertificates Deactivate SSL server-certificate + * validation + * @param friendlyName FriendlyName of the http client for logging + * purposes + * @return {@link SSLContext} with X509 client authentication + * @throws EaafConfigurationException In case of a configuration error + * @throws EaafFactoryException In case of a {@link SSLContext} + * initialization error + */ + public static SSLContext buildSslContextWithSslClientAuthentication(@Nonnull final KeyStore keyStore, + @Nullable String keyAlias, @Nullable String keyPasswordString, + boolean trustAllServerCertificates, @Nonnull String friendlyName) + throws EaafConfigurationException, EaafFactoryException { + try { + log.trace("Open SSL Client-Auth keystore with password: {}", keyPasswordString); + final char[] keyPassword = keyPasswordString == null ? StringUtils.EMPTY.toCharArray() + : keyPasswordString.toCharArray(); + + SSLContextBuilder sslContextBuilder = SSLContexts.custom(); + if (StringUtils.isNotEmpty(keyAlias)) { + sslContextBuilder = sslContextBuilder + .loadKeyMaterial(keyStore, keyPassword, new EaafSslKeySelectionStrategy(keyAlias)); + + } else { + sslContextBuilder = sslContextBuilder + .loadKeyMaterial(keyStore, keyPassword); + + } + + if (trustAllServerCertificates) { + log.warn("Http-client:{} trusts ALL TLS server-certificates!"); + final TrustStrategy trustStrategy = new TrustAllStrategy(); + sslContextBuilder = sslContextBuilder.loadTrustMaterial(trustStrategy); + + } + + return sslContextBuilder.build(); + + } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException + | KeyStoreException e) { + throw new EaafFactoryException(ERROR_03, new Object[] { friendlyName, e.getMessage() }, e); + + } + } + } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java new file mode 100644 index 00000000..7ec58d46 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java @@ -0,0 +1,43 @@ +package at.gv.egiz.eaaf.core.impl.http; + +import javax.annotation.Nonnull; + +import at.gv.egiz.eaaf.core.exceptions.EaafException; + +import org.apache.http.impl.client.CloseableHttpClient; + +public interface IHttpClientFactory { + + /** + * Return an instance of a Apache HTTP client that uses + * default configuration properties from {@link IHttpClientFactory} implementation + * and follows http redirects automatically. + * + * @return http client + */ + @Nonnull + CloseableHttpClient getHttpClient(); + + /** + * Return an instance of a Apache HTTP client that uses + * default configuration properties from {@link IHttpClientFactory} implementation. + * + * @param followRedirects if <code>false</code>, the client does not flow 30x + * http redirects + * @return http client + */ + @Nonnull + CloseableHttpClient getHttpClient(boolean followRedirects); + + /** + * Return an instance of a Apache HTTP client based in {@link HttpClientConfiguration}. + * + * @param config Configuration object for this http client + * @return http client + * @throws EaafException In case of a http-client initialization problem + */ + @Nonnull + CloseableHttpClient getHttpClient(@Nonnull HttpClientConfiguration config) + throws EaafException; + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/IHttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/IHttpClientFactory.java deleted file mode 100644 index f922e1ac..00000000 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/IHttpClientFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -package at.gv.egiz.eaaf.core.impl.utils; - -import org.apache.http.impl.client.CloseableHttpClient; - -public interface IHttpClientFactory { - - /** - * Return an instance of a Apache HTTP client that follows http redirects - * automatically. - * - * @return - */ - CloseableHttpClient getHttpClient(); - - /** - * Return an instance of a Apache HTTP client. - * - * @param followRedirects if <code>false</code>, the client does not flow 30x - * http redirects - * @return - */ - CloseableHttpClient getHttpClient(boolean followRedirects); - -} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/TransactionIdUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/TransactionIdUtils.java index 4cbcfa70..4c1601c0 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/TransactionIdUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/TransactionIdUtils.java @@ -19,6 +19,8 @@ package at.gv.egiz.eaaf.core.impl.utils; +import java.util.UUID; + import at.gv.egiz.eaaf.core.api.IRequest; /** @@ -29,7 +31,6 @@ import at.gv.egiz.eaaf.core.api.IRequest; */ public class TransactionIdUtils { - // MDC variables for logging /** * To correlate technical logs over one single transactions. */ @@ -46,6 +47,11 @@ public class TransactionIdUtils { public static final String MDC_SERVICEPROVIDER_ID = "oaId"; /** + * Unique application identifier that is processed in this transaction. + */ + public static final String MDC_REMOTEHOST = "remoteHost"; + + /** * Set all MDC variables from pending request to this threat context.<br> * These includes SessionID, TransactionID, and unique service-provider * identifier @@ -67,6 +73,7 @@ public class TransactionIdUtils { removeSessionId(); removeTransactionId(); removeServiceProviderId(); + removeRemoteHost(); } @@ -99,6 +106,14 @@ public class TransactionIdUtils { } /** + * Set new random transactionId as an UUID. + */ + public static void setTransactionId() { + org.slf4j.MDC.put(MDC_TRANSACTION_ID, UUID.randomUUID().toString()); + + } + + /** * Set Id to correlate technical logs over one single transactions. * * @param transactionId Unique transaction Id @@ -117,6 +132,14 @@ public class TransactionIdUtils { } /** + * Set a new random SessionId as UUID. + */ + public static void setSessionId() { + org.slf4j.MDC.put(MDC_SESSION_ID, UUID.randomUUID().toString()); + + } + + /** * Set Id to correlate technical logs over a set of transactions, like SSO. * * @param uniqueSessionId Unique Id @@ -135,5 +158,23 @@ public class TransactionIdUtils { } + /** + * Set remoteHost information to correlate technical logs. + * + * @param remoteHost IP address of the remote host + */ + public static void setRemoteHost(final String remoteHost) { + org.slf4j.MDC.put(MDC_REMOTEHOST, remoteHost); + + } + + /** + * Remove remoteHost for Logging. + */ + public static void removeRemoteHost() { + org.slf4j.MDC.remove(MDC_REMOTEHOST); + + } + } diff --git a/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties b/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties index f531e02d..b20c5f63 100644 --- a/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties +++ b/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties @@ -12,4 +12,7 @@ internal.keystore.07=Validation of KeyStore: {0} failed. Reason: {1} internal.keystore.08=Can not access Key: {1} in KeyStore: {0} internal.keystore.09=Can not access Key: {1} in KeyStore: {0} Reason: {2} - +internal.httpclient.00=HttpClient:{0} uses http Basic-Auth, but 'Username' is NOT set +internal.httpclient.01=HttpClient:{0} uses X509 client-auth, but 'KeyStoreConfig' is NOT set +internal.httpclient.02=HttpClient:{0} uses KeyStore:{1}, but 'keyPassword' is NOT set +internal.httpclient.03=Can not initialize SSLContext for HttpClient:{0} Reason:{1}
\ No newline at end of file diff --git a/eaaf_core_utils/src/main/resources/spring/eaaf_utils.beans.xml b/eaaf_core_utils/src/main/resources/spring/eaaf_utils.beans.xml index ab631e34..aa5a50de 100644 --- a/eaaf_core_utils/src/main/resources/spring/eaaf_utils.beans.xml +++ b/eaaf_core_utils/src/main/resources/spring/eaaf_utils.beans.xml @@ -11,8 +11,8 @@ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd" > <bean id="eaafHttpClientFactory" - class="at.gv.egiz.eaaf.core.impl.utils.HttpClientFactory" /> - + class="at.gv.egiz.eaaf.core.impl.http.HttpClientFactory" /> + <bean id="eaafKeyStoreFactory" class="at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory" /> diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java index ed2e159b..cefb1e7e 100644 --- a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java +++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java @@ -7,17 +7,6 @@ import java.security.Provider; import java.security.cert.X509Certificate; import java.util.List; -import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException; -import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; -import at.gv.egiz.eaaf.core.exceptions.EaafException; -import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException; -import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; -import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; -import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; -import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; -import at.gv.egiz.eaaf.core.impl.data.Pair; -import at.gv.egiz.eaaf.core.test.dummy.DummyAuthConfigMap; - import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Before; @@ -35,6 +24,17 @@ import com.google.common.base.Optional; import com.google.common.base.Predicates; import com.google.common.base.Throwables; import com.google.common.collect.FluentIterable; + +import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.test.dummy.DummyAuthConfigMap; import io.grpc.StatusRuntimeException; @RunWith(SpringJUnit4ClassRunner.class) @@ -325,7 +325,7 @@ public class EaafKeyStoreFactoryTest { keyStore.getFirst(), "notexist", "password".toCharArray(), false, "jUnit test"); Assert.assertNull("Credential 3", privCred3); - //read priv. key + //read priv. key final Pair<Key, X509Certificate[]> privCred4 = EaafKeyStoreUtils.getPrivateKeyAndCertificates( keyStore.getFirst(), "meta", "wrong".toCharArray(), false, "jUnit test"); Assert.assertNull("Credential 3", privCred4); diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java new file mode 100644 index 00000000..25bd3008 --- /dev/null +++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java @@ -0,0 +1,329 @@ +package at.gv.egiz.eaaf.core.test.http; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; +import java.security.cert.X509Certificate; + +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.http.HttpClientConfiguration; +import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory; +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("/spring/test_eaaf_pvp_not_lazy.beans.xml") +public class HttpClientFactoryTest { + + @Autowired private EaafKeyStoreFactory keyStoreFactory; + @Autowired private IHttpClientFactory httpClientFactory; + + private MockWebServer mockWebServer = null; + private HttpUrl mockServerUrl; + + /** + * JUnit test set-up. + * + */ + @Before + public void setup() { + + } + + /** + * jUnit test shutdown. + * + * @throws IOException In case of an mockWebServer error + */ + @After + public void shutdown() throws IOException { + if (mockWebServer != null) { + mockWebServer.shutdown(); + mockWebServer = null; + + } + + } + + @Test + public void getDefaultClient() { + final CloseableHttpClient client = httpClientFactory.getHttpClient(); + Assert.assertNotNull("httpClient", client); + + } + + @Test + public void getDefaultClientNoRedirect() { + final CloseableHttpClient client = httpClientFactory.getHttpClient(false); + Assert.assertNotNull("httpClient", client); + + } + + @Test + public void getCustomClientsDefault() throws EaafException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + Assert.assertFalse("Wrong default config - Hostnamevalidation", + config.isDisableHostnameValidation()); + Assert.assertFalse("Wrong default config - TLS Server-certs", + config.isDisableTlsHostCertificateValidation()); + + final CloseableHttpClient client1 = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("first http client", client1); + + final CloseableHttpClient client2 = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("second http client", client2); + + } + + @Test + public void getCustomClientUnknownAuthMethod() throws EaafException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode(RandomStringUtils.randomAlphabetic(5)); + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("httpClient", client); + + } + + @Test + public void getCustomClientBasicAuth() throws EaafException, ClientProtocolException, + IOException, InterruptedException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("password"); + config.setUsername("jUnit"); + config.setPassword("password"); + + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("httpClient", client); + + //setup test webserver that requestes http Basic authentication + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/junit"); + mockWebServer.enqueue(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_UNAUTHORIZED) + .addHeader("www-authenticate: Basic realm=\"protected area\"") + .setBody("Please authenticate.")); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("Successful auth!")); + + //request webservice + final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString()); + final CloseableHttpResponse httpResp2 = client.execute(httpGet2); + Assert.assertEquals("http statusCode", 200, httpResp2.getStatusLine().getStatusCode()); + + //check request contains basic authentication after authentication was requested + final RecordedRequest httpReq1 = mockWebServer.takeRequest(); + final RecordedRequest httpReq2 = mockWebServer.takeRequest(); + Assert.assertNull("wrong BasicAuthHeader", httpReq1.getHeader("Authorization")); + Assert.assertNotNull("missing BasicAuthHeader", httpReq2.getHeader("Authorization")); + + } + + @Test + public void getCustomClientBasicAuthNoUsername() { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("password"); + try { + httpClientFactory.getHttpClient(config); + Assert.fail("Wrong config not detected"); + + } catch (final EaafException e) { + Assert.assertEquals("Wrong errorCode", "internal.httpclient.00", e.getErrorId()); + + } + } + + @Test + public void getCustomClientBasicAuthNoPassword() throws EaafException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("password"); + config.setUsername(RandomStringUtils.randomAlphabetic(5)); + + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("httpClient", client); + + } + + @Test + public void getCustomClientX509AuthNoKeyStoreConfig() { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("ssl"); + try { + httpClientFactory.getHttpClient(config); + Assert.fail("Wrong config not detected"); + + } catch (final EaafException e) { + Assert.assertEquals("Wrong errorCode", "internal.httpclient.01", e.getErrorId()); + + } + } + + @Test + public void getCustomClientX509AuthNoKeyPassword() throws EaafException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("ssl"); + config.buildKeyStoreConfig( + "jks", + "src/test/resources/data/junit.jks", + "password", + null); + + try { + httpClientFactory.getHttpClient(config); + Assert.fail("Wrong config not detected"); + + } catch (final EaafException e) { + Assert.assertEquals("Wrong errorCode", "internal.httpclient.02", e.getErrorId()); + + } + } + + @Test + public void getCustomClientX509Auth() throws EaafException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("ssl"); + config.buildKeyStoreConfig( + "jks", + "src/test/resources/data/junit.jks", + "password", + null); + config.setSslKeyPassword("password"); + + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("httpClient", client); + + } + + @Test + public void getCustomClientX509AuthWithAlias() throws EaafException, ClientProtocolException, + IOException, KeyStoreException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("ssl"); + config.buildKeyStoreConfig( + "jks", + "src/test/resources/data/junit.jks", + "password", + null); + config.setSslKeyPassword("password"); + config.setSslKeyAlias("sig"); + config.setDisableTlsHostCertificateValidation(true); + + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("httpClient", client); + + //set-up mock-up web-server with SSL client authentication + final Pair<KeyStore, Provider> sslClientKeyStore = + keyStoreFactory.buildNewKeyStore(config.getKeyStoreConfig()); + final String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); + final HeldCertificate localhostCertificate = new HeldCertificate.Builder() + .addSubjectAlternativeName(localhost) + .build(); + final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() + .addTrustedCertificate( + (X509Certificate) sslClientKeyStore.getFirst().getCertificate(config.getSslKeyAlias())) + .heldCertificate(localhostCertificate) + .build(); + mockWebServer = new MockWebServer(); + mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false); + mockWebServer.requireClientAuth(); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("Successful auth!")); + mockServerUrl = mockWebServer.url("/sp/junit"); + + //perform test request + final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString()); + final CloseableHttpResponse httpResp2 = client.execute(httpGet2); + Assert.assertEquals("http statusCode", 200, httpResp2.getStatusLine().getStatusCode()); + + } + + @Test + public void getCustomClientX509AuthWrongKeyPassword() throws EaafException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("ssl"); + config.buildKeyStoreConfig( + "jks", + "src/test/resources/data/junit.jks", + "password", + null); + config.setSslKeyPassword(RandomStringUtils.randomAlphanumeric(5)); + config.setSslKeyAlias("sig"); + + try { + httpClientFactory.getHttpClient(config); + Assert.fail("Wrong key password not detected"); + + } catch (final EaafException e) { + Assert.assertEquals("Wrong errorCode", "internal.httpclient.03", e.getErrorId()); + + } + } + + @Test + public void getCustomClientX509AuthWithWrongAlias() throws EaafException, KeyStoreException, + ClientProtocolException, IOException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("ssl"); + config.buildKeyStoreConfig( + "jks", + "src/test/resources/data/junit.jks", + "password", + null); + config.setSslKeyPassword("password"); + config.setSslKeyAlias(RandomStringUtils.randomAlphabetic(5)); + config.setDisableHostnameValidation(true); + config.setFollowHttpRedirects(false); + config.setDisableTlsHostCertificateValidation(true); + + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("httpClient", client); + + //set-up mock-up web-server with SSL client authentication + final Pair<KeyStore, Provider> sslClientKeyStore = + keyStoreFactory.buildNewKeyStore(config.getKeyStoreConfig()); + final String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); + final HeldCertificate localhostCertificate = new HeldCertificate.Builder() + .addSubjectAlternativeName(localhost) + .build(); + final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() + .addTrustedCertificate( + (X509Certificate) sslClientKeyStore.getFirst().getCertificate("meta")) + .heldCertificate(localhostCertificate) + .build(); + mockWebServer = new MockWebServer(); + mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false); + mockWebServer.requireClientAuth(); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("Successful auth!")); + mockServerUrl = mockWebServer.url("/sp/junit"); + + //perform test request + final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString()); + final CloseableHttpResponse httpResp2 = client.execute(httpGet2); + Assert.assertEquals("http statusCode", 200, httpResp2.getStatusLine().getStatusCode()); + + } +} |