|
1 | 1 | package com.clickhouse.client.api; |
2 | 2 |
|
3 | 3 | import com.clickhouse.client.api.enums.SSLMode; |
| 4 | +import com.clickhouse.client.api.internal.HttpAPIClientHelper; |
4 | 5 | import org.testng.Assert; |
5 | 6 | import org.testng.annotations.DataProvider; |
6 | 7 | import org.testng.annotations.Test; |
7 | 8 |
|
| 9 | +import javax.net.ssl.SSLContext; |
8 | 10 | import java.lang.reflect.Field; |
| 11 | +import java.util.HashMap; |
9 | 12 | import java.util.List; |
| 13 | +import java.util.Map; |
10 | 14 |
|
11 | 15 | public class ClientBuilderTest { |
12 | 16 |
|
@@ -86,6 +90,144 @@ public void testSslModeInvalidValueRejected() { |
86 | 90 | .build()); |
87 | 91 | } |
88 | 92 |
|
| 93 | + @Test |
| 94 | + public void testStringSSLContextRejectedBySetOption() { |
| 95 | + Assert.expectThrows(ClientMisconfigurationException.class, |
| 96 | + () -> new Client.Builder().setOption(ClientConfigProperties.SSL_CONTEXT.getKey(), "not-a-context")); |
| 97 | + } |
| 98 | + |
| 99 | + @DataProvider(name = "sslMaterialWithCustomContext_DP") |
| 100 | + public static Object[][] sslMaterialWithCustomContext_DP() { |
| 101 | + return new Object[][] { |
| 102 | + { ClientConfigProperties.SSL_TRUST_STORE.getKey(), "/path/to/truststore.jks" }, |
| 103 | + { ClientConfigProperties.SSL_KEYSTORE_TYPE.getKey(), "JKS" }, |
| 104 | + { ClientConfigProperties.SSL_KEY_STORE_PASSWORD.getKey(), "secret" }, |
| 105 | + { ClientConfigProperties.SSL_KEY.getKey(), "/path/to/client.key" }, |
| 106 | + { ClientConfigProperties.CA_CERTIFICATE.getKey(), "/path/to/ca.crt" }, |
| 107 | + { ClientConfigProperties.SSL_CERTIFICATE.getKey(), "/path/to/client.crt" }, |
| 108 | + }; |
| 109 | + } |
| 110 | + |
| 111 | + @Test(dataProvider = "sslMaterialWithCustomContext_DP") |
| 112 | + public void testCustomSSLContextRejectsOtherSslMaterial(String materialKey, String materialValue) throws Exception { |
| 113 | + SSLContext customContext = SSLContext.getInstance("TLS"); |
| 114 | + customContext.init(null, null, null); |
| 115 | + |
| 116 | + Assert.expectThrows(ClientMisconfigurationException.class, () -> new Client.Builder() |
| 117 | + .addEndpoint("https://localhost:8443") |
| 118 | + .setUsername("default") |
| 119 | + .setPassword("") |
| 120 | + .setOption(materialKey, materialValue) |
| 121 | + .setSSLContext(customContext) |
| 122 | + .build()); |
| 123 | + } |
| 124 | + |
| 125 | + @Test |
| 126 | + public void testCustomSSLContextAllowsSslMode() throws Exception { |
| 127 | + SSLContext customContext = SSLContext.getInstance("TLS"); |
| 128 | + customContext.init(null, null, null); |
| 129 | + |
| 130 | + try (Client client = new Client.Builder() |
| 131 | + .addEndpoint("https://localhost:8443") |
| 132 | + .setUsername("default") |
| 133 | + .setPassword("") |
| 134 | + .setSSLMode(SSLMode.VERIFY_CA) |
| 135 | + .setSSLContext(customContext) |
| 136 | + .build()) { |
| 137 | + Assert.assertSame(extractConfiguration(client).get(ClientConfigProperties.SSL_CONTEXT.getKey()), |
| 138 | + customContext); |
| 139 | + Assert.assertEquals(client.getConfiguration().get(ClientConfigProperties.SSL_MODE.getKey()), |
| 140 | + SSLMode.VERIFY_CA.name()); |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + @Test |
| 145 | + public void testTrustStoreAndClientCertificateConflictRejectedWithoutCustomContext() { |
| 146 | + // Contrast: without a custom SSLContext the trust-store/certificate conflict is still rejected. |
| 147 | + Assert.expectThrows(ClientMisconfigurationException.class, () -> new Client.Builder() |
| 148 | + .addEndpoint("https://localhost:8443") |
| 149 | + .setUsername("default") |
| 150 | + .setPassword("") |
| 151 | + .setSSLTrustStore("/path/to/truststore.jks") |
| 152 | + .setClientCertificate("client.crt") |
| 153 | + .build()); |
| 154 | + } |
| 155 | + |
| 156 | + @Test |
| 157 | + public void testSetSSLContextStoredInConfiguration() throws Exception { |
| 158 | + SSLContext customContext = SSLContext.getInstance("TLS"); |
| 159 | + customContext.init(null, null, null); |
| 160 | + |
| 161 | + try (Client client = new Client.Builder() |
| 162 | + .addEndpoint("https://localhost:8443") |
| 163 | + .setUsername("default") |
| 164 | + .setPassword("") |
| 165 | + .setSSLContext(customContext) |
| 166 | + .build()) { |
| 167 | + Assert.assertSame(extractConfiguration(client).get(ClientConfigProperties.SSL_CONTEXT.getKey()), |
| 168 | + customContext, "The application-supplied SSLContext should be stored in the configuration"); |
| 169 | + } |
| 170 | + |
| 171 | + // Without setSSLContext the key must be absent so the client builds its own context. |
| 172 | + try (Client client = new Client.Builder() |
| 173 | + .addEndpoint("https://localhost:8443") |
| 174 | + .setUsername("default") |
| 175 | + .setPassword("") |
| 176 | + .build()) { |
| 177 | + Assert.assertNull(extractConfiguration(client).get(ClientConfigProperties.SSL_CONTEXT.getKey()), |
| 178 | + "No SSLContext should be stored when none is supplied"); |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + @Test |
| 183 | + public void testCreateSSLContextIgnoresCustomContext() throws Exception { |
| 184 | + SSLContext customContext = SSLContext.getInstance("TLS"); |
| 185 | + customContext.init(null, null, null); |
| 186 | + |
| 187 | + try (Client client = new Client.Builder() |
| 188 | + .addEndpoint("https://localhost:8443") |
| 189 | + .setUsername("default") |
| 190 | + .setPassword("") |
| 191 | + .build()) { |
| 192 | + HttpAPIClientHelper helper = extractHttpClientHelper(client); |
| 193 | + Map<String, Object> configWithCustom = new HashMap<>(extractConfiguration(client)); |
| 194 | + configWithCustom.put(ClientConfigProperties.SSL_CONTEXT.getKey(), customContext); |
| 195 | + // createSSLContext only builds from trust/key material; selecting a custom context is |
| 196 | + // createHttpClient's responsibility, so this must not return the supplied context. |
| 197 | + Assert.assertNotSame(helper.createSSLContext(configWithCustom), customContext); |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + @Test |
| 202 | + public void testCreateHttpClientRejectsNonSSLContextValue() throws Exception { |
| 203 | + try (Client client = new Client.Builder() |
| 204 | + .addEndpoint("https://localhost:8443") |
| 205 | + .setUsername("default") |
| 206 | + .setPassword("") |
| 207 | + .build()) { |
| 208 | + HttpAPIClientHelper helper = extractHttpClientHelper(client); |
| 209 | + Map<String, Object> configWithBadContext = new HashMap<>(extractConfiguration(client)); |
| 210 | + configWithBadContext.put(ClientConfigProperties.SSL_CONTEXT.getKey(), "not-a-context"); |
| 211 | + // A non-SSLContext value under 'ssl_context' is a misconfiguration; createHttpClient must |
| 212 | + // reject it instead of silently building a context from trust/key material. |
| 213 | + Assert.expectThrows(ClientMisconfigurationException.class, |
| 214 | + () -> helper.createHttpClient(true, configWithBadContext)); |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + private static HttpAPIClientHelper extractHttpClientHelper(Client client) throws Exception { |
| 219 | + Field helperField = Client.class.getDeclaredField("httpClientHelper"); |
| 220 | + helperField.setAccessible(true); |
| 221 | + return (HttpAPIClientHelper) helperField.get(client); |
| 222 | + } |
| 223 | + |
| 224 | + @SuppressWarnings("unchecked") |
| 225 | + private static Map<String, Object> extractConfiguration(Client client) throws Exception { |
| 226 | + Field configField = Client.class.getDeclaredField("configuration"); |
| 227 | + configField.setAccessible(true); |
| 228 | + return (Map<String, Object>) configField.get(client); |
| 229 | + } |
| 230 | + |
89 | 231 | private static String extractFirstEndpointUri(Client client) throws Exception { |
90 | 232 | Field endpointsField = Client.class.getDeclaredField("endpoints"); |
91 | 233 | endpointsField.setAccessible(true); |
|
0 commit comments