Skip to content

Commit e8080b3

Browse files
add a way to enable/disable token cache
1 parent eb0fad0 commit e8080b3

File tree

5 files changed

+300
-65
lines changed

5 files changed

+300
-65
lines changed

databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ public class DatabricksConfig {
4747
@ConfigAttribute(env = "DATABRICKS_OAUTH_TOKEN_CACHE_PASSPHRASE", auth = "oauth", sensitive = true)
4848
private String oAuthTokenCachePassphrase;
4949

50+
/**
51+
* Controls whether OAuth token caching is enabled.
52+
* When set to false, tokens will not be cached or loaded from cache.
53+
*/
54+
@ConfigAttribute(env = "DATABRICKS_OAUTH_TOKEN_CACHE_ENABLED", auth = "oauth")
55+
private Boolean isTokenCacheEnabled;
56+
5057
/**
5158
* The OpenID Connect discovery URL used to retrieve OIDC configuration and endpoints.
5259
*
@@ -682,15 +689,36 @@ public DatabricksConfig newWithWorkspaceHost(String host) {
682689
return clone(fieldsToSkip).setHost(host);
683690
}
684691

685-
public String getOAuthPassphrase() {
692+
public String getOAuthTokenCachePassphrase() {
686693
return oAuthTokenCachePassphrase;
687694
}
688695

689-
public DatabricksConfig setOAuthPassphrase(String oAuthPassphrase) {
696+
public DatabricksConfig setOAuthTokenCachePassphrase(String oAuthPassphrase) {
690697
this.oAuthTokenCachePassphrase = oAuthPassphrase;
691698
return this;
692699
}
693700

701+
/**
702+
* Gets whether OAuth token caching is enabled.
703+
* Default is true.
704+
*
705+
* @return true if token caching is enabled, false otherwise
706+
*/
707+
public boolean isTokenCacheEnabled() {
708+
return isTokenCacheEnabled == null || isTokenCacheEnabled;
709+
}
710+
711+
/**
712+
* Sets whether OAuth token caching is enabled.
713+
*
714+
* @param enabled true to enable token caching, false to disable
715+
* @return this config instance
716+
*/
717+
public DatabricksConfig setTokenCacheEnabled(boolean enabled) {
718+
this.isTokenCacheEnabled = enabled;
719+
return this;
720+
}
721+
694722
/**
695723
* Gets the default OAuth redirect URL.
696724
* If one is not provided explicitly, uses http://localhost:8080/callback
@@ -704,40 +732,14 @@ public String getEffectiveOAuthRedirectUrl() {
704732
/**
705733
* Gets the TokenCache instance for the current configuration.
706734
* Creates it if it doesn't exist yet.
735+
* When token caching is disabled, the TokenCache will be created but operations will be no-ops.
707736
*
708737
* @return A TokenCache instance for the current host, client ID, and scopes
709738
*/
710739
public synchronized TokenCache getTokenCache() {
711-
if (tokenCache == null && getHost() != null && getClientId() != null) {
712-
tokenCache = new TokenCache(getHost(), getClientId(), getScopes(), getOAuthPassphrase());
740+
if (tokenCache == null) {
741+
tokenCache = new TokenCache(getHost(), getClientId(), getScopes(), getOAuthTokenCachePassphrase(), isTokenCacheEnabled());
713742
}
714743
return tokenCache;
715744
}
716-
717-
/**
718-
* Loads a cached token if one exists for the current configuration.
719-
*
720-
* @return The cached token, or null if no valid token is cached
721-
*/
722-
public Token loadCachedToken() {
723-
TokenCache cache = getTokenCache();
724-
if (cache == null) {
725-
return null;
726-
}
727-
return cache.load();
728-
}
729-
730-
/**
731-
* Saves a token to the cache for the current configuration.
732-
*
733-
* @param token The token to cache
734-
* @throws IOException If there is an error saving the token
735-
*/
736-
public void saveTokenToCache(Token token) throws IOException {
737-
TokenCache cache = getTokenCache();
738-
if (cache == null) {
739-
return;
740-
}
741-
cache.save(token);
742-
}
743745
}

databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
/**
1212
* A {@code CredentialsProvider} which implements the Authorization Code + PKCE flow by opening a
1313
* browser for the user to authorize the application. When cache support is enabled with
14-
* {@link DatabricksConfig#setOAuthPassphrase}, tokens will be cached to avoid repeated authentication.
14+
* {@link DatabricksConfig#setOAuthTokenCachePassphrase} and {@link DatabricksConfig#setTokenCacheEnabled(boolean)},
15+
* tokens will be cached to avoid repeated authentication.
1516
*/
1617
public class ExternalBrowserCredentialsProvider implements CredentialsProvider {
1718
private static final Logger LOGGER = LoggerFactory.getLogger(ExternalBrowserCredentialsProvider.class);
@@ -31,7 +32,7 @@ public HeaderFactory configure(DatabricksConfig config) {
3132
// Get the token cache from config
3233
TokenCache tokenCache = config.getTokenCache();
3334

34-
// First try to use the cached token if available
35+
// First try to use the cached token if available (will return null if disabled)
3536
Token cachedToken = tokenCache.load();
3637
if (cachedToken != null && cachedToken.getRefreshToken() != null) {
3738
LOGGER.debug("Found cached token for {}:{}", config.getHost(), config.getClientId());
@@ -47,7 +48,9 @@ public HeaderFactory configure(DatabricksConfig config) {
4748
.withRedirectUrl(config.getEffectiveOAuthRedirectUrl())
4849
.build();
4950

50-
LOGGER.debug("Using cached token (will refresh if needed)");
51+
LOGGER.debug("Using cached token, will immediately refresh");
52+
cachedCreds.token = cachedCreds.refresh();
53+
tokenCache.save(cachedToken);
5154
return cachedCreds.configure(config);
5255
} catch (Exception e) {
5356
// If token refresh fails, log and continue to browser auth
@@ -58,7 +61,6 @@ public HeaderFactory configure(DatabricksConfig config) {
5861
// If no cached token or refresh failed, perform browser auth
5962
SessionCredentials credentials = performBrowserAuth(config);
6063
tokenCache.save(credentials.getToken());
61-
LOGGER.debug("Saved browser auth token to cache");
6264
return credentials.configure(config);
6365
} catch (IOException | DatabricksException e) {
6466
LOGGER.error("Failed to authenticate: {}", e.getMessage());

databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/TokenCache.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@
2020
import com.databricks.sdk.core.utils.SerDeUtils;
2121
import com.fasterxml.jackson.databind.ObjectMapper;
2222
import com.google.common.annotations.VisibleForTesting;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
2325

2426
/**
2527
* TokenCache stores OAuth tokens on disk to avoid repeated authentication.
2628
* It generates a unique cache filename based on the host, client ID, and scopes.
2729
* If a passphrase is provided, the token data is encrypted for added security.
30+
* Cache operations can be disabled by setting isEnabled to false.
2831
*/
2932
public class TokenCache {
33+
private static final Logger LOGGER = LoggerFactory.getLogger(TokenCache.class);
34+
3035
// Base path for token cache files, aligned with Python implementation
3136
private static final String BASE_PATH = ".config/databricks-sdk-java/oauth";
3237

@@ -43,6 +48,7 @@ public class TokenCache {
4348
private final Path cacheFile;
4449
private final String passphrase;
4550
private final ObjectMapper mapper;
51+
private final boolean isEnabled;
4652

4753
/**
4854
* Constructs a new TokenCache instance for OAuth token caching
@@ -51,17 +57,20 @@ public class TokenCache {
5157
* @param clientId The OAuth client ID
5258
* @param scopes The OAuth scopes requested (optional)
5359
* @param passphrase The passphrase used to encrypt/decrypt the token cache (optional)
60+
* @param isEnabled Whether token caching is enabled
5461
*/
5562
public TokenCache(
5663
String host,
5764
String clientId,
5865
List<String> scopes,
59-
String passphrase) {
66+
String passphrase,
67+
boolean isEnabled) {
6068
this.host = Objects.requireNonNull(host, "host must be defined");
6169
this.clientId = Objects.requireNonNull(clientId, "clientId must be defined");
6270
this.scopes = scopes != null ? scopes : new ArrayList<>();
6371
this.passphrase = passphrase; // Can be null or empty, encryption will be skipped in that case
6472
this.mapper = SerDeUtils.createMapper();
73+
this.isEnabled = isEnabled;
6574

6675
this.cacheFile = getFilename();
6776
}
@@ -113,11 +122,17 @@ private boolean shouldUseEncryption() {
113122

114123
/**
115124
* Saves a Token to the cache file, encrypting if a passphrase is provided
125+
* Does nothing if caching is disabled
116126
*
117127
* @param token The Token to save
118128
* @throws IOException If an error occurs writing to the file
119129
*/
120130
public void save(Token token) throws IOException {
131+
if (!isEnabled) {
132+
LOGGER.debug("Token caching is disabled, skipping save operation");
133+
return;
134+
}
135+
121136
try {
122137
Files.createDirectories(cacheFile.getParent());
123138

@@ -138,19 +153,28 @@ public void save(Token token) throws IOException {
138153
cacheFile.toFile().setReadable(true, true);
139154
cacheFile.toFile().setWritable(false, false);
140155
cacheFile.toFile().setWritable(true, true);
156+
157+
LOGGER.debug("Successfully saved token to cache: {}", cacheFile);
141158
} catch (Exception e) {
142159
throw new IOException("Failed to save token cache: " + e.getMessage(), e);
143160
}
144161
}
145162

146163
/**
147164
* Loads a Token from the cache file, decrypting if a passphrase was provided
165+
* Returns null if caching is disabled
148166
*
149-
* @return The Token from the cache or null if the cache file doesn't exist or is invalid
167+
* @return The Token from the cache or null if the cache file doesn't exist, is invalid, or caching is disabled
150168
*/
151169
public Token load() {
170+
if (!isEnabled) {
171+
LOGGER.debug("Token caching is disabled, skipping load operation");
172+
return null;
173+
}
174+
152175
try {
153176
if (!Files.exists(cacheFile)) {
177+
LOGGER.debug("No token cache file found at: {}", cacheFile);
154178
return null;
155179
}
156180

@@ -163,6 +187,7 @@ public Token load() {
163187
} catch (Exception e) {
164188
// If decryption fails, it might be because the file was saved without encryption
165189
// or the passphrase is incorrect
190+
LOGGER.debug("Failed to decrypt token cache: {}", e.getMessage());
166191
return null;
167192
}
168193
} else {
@@ -171,10 +196,13 @@ public Token load() {
171196

172197
// Deserialize token from JSON
173198
String json = new String(decodedContent, StandardCharsets.UTF_8);
174-
return mapper.readValue(json, Token.class);
199+
Token token = mapper.readValue(json, Token.class);
200+
LOGGER.debug("Successfully loaded token from cache: {}", cacheFile);
201+
return token;
175202
} catch (Exception e) {
176203
// If there's any issue loading the token, return null
177204
// to allow a fresh token to be obtained
205+
LOGGER.debug("Failed to load token from cache: {}", e.getMessage());
178206
return null;
179207
}
180208
}

0 commit comments

Comments
 (0)