-
Notifications
You must be signed in to change notification settings - Fork 624
[client-v2,jdbc-v2] Change credentials in realtime #2812
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,033
−50
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
6ed0d52
Added core API for changing credentials
chernser ffbdd84
fix encoding
chernser bb1cfa8
Merge branch 'main' into 03/30/26/implement_change_credentials
chernser 32706eb
fixed test
chernser 73aa2a5
Implemented credentials manager as self entity controlling credentials
chernser bd75791
added usage example
chernser 781bddf
Merge branch 'main' into 03/30/26/implement_change_credentials
chernser 2233943
Made CredentialsManager handle values via baked map
chernser 9bbedfa
fix minor issues
chernser e50d874
added documentation with migration part
chernser 56465c3
Removed not needed changes.
chernser 208caf8
fixed null password issue
chernser b0e3895
fixed checking configuration for custom header
chernser 1e4f5ec
updated docs and code to match
chernser 67f51fd
updated change log and methods documentation
chernser f479763
moved new values validation to CredentialsManager
chernser abefd55
Merge branch 'main' into 03/30/26/implement_change_credentials
chernser 0a6a277
code cleanup
chernser c4660aa
Merge branch 'main' into 03/30/26/implement_change_credentials
chernser 7bb18e4
fixed test that set invalid configuration for access token
chernser 441e836
Disable HTTP basic auth
chernser be6b4c2
Disable HTTP basic auth
chernser 1322fb6
Disable HTTP basic auth
chernser cad55b5
fix test by using correct JWT host
chernser 6037235
fix test by using correct JWT host
chernser af426af
fix - JWT try to use default db
chernser 4750094
Added concurrent tests
chernser 4520f45
Merge branch 'main' into 03/30/26/implement_change_credentials
chernser File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
client-v2/src/main/java/com/clickhouse/client/api/internal/ClientUtils.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.clickhouse.client.api.internal; | ||
|
|
||
| /** | ||
| * Class containing utility methods used across the client. | ||
| */ | ||
| public final class ClientUtils { | ||
|
|
||
| private ClientUtils() {} | ||
|
|
||
| public static boolean isNotBlank(String str) { | ||
| return str != null && !str.trim().isEmpty(); | ||
| } | ||
| } |
145 changes: 145 additions & 0 deletions
145
client-v2/src/main/java/com/clickhouse/client/api/internal/CredentialsManager.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| package com.clickhouse.client.api.internal; | ||
|
|
||
| import com.clickhouse.client.api.ClientConfigProperties; | ||
| import com.clickhouse.client.api.ClientMisconfigurationException; | ||
| import org.apache.hc.core5.http.HttpHeaders; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.concurrent.atomic.AtomicReference; | ||
|
|
||
| /** | ||
| * Manages mutable authentication-related client settings. | ||
| * This class uses an atomic reference to ensure thread-safe credential updates. | ||
| * Updates are non-blocking and will be applied to newly initiated requests | ||
| * immediately without affecting ongoing queries. | ||
| */ | ||
| public class CredentialsManager { | ||
|
|
||
| public static final String AUTHORIZATION_HEADER_KEY = | ||
| ClientConfigProperties.httpHeader(HttpHeaders.AUTHORIZATION); | ||
| public static final String AUTH_HEADER_BEARER_PREFIX = "Bearer "; | ||
|
|
||
| private final AtomicReference<Map<String, Object>> authConfig = new AtomicReference<>(); | ||
|
|
||
| private final boolean hasUserPassword; | ||
|
|
||
| private final boolean hasAccessToken; | ||
|
|
||
| public CredentialsManager(Map<String, String> config) { | ||
| this.hasUserPassword = isUserPassword(config); | ||
| this.hasAccessToken = isAccessToken(config); | ||
|
|
||
| boolean sslAuthEnabled = isSslAuth(config); | ||
| boolean hasAuthHeader = isCustomAuthHeader(config); | ||
|
|
||
| final long authMethodsCount = Arrays | ||
|
chernser marked this conversation as resolved.
|
||
| .stream(new Boolean[]{hasUserPassword, hasAccessToken, sslAuthEnabled, hasAuthHeader}) | ||
| .filter(b -> b).count(); | ||
|
|
||
| String username = config.get(ClientConfigProperties.USER.getKey()); | ||
| if (authMethodsCount == 1 && !hasAuthHeader) { | ||
| // Auth header handled specially | ||
| String password = config.getOrDefault(ClientConfigProperties.PASSWORD.getKey(), ""); | ||
| boolean useSslAuth = MapUtils.getFlag(config, ClientConfigProperties.SSL_AUTH.getKey(), false); | ||
| String accessToken = config.get(ClientConfigProperties.ACCESS_TOKEN.getKey()); | ||
| updateBackedConfig(username, password, useSslAuth, accessToken); | ||
| } else if (authMethodsCount == 0 && ClientUtils.isNotBlank(username)) { | ||
| // password not set - it is still user, password case if no other auth | ||
| String password = config.getOrDefault(ClientConfigProperties.PASSWORD.getKey(), ""); | ||
| updateBackedConfig(username, password, false, null); | ||
| } else if (authMethodsCount == 0) { | ||
| throw new ClientMisconfigurationException(NO_AUTH_ERR_MSG); | ||
| } else if (hasAuthHeader) { | ||
| if (hasUserPassword && MapUtils.getFlag(config, ClientConfigProperties.HTTP_USE_BASIC_AUTH.getKey(), | ||
| ClientConfigProperties.HTTP_USE_BASIC_AUTH.getDefObjVal())) { | ||
| throw new ClientMisconfigurationException(PASSWORD_AND_AUTH_HEADER_BASIC_AUTH_ERR_MSG); | ||
| } | ||
| String password = config.getOrDefault(ClientConfigProperties.PASSWORD.getKey(), ""); | ||
| String authHeader = config.getOrDefault(AUTHORIZATION_HEADER_KEY, ""); | ||
| updateBackedConfig(username, password, false, authHeader); | ||
| } else { | ||
| throw new ClientMisconfigurationException(ONLY_ONE_METHOD_ERR_MSG); | ||
| } | ||
| } | ||
|
|
||
| public void applyCredentials(Map<String, Object> target) { | ||
| Map<String, Object> properties = authConfig.get(); | ||
| target.putAll(properties); | ||
| } | ||
|
|
||
| private static final String AUTH_CANNOT_BE_SWITCHED_ERR_MSG = "Authentication type cannot be switched at runtime"; | ||
|
|
||
| /** | ||
| * Replaces the current username/password credentials. | ||
| * | ||
| * <p>Updates are applied atomically and take effect for newly initiated requests | ||
| * without blocking or requiring external synchronization. | ||
| */ | ||
| public void setCredentials(String username, String password) { | ||
| ValidationUtils.checkNonBlank(username, "username"); | ||
| ValidationUtils.checkNonBlank(password, "password"); | ||
|
chernser marked this conversation as resolved.
|
||
| if (!hasUserPassword) { | ||
| throw new ClientMisconfigurationException(AUTH_CANNOT_BE_SWITCHED_ERR_MSG); | ||
| } | ||
| updateBackedConfig(username, password, false, null); | ||
| } | ||
|
|
||
| /** | ||
| * Replaces the current credentials with a bearer token. | ||
| * | ||
| * <p>Updates are applied atomically and take effect for newly initiated requests | ||
| * without blocking or requiring external synchronization. | ||
| */ | ||
| public void setAccessToken(String accessToken) { | ||
| if (!hasAccessToken) { | ||
| throw new ClientMisconfigurationException(AUTH_CANNOT_BE_SWITCHED_ERR_MSG); | ||
| } | ||
| ValidationUtils.checkNonBlank(accessToken, "accessToken"); | ||
| updateBackedConfig(null, null, false, accessToken); | ||
|
chernser marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private void updateBackedConfig(String username, String password, boolean useSslAuth, String authHeader) { | ||
| Map<String, Object> updated = new HashMap<>(); | ||
| updated.put(ClientConfigProperties.USER.getKey(), username); | ||
| updated.put(ClientConfigProperties.PASSWORD.getKey(), password); | ||
| updated.put(ClientConfigProperties.SSL_AUTH.getKey(), useSslAuth); | ||
| updated.put(AUTHORIZATION_HEADER_KEY, authHeader); | ||
| authConfig.set(updated); | ||
| } | ||
|
|
||
| private static final String NO_AUTH_ERR_MSG = "Auth configuration is missing. At least one the following should be provided: " + | ||
| "user & password, access token, custom authentication headers"; | ||
|
|
||
| private static final String ONLY_ONE_METHOD_ERR_MSG = "Only one of password, access token or SSL authentication can be used per client."; | ||
|
|
||
| private static final String SSL_REQUIRES_CERT_ERR_MSG = "SSL authentication requires a client certificate"; | ||
|
|
||
| private static final String PASSWORD_AND_AUTH_HEADER_BASIC_AUTH_ERR_MSG = "When both password and authentication header is set then basic auth. should be disabled"; | ||
|
|
||
| private boolean isUserPassword(Map<String, ?> config) { | ||
| String username = (String) config.get(ClientConfigProperties.USER.getKey()); | ||
| boolean hasUser = ClientUtils.isNotBlank(username); | ||
| String password = (String) config.get(ClientConfigProperties.PASSWORD.getKey()); | ||
| return hasUser && password != null; | ||
| } | ||
|
|
||
| private boolean isSslAuth(Map<String, ?> config) { | ||
| boolean useSslAuth = MapUtils.getFlag(config, ClientConfigProperties.SSL_AUTH.getKey(), false); | ||
| if (useSslAuth && !config.containsKey(ClientConfigProperties.SSL_CERTIFICATE.getKey())) { | ||
| throw new ClientMisconfigurationException(SSL_REQUIRES_CERT_ERR_MSG); | ||
| } | ||
| return useSslAuth; | ||
| } | ||
|
|
||
| private boolean isAccessToken(Map<String, ?> config) { | ||
| String accessToken = (String) config.get(ClientConfigProperties.ACCESS_TOKEN.getKey()); | ||
| return ClientUtils.isNotBlank(accessToken); | ||
| } | ||
|
|
||
| private boolean isCustomAuthHeader(Map<String, ?> config) { | ||
| String authHeader = (String) config.get(AUTHORIZATION_HEADER_KEY); | ||
| return ClientUtils.isNotBlank(authHeader); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.