diff --git a/README.md b/README.md
index 10fa16f1..27ae8db4 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,8 @@ switcher.snapshot.skipvalidation -> true/false Skip snapshotValidation() that ca
switcher.snapshot.updateinterval -> Enable the Snapshot Auto Update given an interval of time - e.g. 1s (s: seconds, m: minutes)
switcher.silent -> true/false Contingency in case of some problem with connectivity with the API
switcher.retry -> Time given to the module to re-establish connectivity with the API - e.g. 5s (s: seconds - m: minutes - h: hours)
+switcher.truststore.path -> Path to the truststore file
+switcher.truststore.password -> Truststore password
(Java 8 applications only)
switcher.regextimeout -> Time in ms given to Timed Match Worker used for offline Regex (ReDoS safety mechanism) - 3000 default value
diff --git a/pom.xml b/pom.xml
index cbac6b43..74648cf0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,9 +51,9 @@
${java.version}
- 2.39
- 2.39
- 2.39
+ 2.40
+ 2.40
+ 2.40
2.10.1
@@ -63,7 +63,7 @@
5.0.0-alpha.11
- 5.9.2
+ 5.9.3
1.9.1
diff --git a/src/main/java/com/github/switcherapi/client/ContextBuilder.java b/src/main/java/com/github/switcherapi/client/ContextBuilder.java
index 16c64a71..8909c71a 100644
--- a/src/main/java/com/github/switcherapi/client/ContextBuilder.java
+++ b/src/main/java/com/github/switcherapi/client/ContextBuilder.java
@@ -110,4 +110,14 @@ public ContextBuilder offlineMode(boolean offlineMode) {
return this;
}
+ public ContextBuilder truststorePath(String truststorePath) {
+ properties.setTruststorePath(truststorePath);
+ return this;
+ }
+
+ public ContextBuilder truststorePassword(String truststorePassword) {
+ properties.setTruststorePassword(truststorePassword);
+ return this;
+ }
+
}
diff --git a/src/main/java/com/github/switcherapi/client/SwitcherProperties.java b/src/main/java/com/github/switcherapi/client/SwitcherProperties.java
index e513913c..bb030f8b 100644
--- a/src/main/java/com/github/switcherapi/client/SwitcherProperties.java
+++ b/src/main/java/com/github/switcherapi/client/SwitcherProperties.java
@@ -51,6 +51,10 @@ class SwitcherProperties {
private boolean offlineMode;
+ private String truststorePath;
+
+ private String truststorePassword;
+
public SwitcherProperties() {
this.environment = DEFAULT_ENV;
this.regexTimeout = DEFAULT_REGEX_TIMEOUT;
@@ -72,6 +76,8 @@ public void loadFromProperties(Properties prop) {
setOfflineMode(Boolean.parseBoolean(SwitcherUtils.resolveProperties(ContextKey.OFFLINE_MODE.getParam(), prop)));
setRetryAfter(SwitcherUtils.resolveProperties(ContextKey.RETRY_AFTER.getParam(), prop));
setRegexTimeout(SwitcherUtils.resolveProperties(ContextKey.REGEX_TIMEOUT.getParam(), prop));
+ setTruststorePath(SwitcherUtils.resolveProperties(ContextKey.TRUSTSTORE_PATH.getParam(), prop));
+ setTruststorePassword(SwitcherUtils.resolveProperties(ContextKey.TRUSTSTORE_PASSWORD.getParam(), prop));
}
public T getValue(ContextKey contextKey, Class type) {
@@ -209,4 +215,20 @@ public void setOfflineMode(boolean offlineMode) {
this.offlineMode = offlineMode;
}
+ 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;
+ }
+
}
diff --git a/src/main/java/com/github/switcherapi/client/model/ContextKey.java b/src/main/java/com/github/switcherapi/client/model/ContextKey.java
index 02ed0d62..3046614b 100644
--- a/src/main/java/com/github/switcherapi/client/model/ContextKey.java
+++ b/src/main/java/com/github/switcherapi/client/model/ContextKey.java
@@ -82,7 +82,17 @@ public enum ContextKey {
/**
* (Number) Defines the Timed Match regex time out.
*/
- REGEX_TIMEOUT("switcher.regextimeout", "regexTimeout");
+ REGEX_TIMEOUT("switcher.regextimeout", "regexTimeout"),
+
+ /**
+ * (Path) Defines the path for the trustsore file.
+ */
+ TRUSTSTORE_PATH("switcher.truststore.path", "truststorePath"),
+
+ /**
+ * (String) Defines the password for the truststore file.
+ */
+ TRUSTSTORE_PASSWORD("switcher.truststore.password", "truststorePassword");
private final String param;
private final String propField;
diff --git a/src/main/java/com/github/switcherapi/client/remote/ClientWSBuilder.java b/src/main/java/com/github/switcherapi/client/remote/ClientWSBuilder.java
new file mode 100644
index 00000000..d6c9f9a2
--- /dev/null
+++ b/src/main/java/com/github/switcherapi/client/remote/ClientWSBuilder.java
@@ -0,0 +1,50 @@
+package com.github.switcherapi.client.remote;
+
+import com.github.switcherapi.client.SwitcherContextBase;
+import com.github.switcherapi.client.exception.SwitcherException;
+import com.github.switcherapi.client.model.ContextKey;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+import javax.ws.rs.client.ClientBuilder;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.KeyStore;
+
+public class ClientWSBuilder {
+
+ private static final String KEYSTORE_TYPE = "JKS";
+
+ private static final String PROTOCOL = "TLSv1.2";
+
+ private ClientWSBuilder() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ public static ClientBuilder builder() {
+ if (StringUtils.isNotBlank(SwitcherContextBase.contextStr(ContextKey.TRUSTSTORE_PATH))) {
+ return builderSSL();
+ }
+
+ return ClientBuilder.newBuilder();
+ }
+
+ public static ClientBuilder builderSSL() {
+ try (InputStream readStream = new FileInputStream(SwitcherContextBase.contextStr(ContextKey.TRUSTSTORE_PATH))) {
+ final KeyStore trustStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ trustStore.load(readStream, SwitcherContextBase.contextStr(ContextKey.TRUSTSTORE_PASSWORD).toCharArray());
+
+ final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(trustStore);
+
+ final SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
+ sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
+
+ return ClientBuilder.newBuilder().sslContext(sslContext);
+ } catch (Exception e) {
+ throw new SwitcherException("Error while building SSL context", e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/github/switcherapi/client/remote/ClientWSImpl.java b/src/main/java/com/github/switcherapi/client/remote/ClientWSImpl.java
index ae233a6b..3be88f06 100644
--- a/src/main/java/com/github/switcherapi/client/remote/ClientWSImpl.java
+++ b/src/main/java/com/github/switcherapi/client/remote/ClientWSImpl.java
@@ -14,7 +14,6 @@
import org.apache.logging.log4j.Logger;
import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
@@ -41,7 +40,7 @@ public class ClientWSImpl implements ClientWS {
private final Client client;
public ClientWSImpl() {
- this.client = ClientBuilder.newClient();
+ this.client = ClientWSBuilder.builder().build();
}
@Override
diff --git a/src/main/java/com/github/switcherapi/client/service/remote/SwitcherRemoteService.java b/src/main/java/com/github/switcherapi/client/service/remote/SwitcherRemoteService.java
index 1f2feec8..a3ade720 100644
--- a/src/main/java/com/github/switcherapi/client/service/remote/SwitcherRemoteService.java
+++ b/src/main/java/com/github/switcherapi/client/service/remote/SwitcherRemoteService.java
@@ -47,7 +47,7 @@ public CriteriaResponse executeCriteria(final Switcher switcher) {
return response;
} catch (final SwitcherRemoteException e) {
- logger.error("Failed to execute criteria - {}", e.getMessage());
+ logger.error("Failed to execute criteria - {}\nCause: {}", e.getMessage(), e.getCause());
return executeSilentCriteria(switcher, e);
}
}
diff --git a/src/test/java/com/github/switcherapi/client/remote/ClientWSBuilderTest.java b/src/test/java/com/github/switcherapi/client/remote/ClientWSBuilderTest.java
new file mode 100644
index 00000000..8e50aae6
--- /dev/null
+++ b/src/test/java/com/github/switcherapi/client/remote/ClientWSBuilderTest.java
@@ -0,0 +1,75 @@
+package com.github.switcherapi.client.remote;
+
+import com.github.switcherapi.client.ContextBuilder;
+import com.github.switcherapi.client.SwitcherContextBase;
+import com.github.switcherapi.client.exception.SwitcherException;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.client.ClientBuilder;
+import java.util.Objects;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ClientWSBuilderTest {
+
+ @Test
+ void shouldCreateClientBuilder() {
+ // given
+ SwitcherContextBase.configure(ContextBuilder.builder()
+ .truststorePath("")
+ .truststorePassword(""));
+
+ // test
+ ClientBuilder clientBuilder = ClientWSBuilder.builder();
+ assertNotNull(clientBuilder);
+
+ SSLContext sslContext = clientBuilder.build().getSslContext();
+ assertNotNull(sslContext);
+ assertEquals("TLS", sslContext.getProtocol());
+ }
+
+ @Test
+ void shouldCreateClientBuilderSSL() {
+ // given
+ String truststorePath = Objects.requireNonNull(getClass().getClassLoader()
+ .getResource("keystore.jks")).getPath();
+
+ SwitcherContextBase.configure(ContextBuilder.builder()
+ .truststorePath(truststorePath)
+ .truststorePassword("changeit"));
+
+ // test
+ ClientBuilder clientBuilder = ClientWSBuilder.builder();
+ assertNotNull(clientBuilder);
+
+ SSLContext sslContext = clientBuilder.build().getSslContext();
+ assertNotNull(sslContext);
+ assertEquals("TLSv1.2", sslContext.getProtocol());
+ }
+
+ @Test
+ void shouldNotCreateClientBuilderSSL_invalidKeystorePassword() {
+ // given
+ String truststorePath = Objects.requireNonNull(getClass().getClassLoader()
+ .getResource("keystore.jks")).getPath();
+
+ SwitcherContextBase.configure(ContextBuilder.builder()
+ .truststorePath(truststorePath)
+ .truststorePassword("INVALID"));
+
+ // test
+ assertThrows(SwitcherException.class, ClientWSBuilder::builder);
+ }
+
+ @Test
+ void shouldNotCreateClientBuilderSSL_invalidKeystorePath() {
+ // given
+ SwitcherContextBase.configure(ContextBuilder.builder()
+ .truststorePath("INVALID")
+ .truststorePassword("changeit"));
+
+ // test
+ assertThrows(SwitcherException.class, ClientWSBuilder::builder);
+ }
+}
diff --git a/src/test/resources/keystore.jks b/src/test/resources/keystore.jks
new file mode 100644
index 00000000..75709948
Binary files /dev/null and b/src/test/resources/keystore.jks differ