From 607a0051baeb9eece35cb1aa07b4af5e6459d547 Mon Sep 17 00:00:00 2001 From: symphony-thibault Date: Sat, 30 May 2020 19:39:16 +0200 Subject: [PATCH 1/4] SDK-75 Fixed SSL issues for RSA authentication --- pom.xml | 294 ++++++++++-------- src/lombok.config | 1 + src/main/java/authentication/ISymAuth.java | 8 +- .../java/authentication/SymBotRSAAuth.java | 267 +++++----------- .../configuration/RetryConfiguration.java | 23 ++ src/main/java/configuration/SymConfig.java | 3 + .../configuration/SymLoadBalancedConfig.java | 16 +- .../exceptions/AuthenticationException.java | 14 +- src/main/java/internal/FileHelper.java | 46 +++ .../java/internal/jersey/JerseyHelper.java | 32 ++ .../jersey/NoCacheFeature.java | 5 +- .../java/utils/HttpClientBuilderHelper.java | 177 +++++------ src/main/java/utils/JwtHelper.java | 5 + 13 files changed, 454 insertions(+), 437 deletions(-) create mode 100644 src/lombok.config create mode 100644 src/main/java/configuration/RetryConfiguration.java create mode 100644 src/main/java/internal/FileHelper.java create mode 100644 src/main/java/internal/jersey/JerseyHelper.java rename src/main/java/{utils => internal}/jersey/NoCacheFeature.java (86%) diff --git a/pom.xml b/pom.xml index 3cfda6b29..65913e160 100644 --- a/pom.xml +++ b/pom.xml @@ -9,39 +9,152 @@ https://github.com/SymphonyPlatformSolutions/symphony-api-client-java Symphony API Client provided by Symphony Platform Solutions team - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Symphony Platform Solutions - platformsolutions@symphony.com - Symphony Communication Services - https://symphony.com/ - - - - - scm:git:git://github.com/SymphonyPlatformSolutions/symphony-api-client-java.git - scm:git:ssh://github.com/SymphonyPlatformSolutions/symphony-api-client-java.git - https://github.com/SymphonyPlatformSolutions/symphony-api-client-java - - UTF-8 - 0.9.1 + + + 1.1.0 + 1.9.4 1.14 2.6 - 1.9.4 + 2.11.0 2.30 - 1.7.30 + 0.9.1 1.18.10 + 0.9.51 + 1.4.0 + 3.0.2 + 1.7.30 + + + io.github.resilience4j + resilience4j-retry + ${resilience4j-retry.version} + + + com.google.code.findbugs + jsr305 + ${jsr305.version} + + + org.apiguardian + apiguardian-api + ${apiguardian-api.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.glassfish.jersey.core + jersey-client + ${jersey.version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + + + org.glassfish.jersey.media + jersey-media-multipart + ${jersey.version} + + + org.glassfish.jersey.connectors + jersey-apache-connector + ${jersey.version} + + + org.glassfish.jersey.inject + jersey-hk2 + ${jersey.version} + + + org.symphonyoss.symphony + messageml + ${messageml.version} + + + io.jsonwebtoken + jjwt + ${jjwt.version} + + + commons-codec + commons-codec + ${commons-codec.version} + + + commons-io + commons-io + ${commons-io.version} + + + commons-beanutils + commons-beanutils + ${commons-beanutils.version} + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + javax.xml.bind + jaxb-api + 2.3.1 + + + javax.activation + javax.activation-api + + + + + + org.bitbucket.b_c + jose4j + 0.7.1 + + + + + junit + junit + 4.13 + test + + + com.github.tomakehurst + wiremock-jre8 + 2.26.3 + test + + + + @@ -145,114 +258,6 @@ - - - com.fasterxml.jackson.core - jackson-databind - 2.10.2 - - - org.slf4j - slf4j-api - ${slf4j.version} - - - org.slf4j - slf4j-log4j12 - ${slf4j.version} - - - org.projectlombok - lombok - ${lombok.version} - provided - - - junit - junit - 4.13 - test - - - org.glassfish.jersey.core - jersey-client - ${jersey.version} - - - org.glassfish.jersey.media - jersey-media-json-jackson - ${jersey.version} - - - org.glassfish.jersey.media - jersey-media-multipart - ${jersey.version} - - - org.glassfish.jersey.connectors - jersey-apache-connector - ${jersey.version} - - - org.glassfish.jersey.inject - jersey-hk2 - ${jersey.version} - - - org.symphonyoss.symphony - messageml - 0.9.47 - - - io.jsonwebtoken - jjwt - ${jjwt.version} - - - commons-codec - commons-codec - ${commons-codec.version} - - - commons-io - commons-io - ${commons-io.version} - - - commons-beanutils - commons-beanutils - ${commons-beanutils.version} - - - org.bitbucket.b_c - jose4j - 0.7.0 - - - javax.servlet - javax.servlet-api - 4.0.1 - provided - - - com.github.tomakehurst - wiremock-jre8 - 2.25.1 - test - - - javax.xml.bind - jaxb-api - 2.3.1 - - - javax.activation - javax.activation-api - - - - - ossrh @@ -293,4 +298,27 @@ + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Symphony Platform Solutions + platformsolutions@symphony.com + Symphony Communication Services + https://symphony.com/ + + + + + scm:git:git://github.com/SymphonyPlatformSolutions/symphony-api-client-java.git + scm:git:ssh://github.com/SymphonyPlatformSolutions/symphony-api-client-java.git + https://github.com/SymphonyPlatformSolutions/symphony-api-client-java + + diff --git a/src/lombok.config b/src/lombok.config new file mode 100644 index 000000000..1bb49ca0f --- /dev/null +++ b/src/lombok.config @@ -0,0 +1 @@ +lombok.log.fieldName=logger \ No newline at end of file diff --git a/src/main/java/authentication/ISymAuth.java b/src/main/java/authentication/ISymAuth.java index b54f99e63..6a48e4544 100644 --- a/src/main/java/authentication/ISymAuth.java +++ b/src/main/java/authentication/ISymAuth.java @@ -3,12 +3,16 @@ import exceptions.AuthenticationException; public interface ISymAuth { + void authenticate() throws AuthenticationException; void sessionAuthenticate() throws AuthenticationException; void kmAuthenticate() throws AuthenticationException; + String getSessionToken(); - void setSessionToken(String sessionToken); + @Deprecated void setSessionToken(String sessionToken); + String getKmToken(); - void setKmToken(String kmToken); + @Deprecated void setKmToken(String kmToken); + void logout(); } diff --git a/src/main/java/authentication/SymBotRSAAuth.java b/src/main/java/authentication/SymBotRSAAuth.java index 3ff5dbba4..f75251a43 100644 --- a/src/main/java/authentication/SymBotRSAAuth.java +++ b/src/main/java/authentication/SymBotRSAAuth.java @@ -1,54 +1,56 @@ package authentication; -import clients.ISymClient; +import static internal.jersey.JerseyHelper.isNotSuccess; +import static internal.jersey.JerseyHelper.isSuccess; + import clients.symphony.api.APIClient; import clients.symphony.api.constants.CommonConstants; import configuration.SymConfig; import exceptions.AuthenticationException; -import exceptions.SymClientException; -import java.io.*; -import java.security.GeneralSecurityException; +import internal.FileHelper; +import io.github.resilience4j.core.IntervalFunction; +import io.github.resilience4j.retry.Retry; +import io.github.resilience4j.retry.RetryConfig; +import io.github.resilience4j.retry.RetryRegistry; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import model.Token; +import org.glassfish.jersey.client.ClientConfig; +import utils.HttpClientBuilderHelper; +import utils.JwtHelper; + +import java.io.IOException; import java.security.PrivateKey; +import java.time.Duration; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.*; +import java.util.concurrent.TimeoutException; + import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import model.Token; -import org.glassfish.jersey.client.ClientConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import utils.HttpClientBuilderHelper; -import utils.JwtHelper; +import javax.xml.ws.WebServiceException; +@Slf4j public class SymBotRSAAuth extends APIClient implements ISymAuth { - private final Logger logger = LoggerFactory.getLogger(SymBotRSAAuth.class); - private String sessionToken = null; - private String kmToken = null; - private SymConfig config; - private Client sessionAuthClient; - private Client kmAuthClient; - private String jwt; - private long lastAuthTime = 0; - private int authRetries = 0; - public SymBotRSAAuth(SymConfig config) { - this.config = config; - ClientBuilder clientBuilder = HttpClientBuilderHelper.getHttpClientBuilderWithTruststore(config); - Client client = clientBuilder.build(); + private final SymConfig config; + private final Client sessionAuthClient; + private final Client kmAuthClient; - this.sessionAuthClient = client; - this.kmAuthClient = client; - - ClientConfig clientConfig = HttpClientBuilderHelper.getPodClientConfig(config); - ClientConfig kmClientConfig = HttpClientBuilderHelper.getKMClientConfig(config); + private String sessionToken; + private String kmToken; + private String jwt; - this.sessionAuthClient = clientBuilder.withConfig(clientConfig).build(); - this.kmAuthClient = clientBuilder.withConfig(kmClientConfig).build(); + public SymBotRSAAuth(SymConfig config) { + this( + config, + HttpClientBuilderHelper.getPodClientConfig(config), + HttpClientBuilderHelper.getKMClientConfig(config) + ); } public SymBotRSAAuth(SymConfig config, ClientConfig sessionAuthClientConfig, ClientConfig kmAuthClientConfig) { @@ -67,157 +69,53 @@ public SymBotRSAAuth(SymConfig config, ClientConfig sessionAuthClientConfig, Cli } @Override + @SneakyThrows public void authenticate() throws AuthenticationException { - PrivateKey privateKey = null; - try { - privateKey = JwtHelper.parseRSAPrivateKey(this.getRSAPrivateKeyFile(this.config)); - } catch (IOException | GeneralSecurityException e) { - logger.error("Error trying to parse RSA private key", e); - } - if (lastAuthTime == 0 || System.currentTimeMillis() - lastAuthTime > AuthEndpointConstants.WAIT_TIME) { - logger.info("Last auth time was {}", lastAuthTime); - logger.info("Now is {}", System.currentTimeMillis()); - jwt = JwtHelper.createSignedJwt(config.getBotUsername(), AuthEndpointConstants.JWT_EXPIRY_MS, privateKey); - ExecutorService executor = Executors.newFixedThreadPool(2); - Future sessionAuthFuture = executor.submit(() -> { - try { - sessionAuthenticate(); - return null; - } catch (AuthenticationException e) { - return e; - } - }); - Future kmAuthFuture = executor.submit(() -> { - try { - kmAuthenticate(); - return null; - } catch (AuthenticationException e) { - return e; - } - }); - executor.shutdown(); + this.jwt = JwtHelper.createSignedJwt(this.config.getBotUsername(), AuthEndpointConstants.JWT_EXPIRY_MS, this.loadPrivateKey()); - try { - int connectionTimeout = config.getConnectionTimeout(); - if (connectionTimeout == 0) { - connectionTimeout = 35000; - } - executor.awaitTermination(connectionTimeout, TimeUnit.MILLISECONDS); - executor.shutdownNow(); - if (!executor.isTerminated()) { - throw new AuthenticationException(new Exception("Timeout")); - } - } catch (InterruptedException e) { - throw new RuntimeException("Termination Interrupted"); - } + logger.debug("RSA authentication with retry : {}", this.config.getRetry()); + final RetryConfig config = RetryConfig.custom() + .maxAttempts(this.config.getRetry().getMaxAttempts()) + .intervalFunction(IntervalFunction.ofExponentialBackoff( + this.config.getRetry().getInitialIntervalMillis(), + this.config.getRetry().getMultiplier()) + ) + .build(); - try { - if (sessionAuthFuture.get() != null) { - throw sessionAuthFuture.get(); - } - if (kmAuthFuture.get() != null) { - throw kmAuthFuture.get(); - } - } catch (InterruptedException | ExecutionException e) { - logger.error("Interrupted Exception"); - } + final RetryRegistry registry = RetryRegistry.of(config); - lastAuthTime = System.currentTimeMillis(); - } else { - try { - logger.info("Re-authenticated too fast. Wait 30 seconds to try again."); - TimeUnit.SECONDS.sleep(AuthEndpointConstants.TIMEOUT); - authenticate(); - } catch (InterruptedException e) { - logger.error("Error with authentication", e); - } - } - } + registry.retry("Session auth").executeCheckedSupplier(() -> { + this.sessionAuthenticate(); + return null; + }); - protected InputStream getRSAPrivateKeyFile(final SymConfig config) throws FileNotFoundException { - final String dirPath = config.getBotPrivateKeyPath(); - final String keyName = config.getBotPrivateKeyName(); - final String path = dirPath + (dirPath.endsWith(File.separator) ? "" : File.separator) + keyName; - if (path.startsWith("classpath:")) { - return this.getClass().getResourceAsStream(path.replace("classpath:", "")); - } - return new FileInputStream(path); + registry.retry("KeyManager auth").executeCheckedSupplier(() -> { + this.kmAuthenticate(); + return null; + }); } @Override public void sessionAuthenticate() throws AuthenticationException { - Map token = new HashMap<>(); - token.put("token", jwt); - - Invocation.Builder builder = this.sessionAuthClient - .target(config.getPodUrl()) - .path(AuthEndpointConstants.SESSION_AUTH_PATH_RSA) - .request(MediaType.APPLICATION_JSON); - - try (Response response = builder.post(Entity.entity(token, MediaType.APPLICATION_JSON))) { - if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { - try { - handleError(response, null); - } catch (Exception e) { - logger.error("Unexpected error, retry authentication in 30 seconds"); - } - try { - TimeUnit.SECONDS.sleep(AuthEndpointConstants.TIMEOUT); - } catch (InterruptedException e) { - logger.error("Error with authentication", e); - } - if (authRetries++ > AuthEndpointConstants.MAX_AUTH_RETRY) { - logger.error("Max retries reached. Giving up on auth."); - return; - } - sessionAuthenticate(); - } else { - sessionToken = response.readEntity(Token.class).getToken(); - } - } catch (Exception e) { - throw new AuthenticationException(e); - } - } - - @Override - protected void handleError(Response response, ISymClient botClient) throws SymClientException { - super.handleError(response, botClient); + logger.debug("Starting session authentication..."); + this.sessionToken = this.doRsaAuth( + this.sessionAuthClient, + this.config.getPodUrl(), + AuthEndpointConstants.SESSION_AUTH_PATH_RSA, + this.jwt + ); } @Override public void kmAuthenticate() throws AuthenticationException { - Map token = new HashMap<>(); - token.put("token", jwt); - - Invocation.Builder builder = this.kmAuthClient - .target(config.getKeyAuthUrl()) - .path(AuthEndpointConstants.KEY_AUTH_PATH_RSA) - .request(MediaType.APPLICATION_JSON); - - try (Response response = builder.post(Entity.entity(token, MediaType.APPLICATION_JSON))) { - if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { - try { - handleError(response, null); - } catch (Exception e) { - logger.error("Unexpected error, retry authentication in 30 seconds"); - } - try { - TimeUnit.SECONDS.sleep(AuthEndpointConstants.TIMEOUT); - } catch (InterruptedException e) { - logger.error("Error with authentication", e); - } - if (authRetries++ > AuthEndpointConstants.MAX_AUTH_RETRY) { - logger.error("Max retries reached. Giving up on auth."); - return; - } - kmAuthenticate(); - } else { - kmToken = response.readEntity(Token.class).getToken(); - } - } catch (Exception e) { - throw new AuthenticationException(e); - } + logger.debug("Starting KM authentication..."); + this.kmToken = this.doRsaAuth( + this.kmAuthClient, + this.config.getKeyAuthUrl(), + AuthEndpointConstants.KEY_AUTH_PATH_RSA, + this.jwt + ); } @Override @@ -242,22 +140,27 @@ public void setKmToken(String kmToken) { @Override public void logout() { - logger.info("Logging out"); - Client client = ClientBuilder.newClient(); - String target = CommonConstants.HTTPS_PREFIX + config.getSessionAuthHost() + ":" + config.getSessionAuthPort(); - Invocation.Builder builder = client.target(target) - .path(AuthEndpointConstants.LOGOUT_PATH) - .request(MediaType.APPLICATION_JSON) - .header("sessionToken", getSessionToken()); + logger.warn("Logout is not needed for RSA authentication."); + } + + public String doRsaAuth(Client client, String target, String path, String jwt) throws AuthenticationException { - try (Response response = builder.post(null)) { - if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { - try { - handleError(response, null); - } catch (Exception e) { - logger.error("Unexpected error, retry logout in 30 seconds", e); - } + final Token payload = new Token(); + payload.setToken(jwt); + + final Invocation.Builder builder = client.target(target).path(path).request(MediaType.APPLICATION_JSON); + + try (final Response response = builder.post(Entity.entity(payload, MediaType.APPLICATION_JSON))) { + if (isNotSuccess(response)) { + throw new AuthenticationException(response.readEntity(String.class)); + } else { + return response.readEntity(Token.class).getToken(); } } } + + @SneakyThrows + private PrivateKey loadPrivateKey() { + return JwtHelper.parseRSAPrivateKey(FileHelper.readFile(this.config.getBotPrivateKeyPath() + this.config.getBotPrivateKeyName())); + } } diff --git a/src/main/java/configuration/RetryConfiguration.java b/src/main/java/configuration/RetryConfiguration.java new file mode 100644 index 000000000..20b0dfbdf --- /dev/null +++ b/src/main/java/configuration/RetryConfiguration.java @@ -0,0 +1,23 @@ +package configuration; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.apiguardian.api.API; + +/** + * Sub-configuration class for Retry parametrization. Experimental, the contract might change in the future. + */ +@ToString +@Getter @Setter +@API(status = API.Status.EXPERIMENTAL) +public class RetryConfiguration { + + public static final int DEFAULT_MAX_ATTEMPTS = 10; + public static final long DEFAULT_INITIAL_INTERVAL_MILLIS = 500L; + public static final double DEFAULT_MULTIPLIER = 1.5; + + private int maxAttempts = DEFAULT_MAX_ATTEMPTS; + private long initialIntervalMillis = DEFAULT_INITIAL_INTERVAL_MILLIS; + private double multiplier = DEFAULT_MULTIPLIER; +} diff --git a/src/main/java/configuration/SymConfig.java b/src/main/java/configuration/SymConfig.java index 707676fd5..2d7a19c44 100755 --- a/src/main/java/configuration/SymConfig.java +++ b/src/main/java/configuration/SymConfig.java @@ -2,6 +2,8 @@ import clients.symphony.api.constants.CommonConstants; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.Setter; import java.util.ArrayList; import java.util.Objects; @@ -48,6 +50,7 @@ public class SymConfig { private boolean showFirehoseErrors; private int connectionTimeout; private ArrayList supportedUriSchemes = new ArrayList<>(); + @Getter @Setter private RetryConfiguration retry = new RetryConfiguration(); public String getSessionAuthHost() { return sessionAuthHost; diff --git a/src/main/java/configuration/SymLoadBalancedConfig.java b/src/main/java/configuration/SymLoadBalancedConfig.java index b955b1fb6..453ee18d2 100644 --- a/src/main/java/configuration/SymLoadBalancedConfig.java +++ b/src/main/java/configuration/SymLoadBalancedConfig.java @@ -40,7 +40,7 @@ public String getAgentHost() { case random: if (currentAgentIndex == -1 || !isSticky) { rotateAgent(); - log.info("Returning random agent index #{}: {}", currentAgentIndex, agentServers.get(currentAgentIndex)); + logger.info("Returning random agent index #{}: {}", currentAgentIndex, agentServers.get(currentAgentIndex)); } return agentServers.get(currentAgentIndex); @@ -49,7 +49,7 @@ public String getAgentHost() { currentAgentIndex++; } String roundRobinAgentHost = agentServers.get(currentAgentIndex); - log.info("Returning round-robin agent index #{}: {}", currentAgentIndex, roundRobinAgentHost); + logger.info("Returning round-robin agent index #{}: {}", currentAgentIndex, roundRobinAgentHost); if (!isSticky) { rotateAgent(); } @@ -57,10 +57,10 @@ public String getAgentHost() { case external: if (actualAgentHost == null || !isSticky) { - log.info("Retrieving actual agent hostname.."); + logger.info("Retrieving actual agent hostname.."); rotateAgent(); } - log.info("Actual agent host: {}", actualAgentHost); + logger.info("Actual agent host: {}", actualAgentHost); return actualAgentHost; default: @@ -88,7 +88,7 @@ public void rotateAgent() { break; default: } - log.info("Agent rotated to: {}", newAgent); + logger.info("Agent rotated to: {}", newAgent); } /** @@ -105,11 +105,11 @@ protected String getActualAgentHost() { .get(); if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { - log.error("Unable to get actual Agent hostname, cause : {}", response); + logger.error("Unable to get actual Agent hostname, cause : {}", response); return null; } else { final String agentServerFqdn = response.readEntity(AgentInfo.class).getServerFqdn(); - log.debug("Agent FQDN={}", agentServerFqdn); + logger.debug("Agent FQDN={}", agentServerFqdn); return agentServerFqdn; } } @@ -118,7 +118,7 @@ public void cloneAttributes(SymConfig config) { try { BeanUtils.copyProperties(this, config); } catch (IllegalAccessException | InvocationTargetException e) { - log.error("Unable to copy properties from " + config + " to this.", e); + logger.error("Unable to copy properties from " + config + " to this.", e); } } } diff --git a/src/main/java/exceptions/AuthenticationException.java b/src/main/java/exceptions/AuthenticationException.java index 2adb530cd..1d3f7c284 100644 --- a/src/main/java/exceptions/AuthenticationException.java +++ b/src/main/java/exceptions/AuthenticationException.java @@ -1,18 +1,22 @@ package exceptions; public class AuthenticationException extends Exception { - private Exception rootException; + + public AuthenticationException(String message) { + super(message); + } public AuthenticationException(Exception rootException) { super(rootException); - this.rootException = rootException; } + @Deprecated public Exception getRootException() { - return rootException; + return (Exception) this.getCause(); } - public void setRootException(Exception rootException) { - this.rootException = rootException; + @Deprecated + public boolean hasRootException() { + return this.getRootException() != null; } } diff --git a/src/main/java/internal/FileHelper.java b/src/main/java/internal/FileHelper.java new file mode 100644 index 000000000..b942a1beb --- /dev/null +++ b/src/main/java/internal/FileHelper.java @@ -0,0 +1,46 @@ +package internal; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.apiguardian.api.API; +import utils.HttpClientBuilderHelper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +import javax.annotation.Nonnull; + +/** + * Helper class for reading files from either classpath or system path. Internal usage only. + */ +@Slf4j +@API(status = API.Status.INTERNAL) +public class FileHelper { + + /** + * Loads file content (as byte[]) from either system or classpath location. + * + * @param path Absolute file path or classpath location + * @return content of the file + */ + @SneakyThrows + public static byte[] readFile(@Nonnull final String path) { + + byte[] content; + + if(new File(path).exists()) { + content = IOUtils.toByteArray(new FileInputStream(path)); + logger.debug("File loaded from system path : {}", path); + } + else if (HttpClientBuilderHelper.class.getResource(path) != null) { + content = IOUtils.toByteArray(HttpClientBuilderHelper.class.getResourceAsStream(path.replace("classpath:", ""))); + logger.debug("File loaded from classpath location : {}", path); + } else { + throw new FileNotFoundException("Unable to load custom truststore from path : " + path); + } + + return content; + } +} diff --git a/src/main/java/internal/jersey/JerseyHelper.java b/src/main/java/internal/jersey/JerseyHelper.java new file mode 100644 index 000000000..f6f1a25de --- /dev/null +++ b/src/main/java/internal/jersey/JerseyHelper.java @@ -0,0 +1,32 @@ +package internal.jersey; + +import org.apiguardian.api.API; + +import javax.ws.rs.core.Response; + +/** + * Helper class that facilitate recurrent Jersey {@link javax.ws.rs.client.Client} operations. Internal usage only. + */ +@API(status = API.Status.INTERNAL) +public class JerseyHelper { + + /** + * Checks if {@link javax.ws.rs.client.Client} response is not successful. + * + * @param response Jersey {@link javax.ws.rs.client.Client} response. + * @return true if response is not successful, false otherwise. + */ + public static boolean isNotSuccess(Response response) { + return !isSuccess(response); + } + + /** + * Checks if {@link javax.ws.rs.client.Client} response is successful. + * + * @param response Jersey {@link javax.ws.rs.client.Client} response. + * @return true if response is successful, false otherwise. + */ + public static boolean isSuccess(Response response) { + return response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL; + } +} diff --git a/src/main/java/utils/jersey/NoCacheFeature.java b/src/main/java/internal/jersey/NoCacheFeature.java similarity index 86% rename from src/main/java/utils/jersey/NoCacheFeature.java rename to src/main/java/internal/jersey/NoCacheFeature.java index e069f86c0..12cb70aa1 100644 --- a/src/main/java/utils/jersey/NoCacheFeature.java +++ b/src/main/java/internal/jersey/NoCacheFeature.java @@ -1,4 +1,6 @@ -package utils.jersey; +package internal.jersey; + +import org.apiguardian.api.API; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; @@ -11,6 +13,7 @@ * @author Thibault Pensec * @since 24/02/2020 */ +@API(status = API.Status.INTERNAL) public class NoCacheFeature implements ClientRequestFilter { @Override diff --git a/src/main/java/utils/HttpClientBuilderHelper.java b/src/main/java/utils/HttpClientBuilderHelper.java index 2171778f1..a6fab5929 100644 --- a/src/main/java/utils/HttpClientBuilderHelper.java +++ b/src/main/java/utils/HttpClientBuilderHelper.java @@ -1,89 +1,73 @@ package utils; +import static org.apache.commons.lang3.StringUtils.isEmpty; + import configuration.SymConfig; -import java.io.*; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.util.Enumeration; -import javax.ws.rs.client.ClientBuilder; +import internal.FileHelper; +import internal.jersey.NoCacheFeature; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.glassfish.jersey.SslConfigurator; import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import utils.jersey.NoCacheFeature; -import static org.apache.commons.lang3.StringUtils.isEmpty; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.Enumeration; +import java.util.Optional; + +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.ws.rs.client.ClientBuilder; +@Slf4j public class HttpClientBuilderHelper { - private static final Logger logger = LoggerFactory.getLogger(HttpClientBuilderHelper.class); public static ClientBuilder getHttpClientBuilderWithTruststore(SymConfig config) { - KeyStore jksStore = getJksKeystore(); - ClientBuilder clientBuilder = ClientBuilder.newBuilder(); - - if (config.getTruststorePath() != null && jksStore != null) { - loadTrustStore(config, jksStore, clientBuilder); - } - - clientBuilder.register(NoCacheFeature.class); - - return clientBuilder; + return ClientBuilder.newBuilder().register(NoCacheFeature.class) + .sslContext(createSSLContext( + config.getTruststorePath(), + config.getTruststorePassword(), + null, + null + )); } public static ClientBuilder getHttpClientBotBuilder(SymConfig config) { - KeyStore pkcsStore = getPkcsKeystore(); - KeyStore jksStore = getJksKeystore(); - - try (InputStream keyStoreIS = loadInputStream(config.getBotCertPath() + config.getBotCertName())) { - if (pkcsStore != null) { - pkcsStore.load(keyStoreIS, config.getBotCertPassword().toCharArray()); - } - } catch (CertificateException | NoSuchAlgorithmException | IOException e) { - logger.error("Error loading bot keystore file", e); - } - - ClientBuilder clientBuilder = ClientBuilder.newBuilder().keyStore(pkcsStore, config.getBotCertPassword().toCharArray()); - if (config.getTruststorePath() != null && jksStore != null) { - loadTrustStore(config, jksStore, clientBuilder); - } - clientBuilder.register(NoCacheFeature.class); - - return clientBuilder; + return ClientBuilder.newBuilder().register(NoCacheFeature.class) + .sslContext(createSSLContext( + config.getTruststorePath(), + config.getTruststorePassword(), + config.getBotCertPath() + config.getBotCertName(), + config.getBotCertPassword() + )); } public static ClientBuilder getHttpClientAppBuilder(SymConfig config) { - KeyStore pkcsStore = getPkcsKeystore(); - KeyStore jksStore = getJksKeystore(); - - try (InputStream keyStoreIS = loadInputStream(config.getAppCertPath() + config.getAppCertName())) { - if (pkcsStore != null) { - pkcsStore.load(keyStoreIS, config.getBotCertPassword().toCharArray()); - } - } catch (CertificateException | NoSuchAlgorithmException | IOException e) { - logger.error("Error loading app keystore file", e); - } - - ClientBuilder clientBuilder = ClientBuilder.newBuilder().keyStore(pkcsStore, config.getAppCertPassword().toCharArray()); - if (config.getTruststorePath() != null && jksStore != null) { - loadTrustStore(config, jksStore, clientBuilder); - } - - clientBuilder.register(NoCacheFeature.class); - - return clientBuilder; + return ClientBuilder.newBuilder().register(NoCacheFeature.class) + .sslContext(createSSLContext( + config.getTruststorePath(), + config.getTruststorePassword(), + config.getAppCertPath() + config.getAppCertName(), + config.getAppCertPassword() + )); } public static ClientConfig getPodClientConfig(SymConfig config) { - String proxyURL = !isEmpty(config.getPodProxyURL()) ? - config.getPodProxyURL() : config.getProxyURL(); - String proxyUser = !isEmpty(config.getPodProxyUsername()) ? - config.getPodProxyUsername() : config.getProxyUsername(); - String proxyPass = !isEmpty(config.getPodProxyPassword()) ? - config.getPodProxyPassword() : config.getProxyPassword(); - + final String proxyURL = !isEmpty(config.getPodProxyURL()) ? config.getPodProxyURL() : config.getProxyURL(); + final String proxyUser = !isEmpty(config.getPodProxyUsername()) ? config.getPodProxyUsername() : config.getProxyUsername(); + final String proxyPass = !isEmpty(config.getPodProxyPassword()) ? config.getPodProxyPassword() : config.getProxyPassword(); return getClientConfig(config, proxyURL, proxyUser, proxyPass); } @@ -103,13 +87,13 @@ public static ClientConfig getKMClientConfig(SymConfig config) { return getClientConfig(config, kmProxyURL, kmProxyUser, kmProxyPass); } - private static ClientConfig getClientConfig( - SymConfig config, String proxyURL, String proxyUser, String proxyPass - ) { - ClientConfig clientConfig = new ClientConfig(); + private static ClientConfig getClientConfig(SymConfig config, String proxyURL, String proxyUser, String proxyPass) { + final ClientConfig clientConfig = new ClientConfig(); + if (config.getConnectionTimeout() == 0) { config.setConnectionTimeout(35000); } + clientConfig.property(ClientProperties.CONNECT_TIMEOUT, config.getConnectionTimeout()); clientConfig.property(ClientProperties.READ_TIMEOUT, config.getConnectionTimeout()); @@ -122,51 +106,32 @@ private static ClientConfig getClientConfig( } } - clientConfig.register(NoCacheFeature.class); - return clientConfig; } - private static void loadTrustStore(SymConfig config, KeyStore tks, ClientBuilder clientBuilder) { - try (InputStream trustStoreIS = loadInputStream(config.getTruststorePath())) { - tks.load(trustStoreIS, config.getTruststorePassword().toCharArray()); - clientBuilder.trustStore(tks); - Enumeration aliases = tks.aliases(); - while (aliases.hasMoreElements()) { - String alias = aliases.nextElement(); - logger.debug("Truststore entry's alias: " + alias); - } - logger.debug(tks.toString()); - } catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException e) { - logger.error("Error loading truststore", e); - } - } + @SneakyThrows + private static SSLContext createSSLContext( + @Nullable final String truststorePath, + @Nullable final String truststorePassword, + @Nullable final String keystorePath, + @Nullable final String keystorePassword + ) { + final SslConfigurator sslConfig = SslConfigurator.newInstance(); - private static InputStream loadInputStream(String fileName) throws FileNotFoundException { - if ((new File(fileName)).exists()) { - return new FileInputStream(fileName); - } else if (HttpClientBuilderHelper.class.getResource(fileName) != null) { - return HttpClientBuilderHelper.class.getResourceAsStream(fileName); - } else { - throw new FileNotFoundException(); + if (!isEmpty(truststorePath) && !isEmpty(truststorePassword)) { + byte[] trustStoreBytes = FileHelper.readFile(truststorePath); + sslConfig + .trustStoreBytes(trustStoreBytes) + .trustStorePassword(truststorePassword); } - } - private static KeyStore getPkcsKeystore() { - try { - return KeyStore.getInstance("PKCS12"); - } catch (KeyStoreException e) { - logger.error("Error creating PKCS keystore instance", e); + if (!isEmpty(keystorePath) && !isEmpty(keystorePassword)) { + byte[] keystoreBytes = FileHelper.readFile(keystorePath); + sslConfig + .trustStoreBytes(keystoreBytes) + .trustStorePassword(keystorePassword); } - return null; - } - private static KeyStore getJksKeystore() { - try { - return KeyStore.getInstance("JKS"); - } catch (KeyStoreException e) { - logger.error("Error creating JKS keystore instance", e); - } - return null; + return sslConfig.createSSLContext(); } } diff --git a/src/main/java/utils/JwtHelper.java b/src/main/java/utils/JwtHelper.java index 4a9f60a92..1663ca5ec 100644 --- a/src/main/java/utils/JwtHelper.java +++ b/src/main/java/utils/JwtHelper.java @@ -60,6 +60,11 @@ public static PrivateKey parseRSAPrivateKey(final InputStream pemPrivateKeyFile) return parseRSAPrivateKey(IOUtils.toString(pemPrivateKeyFile, Charset.defaultCharset())); } + public static PrivateKey parseRSAPrivateKey(final byte[] content) + throws IOException, GeneralSecurityException { + return parseRSAPrivateKey(IOUtils.toString(content, "utf-8")); + } + /** * Create a RSA Private Ket from a PEM String. It supports PKCS#1 and PKCS#8 string formats */ From 1a78bbd714f77f93a9d76a18da31d265eff6a1b1 Mon Sep 17 00:00:00 2001 From: symphony-thibault Date: Sat, 30 May 2020 19:45:12 +0200 Subject: [PATCH 2/4] SDK-75 Added doc for Retry configuration --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 70d81136b..99c37faae 100755 --- a/README.md +++ b/README.md @@ -102,6 +102,13 @@ can exclude the bot certificate section, all extension app sections and all opti // Optional: If custom URI schemes need to be supported by MessageML parser By setting this property, the default schemes (http and https) will be overridden "supportedUriSchemes": ["http", "https", "customScheme"], + + // Optional/experimental: exponential backoff configuration for retryiable actions + "retry": { + "maxAttempts": 10, + "initialIntervalMillis": 500, + "multiplier": 1.5 + } } ``` From 82f51c2c39bc165547453013ae147e6b23db9777 Mon Sep 17 00:00:00 2001 From: symphony-thibault Date: Sun, 31 May 2020 19:32:38 +0200 Subject: [PATCH 3/4] SDK-75 Added unit tests, simplified SymConfig class with Lombok usage --- README.md | 2 +- .../java/authentication/SymBotRSAAuth.java | 4 +- .../symphony/api/HealthcheckClient.java | 2 +- src/main/java/configuration/SymConfig.java | 402 +++--------------- src/main/java/internal/FileHelper.java | 25 +- .../java/utils/HttpClientBuilderHelper.java | 45 +- src/test/java/internal/FileHelperTest.java | 32 ++ 7 files changed, 140 insertions(+), 372 deletions(-) create mode 100644 src/test/java/internal/FileHelperTest.java diff --git a/README.md b/README.md index 99c37faae..88eb50d7d 100755 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ can exclude the bot certificate section, all extension app sections and all opti By setting this property, the default schemes (http and https) will be overridden "supportedUriSchemes": ["http", "https", "customScheme"], - // Optional/experimental: exponential backoff configuration for retryiable actions + // Optional/experimental: exponential backoff configuration for retries "retry": { "maxAttempts": 10, "initialIntervalMillis": 500, diff --git a/src/main/java/authentication/SymBotRSAAuth.java b/src/main/java/authentication/SymBotRSAAuth.java index f75251a43..bebb0756d 100644 --- a/src/main/java/authentication/SymBotRSAAuth.java +++ b/src/main/java/authentication/SymBotRSAAuth.java @@ -79,8 +79,8 @@ public void authenticate() throws AuthenticationException { .maxAttempts(this.config.getRetry().getMaxAttempts()) .intervalFunction(IntervalFunction.ofExponentialBackoff( this.config.getRetry().getInitialIntervalMillis(), - this.config.getRetry().getMultiplier()) - ) + this.config.getRetry().getMultiplier() + )) .build(); final RetryRegistry registry = RetryRegistry.of(config); diff --git a/src/main/java/clients/symphony/api/HealthcheckClient.java b/src/main/java/clients/symphony/api/HealthcheckClient.java index e768d254b..068cd295e 100644 --- a/src/main/java/clients/symphony/api/HealthcheckClient.java +++ b/src/main/java/clients/symphony/api/HealthcheckClient.java @@ -20,7 +20,7 @@ public HealthcheckClient(ISymClient client) { } public HealthcheckResponse performHealthCheck() { - boolean showFirehoseErrors = botClient.getConfig().getShowFirehoseErrors(); + boolean showFirehoseErrors = botClient.getConfig().isShowFirehoseErrors(); if (showFirehoseErrors) { HashMap parameters = new HashMap<>(); parameters.put(QueryParameterNames.SHOW_FIREHOSE_ERRORS.getName(), Boolean.TRUE); diff --git a/src/main/java/configuration/SymConfig.java b/src/main/java/configuration/SymConfig.java index 2d7a19c44..577f3a577 100755 --- a/src/main/java/configuration/SymConfig.java +++ b/src/main/java/configuration/SymConfig.java @@ -8,361 +8,85 @@ import java.util.ArrayList; import java.util.Objects; +@Getter @Setter @JsonIgnoreProperties(ignoreUnknown = true) public class SymConfig { + + private static final int DEFAULT_CONNECTION_TIMEOUT = 35000; + private static final int DEFAULT_READ_TIMEOUT = 60000; + + // ---------------------------------------------------------------------------------------------------------------// + // NETWORK + // private String sessionAuthHost; private int sessionAuthPort; + private String keyAuthHost; private int keyAuthPort; + private String keyManagerProxyURL; + private String keyManagerProxyUsername; + private String keyManagerProxyPassword; + private String podHost; private int podPort; + private String podProxyURL; + private String podProxyUsername; + private String podProxyPassword; + private String agentHost; private int agentPort; - private String botCertPath; - private String botCertName; - private String botCertPassword; - private String botEmailAddress; - private String appCertPath; - private String appCertName; - private String appCertPassword; + private String agentProxyURL; + private String agentProxyUsername; + private String agentProxyPassword; + private String proxyURL; private String proxyUsername; private String proxyPassword; - private String podProxyURL; - private String podProxyUsername; - private String podProxyPassword; - private String keyManagerProxyURL; - private String keyManagerProxyUsername; - private String keyManagerProxyPassword; - private int authTokenRefreshPeriod; - private String truststorePath; - private String truststorePassword; + + private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; + private int readTimeout = DEFAULT_READ_TIMEOUT; + + // ---------------------------------------------------------------------------------------------------------------// + // AUTHENTICATION + // private String botUsername; + private String botEmailAddress; + // rsa private String botPrivateKeyPath; private String botPrivateKeyName; + // cert + private String botCertPath; + private String botCertName; + private String botCertPassword; + + private String appId; + // rsa private String appPrivateKeyPath; private String appPrivateKeyName; - private String appId; + // cert + private String appCertPath; + private String appCertName; + private String appCertPassword; + + // ---------------------------------------------------------------------------------------------------------------// + // SSL + // + private String truststorePath; + private String truststorePassword; + + // ---------------------------------------------------------------------------------------------------------------// + // DATAFEED private int datafeedEventsThreadpoolSize; private int datafeedEventsErrorTimeout; private Boolean reuseDatafeedID; + + // ---------------------------------------------------------------------------------------------------------------// + // MISC + // private String authenticationFilterUrlPattern; private boolean showFirehoseErrors; - private int connectionTimeout; private ArrayList supportedUriSchemes = new ArrayList<>(); - @Getter @Setter private RetryConfiguration retry = new RetryConfiguration(); - - public String getSessionAuthHost() { - return sessionAuthHost; - } - - public void setSessionAuthHost(String sessionAuthHost) { - this.sessionAuthHost = sessionAuthHost; - } - - public int getSessionAuthPort() { - return sessionAuthPort; - } - - public void setSessionAuthPort(int sessionAuthPort) { - this.sessionAuthPort = sessionAuthPort; - } - - public String getKeyAuthHost() { - return keyAuthHost; - } - - public void setKeyAuthHost(String keyAuthHost) { - this.keyAuthHost = keyAuthHost; - } - - public int getKeyAuthPort() { - return keyAuthPort; - } - - public void setKeyAuthPort(int keyAuthPort) { - this.keyAuthPort = keyAuthPort; - } - - public String getPodHost() { - return podHost; - } - - public void setPodHost(String podHost) { - this.podHost = podHost; - } - - public int getPodPort() { - return podPort; - } - - public void setPodPort(int podPort) { - this.podPort = podPort; - } - - public String getAgentHost() { - return agentHost; - } - - public void setAgentHost(String agentHost) { - this.agentHost = agentHost; - } - - public int getAgentPort() { - return agentPort; - } - - public void setAgentPort(int agentPort) { - this.agentPort = agentPort; - } - - public String getBotCertPath() { - return botCertPath; - } - - public void setBotCertPath(String botCertPath) { - this.botCertPath = botCertPath; - } - - public String getBotCertName() { - return botCertName; - } - - public void setBotCertName(String botCertName) { - this.botCertName = botCertName; - } - - public String getBotCertPassword() { - return botCertPassword; - } - - public void setBotCertPassword(String botCertPassword) { - this.botCertPassword = botCertPassword; - } - - public String getBotEmailAddress() { - return botEmailAddress; - } - - public void setBotEmailAddress(String botEmailAddress) { - this.botEmailAddress = botEmailAddress; - } - - public String getAppCertPath() { - return appCertPath; - } - - public void setAppCertPath(String appCertPath) { - this.appCertPath = appCertPath; - } - - public String getAppCertName() { - return appCertName; - } - - public void setAppCertName(String appCertName) { - this.appCertName = appCertName; - } - - public String getAppCertPassword() { - return appCertPassword; - } - - public void setAppCertPassword(String appCertPassword) { - this.appCertPassword = appCertPassword; - } - - public String getProxyURL() { - return Objects.toString(proxyURL, "").trim(); - } - - public void setProxyURL(String proxyURL) { - this.proxyURL = proxyURL; - } - - public String getProxyUsername() { - return Objects.toString(proxyUsername, "").trim(); - } - - public void setProxyUsername(String proxyUsername) { - this.proxyUsername = proxyUsername; - } - - public String getProxyPassword() { - return Objects.toString(proxyPassword, "").trim(); - } - - public void setProxyPassword(String proxyPassword) { - this.proxyPassword = proxyPassword; - } - - public String getPodProxyURL() { - return Objects.toString(podProxyURL, "").trim(); - } - - public void setPodProxyURL(String podProxyURL) { - this.podProxyURL = podProxyURL; - } - - public String getPodProxyUsername() { - return Objects.toString(podProxyUsername, "").trim(); - } - - public void setPodProxyUsername(String podProxyUsername) { - this.podProxyUsername = podProxyUsername; - } - - public String getPodProxyPassword() { - return Objects.toString(podProxyPassword, "").trim(); - } - - public void setPodProxyPassword(String podProxyPassword) { - this.podProxyPassword = podProxyPassword; - } - - public String getKeyManagerProxyURL() { - return Objects.toString(keyManagerProxyURL, "").trim(); - } - - public void setKeyManagerProxyURL(String keyManagerProxyURL) { - this.keyManagerProxyURL = keyManagerProxyURL; - } - - public String getKeyManagerProxyUsername() { - return keyManagerProxyUsername; - } - - public void setKeyManagerProxyUsername(String keyManagerProxyUsername) { - this.keyManagerProxyUsername = keyManagerProxyUsername; - } - - public String getKeyManagerProxyPassword() { - return keyManagerProxyPassword; - } - - public void setKeyManagerProxyPassword(String keyManagerProxyPassword) { - this.keyManagerProxyPassword = keyManagerProxyPassword; - } - - public int getAuthTokenRefreshPeriod() { - return authTokenRefreshPeriod; - } - - public void setAuthTokenRefreshPeriod(int authTokenRefreshPeriod) { - this.authTokenRefreshPeriod = authTokenRefreshPeriod; - } - - public String getTruststorePath() { - return truststorePath; - } - - public void setTruststorePath(String truststorePath) { - this.truststorePath = truststorePath; - } - - public String getTruststorePassword() { - return truststorePassword; - } - - public void setTruststorePassword(String truststorePassword) { - this.truststorePassword = truststorePassword; - } - - public String getBotUsername() { - return botUsername; - } - - public void setBotUsername(String botUsername) { - this.botUsername = botUsername; - } - - public String getBotPrivateKeyPath() { - return botPrivateKeyPath; - } - - public void setBotPrivateKeyPath(String botPrivateKeyPath) { - this.botPrivateKeyPath = botPrivateKeyPath; - } - - public String getBotPrivateKeyName() { - return botPrivateKeyName; - } - - public void setBotPrivateKeyName(String botPrivateKeyName) { - this.botPrivateKeyName = botPrivateKeyName; - } - - public String getAppPrivateKeyPath() { - return appPrivateKeyPath; - } - - public void setAppPrivateKeyPath(String appPrivateKeyPath) { - this.appPrivateKeyPath = appPrivateKeyPath; - } - - public String getAppPrivateKeyName() { - return appPrivateKeyName; - } - - public void setAppPrivateKeyName(String appPrivateKeyName) { - this.appPrivateKeyName = appPrivateKeyName; - } - - public String getAppId() { - return appId; - } - - public void setAppId(String appId) { - this.appId = appId; - } - - public int getDatafeedEventsThreadpoolSize() { - return datafeedEventsThreadpoolSize; - } - - public void setDatafeedEventsThreadpoolSize(int datafeedEventsThreadpoolSize) { - this.datafeedEventsThreadpoolSize = datafeedEventsThreadpoolSize; - } - - public int getDatafeedEventsErrorTimeout() { - return datafeedEventsErrorTimeout; - } - - public void setDatafeedEventsErrorTimeout(int datafeedEventsErrorTimeout) { - this.datafeedEventsErrorTimeout = datafeedEventsErrorTimeout; - } - - public Boolean getReuseDatafeedID() { - return reuseDatafeedID; - } - - public void setReuseDatafeedID(boolean reuseDatafeedID) { - this.reuseDatafeedID = reuseDatafeedID; - } - - public String getAuthenticationFilterUrlPattern() { - return authenticationFilterUrlPattern; - } - - public void setAuthenticationFilterUrlPattern(String authenticationFilterUrlPattern) { - this.authenticationFilterUrlPattern = authenticationFilterUrlPattern; - } - - public boolean getShowFirehoseErrors() { - return showFirehoseErrors; - } - - public void setShowFirehoseErrors(boolean showFirehoseErrors) { - this.showFirehoseErrors = showFirehoseErrors; - } - - public int getConnectionTimeout() { - return connectionTimeout; - } - - public void setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } + private RetryConfiguration retry = new RetryConfiguration(); public String getAgentUrl() { String port = (this.getAgentPort() == 443) ? "" : ":" + this.getAgentPort(); @@ -370,21 +94,17 @@ public String getAgentUrl() { } public String getPodUrl() { - String port = (podPort == 443) ? "" : ":" + podPort; - return CommonConstants.HTTPS_PREFIX + podHost + port; + String port = (this.getPodPort() == 443) ? "" : ":" + this.getPodPort(); + return CommonConstants.HTTPS_PREFIX + this.getPodHost() + port; } public String getKeyAuthUrl() { - String port = (keyAuthPort == 443) ? "" : ":" + keyAuthPort; - return CommonConstants.HTTPS_PREFIX + keyAuthHost + port; + String port = (this.getKeyAuthPort() == 443) ? "" : ":" + this.getKeyAuthPort(); + return CommonConstants.HTTPS_PREFIX + this.getKeyAuthHost() + port; } public String getSessionAuthUrl() { - String port = (sessionAuthPort == 443) ? "" : ":" + sessionAuthPort; - return CommonConstants.HTTPS_PREFIX + sessionAuthHost + port; - } - - public ArrayList getSupportedUriSchemes() { - return supportedUriSchemes; + String port = (this.getSessionAuthPort() == 443) ? "" : ":" + this.getSessionAuthPort(); + return CommonConstants.HTTPS_PREFIX + this.getSessionAuthHost() + port; } } diff --git a/src/main/java/internal/FileHelper.java b/src/main/java/internal/FileHelper.java index b942a1beb..1dfb136af 100644 --- a/src/main/java/internal/FileHelper.java +++ b/src/main/java/internal/FileHelper.java @@ -1,10 +1,10 @@ package internal; +import static org.apache.commons.io.IOUtils.toByteArray; + import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.IOUtils; import org.apiguardian.api.API; -import utils.HttpClientBuilderHelper; import java.io.File; import java.io.FileInputStream; @@ -30,17 +30,26 @@ public static byte[] readFile(@Nonnull final String path) { byte[] content; - if(new File(path).exists()) { - content = IOUtils.toByteArray(new FileInputStream(path)); + if(!isClasspath(path) && new File(path).exists()) { + content = toByteArray(new FileInputStream(path)); logger.debug("File loaded from system path : {}", path); } - else if (HttpClientBuilderHelper.class.getResource(path) != null) { - content = IOUtils.toByteArray(HttpClientBuilderHelper.class.getResourceAsStream(path.replace("classpath:", ""))); + else if (FileHelper.class.getResource(classpath(path)) != null) { + content = toByteArray(FileHelper.class.getResourceAsStream(classpath(path))); logger.debug("File loaded from classpath location : {}", path); - } else { - throw new FileNotFoundException("Unable to load custom truststore from path : " + path); + } + else { + throw new FileNotFoundException("Unable to load file from path : " + path); } return content; } + + private static String classpath(String path) { + return path.replace("classpath:", ""); + } + + private static boolean isClasspath(String path) { + return path.startsWith("classpath:"); + } } diff --git a/src/main/java/utils/HttpClientBuilderHelper.java b/src/main/java/utils/HttpClientBuilderHelper.java index a6fab5929..ed61d46d8 100644 --- a/src/main/java/utils/HttpClientBuilderHelper.java +++ b/src/main/java/utils/HttpClientBuilderHelper.java @@ -31,6 +31,9 @@ import javax.net.ssl.SSLContext; import javax.ws.rs.client.ClientBuilder; +/** + * Set of support methods for creating and initializing Jersey {@link javax.ws.rs.client.Client} for Pod, Agent, KM, etc. + */ @Slf4j public class HttpClientBuilderHelper { @@ -65,37 +68,37 @@ public static ClientBuilder getHttpClientAppBuilder(SymConfig config) { } public static ClientConfig getPodClientConfig(SymConfig config) { - final String proxyURL = !isEmpty(config.getPodProxyURL()) ? config.getPodProxyURL() : config.getProxyURL(); - final String proxyUser = !isEmpty(config.getPodProxyUsername()) ? config.getPodProxyUsername() : config.getProxyUsername(); - final String proxyPass = !isEmpty(config.getPodProxyPassword()) ? config.getPodProxyPassword() : config.getProxyPassword(); - return getClientConfig(config, proxyURL, proxyUser, proxyPass); + return getClientConfig( + config, + getOr(config.getPodProxyURL(), config.getProxyURL()), + getOr(config.getPodProxyUsername(), config.getProxyUsername()), + getOr(config.getPodProxyPassword(), config.getProxyPassword()) + ); } public static ClientConfig getAgentClientConfig(SymConfig config) { - String proxyURL = config.getProxyURL(); - String proxyUser = config.getProxyUsername(); - String proxyPass = config.getProxyPassword(); - - return getClientConfig(config, proxyURL, proxyUser, proxyPass); + return getClientConfig( + config, + getOr(config.getAgentProxyURL(), config.getProxyURL()), + getOr(config.getAgentProxyUsername(), config.getProxyUsername()), + getOr(config.getAgentProxyPassword(), config.getProxyPassword()) + ); } public static ClientConfig getKMClientConfig(SymConfig config) { - String kmProxyURL = config.getKeyManagerProxyURL(); - String kmProxyUser = config.getKeyManagerProxyUsername(); - String kmProxyPass = config.getKeyManagerProxyPassword(); - - return getClientConfig(config, kmProxyURL, kmProxyUser, kmProxyPass); + return getClientConfig( + config, + getOr(config.getKeyManagerProxyURL(), config.getProxyURL()), + getOr(config.getKeyManagerProxyUsername(), config.getProxyURL()), + getOr(config.getKeyManagerProxyPassword(), config.getProxyURL()) + ); } private static ClientConfig getClientConfig(SymConfig config, String proxyURL, String proxyUser, String proxyPass) { final ClientConfig clientConfig = new ClientConfig(); - if (config.getConnectionTimeout() == 0) { - config.setConnectionTimeout(35000); - } - clientConfig.property(ClientProperties.CONNECT_TIMEOUT, config.getConnectionTimeout()); - clientConfig.property(ClientProperties.READ_TIMEOUT, config.getConnectionTimeout()); + clientConfig.property(ClientProperties.READ_TIMEOUT, config.getReadTimeout()); if (!isEmpty(proxyURL)) { clientConfig.connectorProvider(new ApacheConnectorProvider()); @@ -134,4 +137,8 @@ private static SSLContext createSSLContext( return sslConfig.createSSLContext(); } + + private static String getOr(final String preferredValue, final String fallbackValue) { + return !isEmpty(preferredValue) ? preferredValue : fallbackValue; + } } diff --git a/src/test/java/internal/FileHelperTest.java b/src/test/java/internal/FileHelperTest.java new file mode 100644 index 000000000..f9040472d --- /dev/null +++ b/src/test/java/internal/FileHelperTest.java @@ -0,0 +1,32 @@ +package internal; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public class FileHelperTest { + + @Test + public void should_read_file_from_classpath() { + assertNotNull(FileHelper.readFile("/avatar.png")); + assertNotNull(FileHelper.readFile("classpath:/avatar.png")); + } + + @Test + public void should_read_file_from_system() throws IOException { + final Path file = Files.createTempFile(UUID.randomUUID().toString(), ".txt"); + assertNotNull(FileHelper.readFile(file.toAbsolutePath().toString())); + Files.delete(file); + } + + @Test(expected = FileNotFoundException.class) + public void fail_to_read_file() { + FileHelper.readFile(UUID.randomUUID().toString() + ".abc"); + } +} \ No newline at end of file From 273da3e8b502f3a2dc9c432158a2b7157162771f Mon Sep 17 00:00:00 2001 From: symphony-thibault Date: Mon, 1 Jun 2020 21:24:00 +0200 Subject: [PATCH 4/4] SDK-75 Added more unit tests, better error management in SymBotRSAAuth when JWT needs to be refreshed --- .../java/authentication/SymBotRSAAuth.java | 41 ++++++++--- src/main/java/clients/SymBotClient.java | 6 +- src/main/java/configuration/SymConfig.java | 4 +- src/main/java/internal/FileHelper.java | 5 ++ .../java/internal/jersey/JerseyHelper.java | 10 +++ .../java/utils/HttpClientBuilderHelper.java | 32 ++++++--- .../it/authentication/SymBotRSAAuthTest.java | 68 ++++++++++++------- src/test/java/it/commons/BotTest.java | 8 ++- src/test/resources/bot-config.json | 9 ++- src/test/resources/log4j.properties | 2 +- 10 files changed, 137 insertions(+), 48 deletions(-) diff --git a/src/main/java/authentication/SymBotRSAAuth.java b/src/main/java/authentication/SymBotRSAAuth.java index bebb0756d..acdac415a 100644 --- a/src/main/java/authentication/SymBotRSAAuth.java +++ b/src/main/java/authentication/SymBotRSAAuth.java @@ -8,6 +8,7 @@ import configuration.SymConfig; import exceptions.AuthenticationException; import internal.FileHelper; +import internal.jersey.JerseyHelper; import io.github.resilience4j.core.IntervalFunction; import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.RetryConfig; @@ -20,12 +21,16 @@ import utils.JwtHelper; import java.io.IOException; +import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.time.Duration; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeoutException; +import javax.ws.rs.ProcessingException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; @@ -55,7 +60,7 @@ public SymBotRSAAuth(SymConfig config) { public SymBotRSAAuth(SymConfig config, ClientConfig sessionAuthClientConfig, ClientConfig kmAuthClientConfig) { this.config = config; - ClientBuilder clientBuilder = HttpClientBuilderHelper.getHttpClientBuilderWithTruststore(config); + final ClientBuilder clientBuilder = HttpClientBuilderHelper.getHttpClientBuilderWithTruststore(config); if (sessionAuthClientConfig != null) { this.sessionAuthClient = clientBuilder.withConfig(sessionAuthClientConfig).build(); } else { @@ -72,9 +77,9 @@ public SymBotRSAAuth(SymConfig config, ClientConfig sessionAuthClientConfig, Cli @SneakyThrows public void authenticate() throws AuthenticationException { - this.jwt = JwtHelper.createSignedJwt(this.config.getBotUsername(), AuthEndpointConstants.JWT_EXPIRY_MS, this.loadPrivateKey()); + this.refreshJwt(); - logger.debug("RSA authentication with retry : {}", this.config.getRetry()); + logger.debug("RSA authentication with {}", this.config.getRetry()); final RetryConfig config = RetryConfig.custom() .maxAttempts(this.config.getRetry().getMaxAttempts()) .intervalFunction(IntervalFunction.ofExponentialBackoff( @@ -105,6 +110,7 @@ public void sessionAuthenticate() throws AuthenticationException { AuthEndpointConstants.SESSION_AUTH_PATH_RSA, this.jwt ); + logger.debug("Session token successfully retrieved !"); } @Override @@ -116,6 +122,7 @@ public void kmAuthenticate() throws AuthenticationException { AuthEndpointConstants.KEY_AUTH_PATH_RSA, this.jwt ); + logger.debug("KM token successfully retrieved !"); } @Override @@ -152,15 +159,33 @@ public String doRsaAuth(Client client, String target, String path, String jwt) t try (final Response response = builder.post(Entity.entity(payload, MediaType.APPLICATION_JSON))) { if (isNotSuccess(response)) { - throw new AuthenticationException(response.readEntity(String.class)); + + if(response.getStatus() == 401) { + logger.warn("JWT has expired, it will be refreshed before retry."); + // the JWT has expired, let's refresh it before retry + this.refreshJwt(); + } + + final String responsePayload = JerseyHelper.read(response, String.class); + throw new AuthenticationException(responsePayload); } else { - return response.readEntity(Token.class).getToken(); + return JerseyHelper.read(response, Token.class).getToken(); } + } catch (ProcessingException ex) { + logger.error("Unable to process RSA authentication request.", ex); + throw new AuthenticationException(ex); } } - @SneakyThrows - private PrivateKey loadPrivateKey() { - return JwtHelper.parseRSAPrivateKey(FileHelper.readFile(this.config.getBotPrivateKeyPath() + this.config.getBotPrivateKeyName())); + private void refreshJwt() throws AuthenticationException { + try { + final String privateKeyPath = FileHelper.path(this.config.getBotPrivateKeyPath(), this.config.getBotPrivateKeyName()); + final PrivateKey privateKey = JwtHelper.parseRSAPrivateKey(FileHelper.readFile(privateKeyPath)); + this.jwt = JwtHelper.createSignedJwt(this.config.getBotUsername(), AuthEndpointConstants.JWT_EXPIRY_MS, privateKey); + logger.debug("JWT successfully refresh for RSA authentication"); + } catch (IOException | GeneralSecurityException ex) { + logger.error("Something went wrong while refreshing JWT, see cause below:", ex); + throw new AuthenticationException(ex); + } } } diff --git a/src/main/java/clients/SymBotClient.java b/src/main/java/clients/SymBotClient.java index c53d2db30..8c0335861 100755 --- a/src/main/java/clients/SymBotClient.java +++ b/src/main/java/clients/SymBotClient.java @@ -82,7 +82,11 @@ private static SymBotClient initBot(String configPath, Cla try { botAuth.authenticate(); } catch (AuthenticationException e) { - throw e.getRootException(); + if(e.hasRootException()) { + throw e.getRootException(); + } else { + throw e; + } } return new SymBotClient(config, botAuth); } diff --git a/src/main/java/configuration/SymConfig.java b/src/main/java/configuration/SymConfig.java index 577f3a577..5f415f657 100755 --- a/src/main/java/configuration/SymConfig.java +++ b/src/main/java/configuration/SymConfig.java @@ -12,8 +12,8 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class SymConfig { - private static final int DEFAULT_CONNECTION_TIMEOUT = 35000; - private static final int DEFAULT_READ_TIMEOUT = 60000; + private static final int DEFAULT_CONNECTION_TIMEOUT = 10_000; + private static final int DEFAULT_READ_TIMEOUT = 35_000; // ---------------------------------------------------------------------------------------------------------------// // NETWORK diff --git a/src/main/java/internal/FileHelper.java b/src/main/java/internal/FileHelper.java index 1dfb136af..0ba75fe65 100644 --- a/src/main/java/internal/FileHelper.java +++ b/src/main/java/internal/FileHelper.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.nio.file.Paths; import javax.annotation.Nonnull; @@ -45,6 +46,10 @@ else if (FileHelper.class.getResource(classpath(path)) != null) { return content; } + public static String path(String first, String... more) { + return Paths.get(first, more).toString(); + } + private static String classpath(String path) { return path.replace("classpath:", ""); } diff --git a/src/main/java/internal/jersey/JerseyHelper.java b/src/main/java/internal/jersey/JerseyHelper.java index f6f1a25de..0cce5f1a9 100644 --- a/src/main/java/internal/jersey/JerseyHelper.java +++ b/src/main/java/internal/jersey/JerseyHelper.java @@ -29,4 +29,14 @@ public static boolean isNotSuccess(Response response) { public static boolean isSuccess(Response response) { return response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL; } + + /** + * Read Jersey {@link javax.ws.rs.client.Client} response and map it to a {@link Class}. + * @param response The Jersey {@link javax.ws.rs.client.Client} response. + * @param clz Type of the mapping class. + * @return mapped response. + */ + public static T read(final Response response, final Class clz) { + return response.readEntity(clz); + } } diff --git a/src/main/java/utils/HttpClientBuilderHelper.java b/src/main/java/utils/HttpClientBuilderHelper.java index ed61d46d8..8ed1a1ec8 100644 --- a/src/main/java/utils/HttpClientBuilderHelper.java +++ b/src/main/java/utils/HttpClientBuilderHelper.java @@ -15,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -24,8 +25,11 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.util.Collections; import java.util.Enumeration; +import java.util.List; import java.util.Optional; +import java.util.stream.StreamSupport; import javax.annotation.Nullable; import javax.net.ssl.SSLContext; @@ -37,6 +41,8 @@ @Slf4j public class HttpClientBuilderHelper { + private static final String TRUSTSTORE_FORMAT = "JKS"; + public static ClientBuilder getHttpClientBuilderWithTruststore(SymConfig config) { return ClientBuilder.newBuilder().register(NoCacheFeature.class) .sslContext(createSSLContext( @@ -52,7 +58,7 @@ public static ClientBuilder getHttpClientBotBuilder(SymConfig config) { .sslContext(createSSLContext( config.getTruststorePath(), config.getTruststorePassword(), - config.getBotCertPath() + config.getBotCertName(), + FileHelper.path(config.getBotCertPath(), config.getBotCertName()), config.getBotCertPassword() )); } @@ -62,7 +68,7 @@ public static ClientBuilder getHttpClientAppBuilder(SymConfig config) { .sslContext(createSSLContext( config.getTruststorePath(), config.getTruststorePassword(), - config.getAppCertPath() + config.getAppCertName(), + FileHelper.path(config.getAppCertPath(), config.getAppCertName()), config.getAppCertPassword() )); } @@ -122,17 +128,25 @@ private static SSLContext createSSLContext( final SslConfigurator sslConfig = SslConfigurator.newInstance(); if (!isEmpty(truststorePath) && !isEmpty(truststorePassword)) { - byte[] trustStoreBytes = FileHelper.readFile(truststorePath); - sslConfig - .trustStoreBytes(trustStoreBytes) - .trustStorePassword(truststorePassword); + final byte[] trustStoreBytes = FileHelper.readFile(truststorePath); + final KeyStore truststore = KeyStore.getInstance(TRUSTSTORE_FORMAT); + truststore.load(new ByteArrayInputStream(trustStoreBytes), truststorePassword.toCharArray()); + // if logging debug is enabled, we print the truststore entries + if(logger.isDebugEnabled()) { + final List aliases = Collections.list(truststore.aliases()); + logger.debug("Your custom truststore ('{}') contains {} entries :", truststorePath, aliases.size()); + for (String alias : aliases) { + logger.debug("# {}", alias); + } + } + sslConfig.trustStore(truststore); } if (!isEmpty(keystorePath) && !isEmpty(keystorePassword)) { - byte[] keystoreBytes = FileHelper.readFile(keystorePath); + final byte[] keystoreBytes = FileHelper.readFile(keystorePath); sslConfig - .trustStoreBytes(keystoreBytes) - .trustStorePassword(keystorePassword); + .keyStoreBytes(keystoreBytes) + .keyStorePassword(keystorePassword); } return sslConfig.createSSLContext(); diff --git a/src/test/java/it/authentication/SymBotRSAAuthTest.java b/src/test/java/it/authentication/SymBotRSAAuthTest.java index 326d79f79..73555e812 100644 --- a/src/test/java/it/authentication/SymBotRSAAuthTest.java +++ b/src/test/java/it/authentication/SymBotRSAAuthTest.java @@ -6,38 +6,58 @@ import it.commons.ServerTest; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; + +import org.junit.Before; import org.junit.Test; import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static it.commons.BotTest.stubPost; import static org.junit.Assert.*; public class SymBotRSAAuthTest extends ServerTest { + + private final SymBotRSAAuth symBotRSAAuth = new SymBotRSAAuth(config); + + @Before + public void setup() { + // stub KM auth response + stubPost( + AuthEndpointConstants.KEY_AUTH_PATH_RSA, + "{ \"token\": \"0100e4feOiJSUzUxMiJ97oqGf729d1866f\", \"name\": \"sessionToken\" }" + ); + } + + @Test + public void should_authenticate_with_success() throws AuthenticationException { + + // session auth returns 200 + stubPost( + AuthEndpointConstants.SESSION_AUTH_PATH_RSA, + "{ \"token\": \"eyJhbGciOiJSUzUxMiJ97oqG1Kd28l1FpQ\", \"name\": \"sessionToken\" }" + ); + + this.symBotRSAAuth.authenticate(); + assertNotNull(this.symBotRSAAuth.getSessionToken()); + assertEquals("eyJhbGciOiJSUzUxMiJ97oqG1Kd28l1FpQ", this.symBotRSAAuth.getSessionToken()); + assertNotNull(this.symBotRSAAuth.getKmToken()); + assertEquals("0100e4feOiJSUzUxMiJ97oqGf729d1866f", this.symBotRSAAuth.getKmToken()); + } + @Test - public void authenticateSuccess() { - stubFor(post(urlEqualTo(AuthEndpointConstants.SESSION_AUTH_PATH_RSA)) - .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) - .willReturn(aResponse() - .withStatus(200) - .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .withBody("{ \"token\": \"eyJhbGciOiJSUzUxMiJ97oqG1Kd28l1FpQ\", \"name\": \"sessionToken\" }"))); - - stubFor(post(urlEqualTo(AuthEndpointConstants.KEY_AUTH_PATH_RSA)) - .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) - .willReturn(aResponse() - .withStatus(200) - .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .withBody("{ \"token\": \"0100e4feOiJSUzUxMiJ97oqGf729d1866f\", \"name\": \"keyManagerToken\" }"))); - - SymBotRSAAuth symBotRSAAuth = new SymBotRSAAuth(config); + public void should_fail_to_authenticate_session() { + + final String responsePayload = "{ \"error\": \"Service unavailable\" }"; + + // session auth returns 503 + stubPost( + AuthEndpointConstants.SESSION_AUTH_PATH_RSA, + responsePayload, + 503 + ); try { - symBotRSAAuth.authenticate(); - - assertNotNull(symBotRSAAuth.getSessionToken()); - assertEquals("eyJhbGciOiJSUzUxMiJ97oqG1Kd28l1FpQ", symBotRSAAuth.getSessionToken()); - assertNotNull(symBotRSAAuth.getKmToken()); - assertEquals("0100e4feOiJSUzUxMiJ97oqGf729d1866f", symBotRSAAuth.getKmToken()); - } catch (AuthenticationException e) { - fail(); + this.symBotRSAAuth.authenticate(); + } catch (AuthenticationException ex) { + assertEquals(responsePayload, ex.getMessage()); } } } diff --git a/src/test/java/it/commons/BotTest.java b/src/test/java/it/commons/BotTest.java index 29e41c4d3..f20258ef2 100644 --- a/src/test/java/it/commons/BotTest.java +++ b/src/test/java/it/commons/BotTest.java @@ -65,11 +65,15 @@ protected static StubMapping stubDelete(String url, String returnedJsonResponse) ); } - protected static StubMapping stubPost(String url, String returnedJsonResponse) { + public static StubMapping stubPost(String url, String returnedJsonResponse) { + return stubPost(url,returnedJsonResponse, 200); + } + + public static StubMapping stubPost(String url, String returnedJsonResponse, int status) { return stubFor(post(urlEqualTo(url)) .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) .willReturn(aResponse() - .withStatus(200) + .withStatus(status) .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .withBody(returnedJsonResponse)) ); diff --git a/src/test/resources/bot-config.json b/src/test/resources/bot-config.json index 682ebc998..910b510ed 100644 --- a/src/test/resources/bot-config.json +++ b/src/test/resources/bot-config.json @@ -14,7 +14,14 @@ "botPrivateKeyName": "testprivatekey.pkcs8", "botUsername": "testbot", "authTokenRefreshPeriod": "30", + "connectionTimeout": 1000, + "truststorePath": "src/test/resources/testkeystore.jks", "truststorePassword": "123456", - "connectionTimeout": 1000 + + "retry": { + "maxAttempts": 2, + "initialIntervalMillis": 200, + "multiplier": 1.5 + } } diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties index ed367509d..3e9cf4126 100644 --- a/src/test/resources/log4j.properties +++ b/src/test/resources/log4j.properties @@ -1,4 +1,4 @@ -log4j.rootLogger=INFO, stdout +log4j.rootLogger=DEBUG, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout