Skip to content

Commit 6ed0d52

Browse files
committed
Added core API for changing credentials
1 parent 8a3e33a commit 6ed0d52

10 files changed

Lines changed: 1039 additions & 628 deletions

File tree

client-v2/src/main/java/com/clickhouse/client/api/Client.java

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.clickhouse.client.api.insert.InsertResponse;
1717
import com.clickhouse.client.api.insert.InsertSettings;
1818
import com.clickhouse.client.api.internal.ClientStatisticsHolder;
19+
import com.clickhouse.client.api.internal.CredentialsManager;
1920
import com.clickhouse.client.api.internal.HttpAPIClientHelper;
2021
import com.clickhouse.client.api.internal.MapUtils;
2122
import com.clickhouse.client.api.internal.TableSchemaParser;
@@ -141,11 +142,13 @@ public class Client implements AutoCloseable {
141142
private final int retries;
142143
private LZ4Factory lz4Factory = null;
143144
private final Supplier<String> queryIdGenerator;
145+
private final CredentialsManager credentialsManager;
144146

145147
private Client(Collection<Endpoint> endpoints, Map<String,String> configuration,
146148
ExecutorService sharedOperationExecutor, ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy,
147149
Object metricsRegistry, Supplier<String> queryIdGenerator) {
148-
this.configuration = ClientConfigProperties.parseConfigMap(configuration);
150+
this.configuration = new ConcurrentHashMap<>(ClientConfigProperties.parseConfigMap(configuration));
151+
this.credentialsManager = new CredentialsManager(this.configuration);
149152
this.readOnlyConfig = Collections.unmodifiableMap(configuration);
150153
this.metricsRegistry = metricsRegistry;
151154
this.queryIdGenerator = queryIdGenerator;
@@ -364,8 +367,11 @@ public Builder setOption(String key, String value) {
364367
if (key.equals(ClientConfigProperties.PRODUCT_NAME.getKey())) {
365368
setClientName(value);
366369
}
370+
if (key.equals(ClientConfigProperties.ACCESS_TOKEN.getKey())) {
371+
setAccessToken(value);
372+
}
367373
if (key.equals(ClientConfigProperties.BEARERTOKEN_AUTH.getKey())) {
368-
useBearerTokenAuth(value);
374+
setAccessToken(value);
369375
}
370376
return this;
371377
}
@@ -393,13 +399,17 @@ public Builder setPassword(String password) {
393399
}
394400

395401
/**
396-
* Access token for authentication with server. Required for all operations.
402+
* Preferred way to configure token-based authentication.
397403
* Same access token will be used for all endpoints.
404+
* Internally it is sent as an HTTP Bearer token.
398405
*
399406
* @param accessToken - plain text access token
400407
*/
408+
@SuppressWarnings("deprecation")
401409
public Builder setAccessToken(String accessToken) {
402410
this.configuration.put(ClientConfigProperties.ACCESS_TOKEN.getKey(), accessToken);
411+
this.configuration.remove(ClientConfigProperties.BEARERTOKEN_AUTH.getKey());
412+
this.httpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
403413
return this;
404414
}
405415

@@ -1007,16 +1017,16 @@ public Builder setOptions(Map<String, String> options) {
10071017
}
10081018

10091019
/**
1010-
* Specifies whether to use Bearer Authentication and what token to use.
1011-
* The token will be sent as is, so it should be encoded before passing to this method.
1020+
* Legacy HTTP-specific alias for {@link Builder#setAccessToken(String)}.
1021+
* Prefer using {@link Builder#setAccessToken(String)}.
10121022
*
10131023
* @param bearerToken - token to use
10141024
* @return same instance of the builder
10151025
*/
1026+
@Deprecated
10161027
public Builder useBearerTokenAuth(String bearerToken) {
10171028
// Most JWT libraries (https://jwt.io/libraries?language=Java) compact tokens in proper way
1018-
this.httpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + bearerToken);
1019-
return this;
1029+
return setAccessToken(bearerToken);
10201030
}
10211031

10221032
/**
@@ -1099,28 +1109,10 @@ public Client build() {
10991109
if (this.endpoints.isEmpty()) {
11001110
throw new IllegalArgumentException("At least one endpoint is required");
11011111
}
1102-
// check if username and password are empty. so can not initiate client?
1103-
boolean useSslAuth = MapUtils.getFlag(this.configuration, ClientConfigProperties.SSL_AUTH.getKey());
1104-
boolean hasAccessToken = this.configuration.containsKey(ClientConfigProperties.ACCESS_TOKEN.getKey());
1105-
boolean hasUser = this.configuration.containsKey(ClientConfigProperties.USER.getKey());
1106-
boolean hasPassword = this.configuration.containsKey(ClientConfigProperties.PASSWORD.getKey());
1107-
boolean customHttpHeaders = this.configuration.containsKey(ClientConfigProperties.httpHeader(HttpHeaders.AUTHORIZATION));
1108-
1109-
if (!(useSslAuth || hasAccessToken || hasUser || hasPassword || customHttpHeaders)) {
1110-
throw new IllegalArgumentException("Username and password (or access token or SSL authentication or pre-define Authorization header) are required");
1111-
}
1112-
1113-
if (useSslAuth && (hasAccessToken || hasPassword)) {
1114-
throw new IllegalArgumentException("Only one of password, access token or SSL authentication can be used per client.");
1115-
}
11161112

1117-
if (useSslAuth && !this.configuration.containsKey(ClientConfigProperties.SSL_CERTIFICATE.getKey())) {
1118-
throw new IllegalArgumentException("SSL authentication requires a client certificate");
1119-
}
1120-
1121-
if (this.configuration.containsKey(ClientConfigProperties.SSL_TRUST_STORE.getKey()) &&
1122-
this.configuration.containsKey(ClientConfigProperties.SSL_CERTIFICATE.getKey())) {
1123-
throw new IllegalArgumentException("Trust store and certificates cannot be used together");
1113+
ClientMisconfigurationException authConfigException = CredentialsManager.validateAuthConfig(configuration);
1114+
if (authConfigException != null) {
1115+
throw authConfigException;
11241116
}
11251117

11261118
// Check timezone settings
@@ -2153,8 +2145,28 @@ public Collection<String> getDBRoles() {
21532145
return unmodifiableDbRolesView;
21542146
}
21552147

2148+
public void setCredentials(String username, String password) {
2149+
this.credentialsManager.setCredentials(username, password);
2150+
}
2151+
2152+
/**
2153+
* Preferred runtime API to update token-based authentication.
2154+
* Internally it refreshes the HTTP Bearer token used by requests.
2155+
*
2156+
* @param accessToken - plain text access token
2157+
*/
2158+
public void setAccessToken(String accessToken) {
2159+
this.credentialsManager.setAccessToken(accessToken);
2160+
}
2161+
2162+
/**
2163+
* Legacy HTTP-specific alias for {@link #setAccessToken(String)}.
2164+
* Prefer using {@link #setAccessToken(String)}.
2165+
*
2166+
* @param bearer - token to use
2167+
*/
21562168
public void updateBearerToken(String bearer) {
2157-
this.configuration.put(ClientConfigProperties.httpHeader(HttpHeaders.AUTHORIZATION), "Bearer " + bearer);
2169+
setAccessToken(bearer);
21582170
}
21592171

21602172
private Endpoint getNextAliveNode() {
@@ -2170,8 +2182,7 @@ private Endpoint getNextAliveNode() {
21702182
* @return request settings - merged client and operation settings
21712183
*/
21722184
private Map<String, Object> buildRequestSettings(Map<String, Object> opSettings) {
2173-
Map<String, Object> requestSettings = new HashMap<>();
2174-
requestSettings.putAll(configuration);
2185+
Map<String, Object> requestSettings = credentialsManager.snapshot();
21752186
requestSettings.putAll(opSettings);
21762187
return requestSettings;
21772188
}

client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ public enum ClientConfigProperties {
125125

126126
CLIENT_NETWORK_BUFFER_SIZE("client_network_buffer_size", Integer.class, "300000"),
127127

128+
/**
129+
* Preferred client setting for token-based authentication like JWT and Oauth.
130+
* For Http it is translated to Authorization Bearer header.
131+
*/
128132
ACCESS_TOKEN("access_token", String.class),
129133

130134
SSL_AUTH("ssl_authentication", Boolean.class, "false"),
@@ -157,6 +161,10 @@ public Object parseValue(String value) {
157161
@Deprecated
158162
PRODUCT_NAME("product_name", String.class),
159163

164+
/**
165+
* HTTP-specific alias for {@link ClientConfigProperties#ACCESS_TOKEN}.
166+
* Prefer using {@link ClientConfigProperties#ACCESS_TOKEN}.
167+
*/
160168
BEARERTOKEN_AUTH ("bearer_token", String.class),
161169
/**
162170
* Indicates that data provided for write operation is compressed by application.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.clickhouse.client.api.internal;
2+
3+
import com.clickhouse.client.api.ClientConfigProperties;
4+
import com.clickhouse.client.api.ClientMisconfigurationException;
5+
import org.apache.hc.core5.http.HttpHeaders;
6+
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
10+
/**
11+
* Manages mutable authentication-related client settings.
12+
*/
13+
public class CredentialsManager {
14+
private final Map<String, Object> configuration;
15+
private final Object lock = new Object();
16+
17+
public CredentialsManager(Map<String, Object> configuration) {
18+
this.configuration = configuration;
19+
}
20+
21+
public Map<String, Object> snapshot() {
22+
synchronized (lock) {
23+
return new HashMap<>(configuration);
24+
}
25+
}
26+
27+
public void setCredentials(String username, String password) {
28+
synchronized (lock) {
29+
configuration.put(ClientConfigProperties.USER.getKey(), username);
30+
configuration.put(ClientConfigProperties.PASSWORD.getKey(), password);
31+
configuration.put(ClientConfigProperties.SSL_AUTH.getKey(), Boolean.FALSE);
32+
configuration.remove(ClientConfigProperties.ACCESS_TOKEN.getKey());
33+
configuration.remove(ClientConfigProperties.BEARERTOKEN_AUTH.getKey());
34+
configuration.remove(ClientConfigProperties.httpHeader(HttpHeaders.AUTHORIZATION));
35+
}
36+
}
37+
38+
public void setAccessToken(String accessToken) {
39+
synchronized (lock) {
40+
configuration.put(ClientConfigProperties.ACCESS_TOKEN.getKey(), accessToken);
41+
configuration.put(ClientConfigProperties.SSL_AUTH.getKey(), Boolean.FALSE);
42+
configuration.remove(ClientConfigProperties.BEARERTOKEN_AUTH.getKey());
43+
configuration.remove(ClientConfigProperties.USER.getKey());
44+
configuration.remove(ClientConfigProperties.PASSWORD.getKey());
45+
configuration.put(ClientConfigProperties.httpHeader(HttpHeaders.AUTHORIZATION), "Bearer " + accessToken);
46+
}
47+
}
48+
49+
public static ClientMisconfigurationException validateAuthConfig(Map<String, String> configuration) {
50+
// check if username and password are empty. so can not initiate client?
51+
boolean useSslAuth = MapUtils.getFlag(configuration, ClientConfigProperties.SSL_AUTH.getKey());
52+
boolean hasAccessToken = configuration.containsKey(ClientConfigProperties.ACCESS_TOKEN.getKey());
53+
boolean hasUser = configuration.containsKey(ClientConfigProperties.USER.getKey());
54+
boolean hasPassword = configuration.containsKey(ClientConfigProperties.PASSWORD.getKey());
55+
boolean customHttpHeaders = configuration.containsKey(ClientConfigProperties.httpHeader(HttpHeaders.AUTHORIZATION));
56+
57+
if (!(useSslAuth || hasAccessToken || hasUser || hasPassword || customHttpHeaders)) {
58+
return new ClientMisconfigurationException("Username and password (or access token or SSL authentication or pre-define Authorization header) are required");
59+
}
60+
61+
if (useSslAuth && (hasAccessToken || hasPassword)) {
62+
return new ClientMisconfigurationException("Only one of password, access token or SSL authentication can be used per client.");
63+
}
64+
65+
if (useSslAuth && !configuration.containsKey(ClientConfigProperties.SSL_CERTIFICATE.getKey())) {
66+
return new ClientMisconfigurationException("SSL authentication requires a client certificate");
67+
}
68+
69+
if (configuration.containsKey(ClientConfigProperties.SSL_TRUST_STORE.getKey()) &&
70+
configuration.containsKey(ClientConfigProperties.SSL_CERTIFICATE.getKey())) {
71+
return new ClientMisconfigurationException("Trust store and certificates cannot be used together");
72+
}
73+
74+
return null;
75+
}
76+
}

0 commit comments

Comments
 (0)