Skip to content

Commit d8c366e

Browse files
update implementation based on discussion
1 parent 6f1b69d commit d8c366e

File tree

12 files changed

+350
-538
lines changed

12 files changed

+350
-538
lines changed

NEXT_CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
## Release v0.44.0
44

55
### New Features and Improvements
6-
Added `TokenCache` to `ExternalBrowserCredentialsProvider` to reduce number of authentications needed for U2M OAuth
7-
6+
* Added `TokenCache` to `ExternalBrowserCredentialsProvider` to reduce number of authentications needed for U2M OAuth.
7+
88
### Bug Fixes
99

1010
### Documentation

databricks-sdk-java/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
<artifactId>google-auth-library-oauth2-http</artifactId>
9898
<version>1.20.0</version>
9999
</dependency>
100+
<!-- Jackson JSR310 module needed to serialize/deserialize java.time classes in TokenCache -->
100101
<dependency>
101102
<groupId>com.fasterxml.jackson.datatype</groupId>
102103
<artifactId>jackson-datatype-jsr310</artifactId>

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

Lines changed: 20 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
import com.databricks.sdk.core.http.HttpClient;
55
import com.databricks.sdk.core.http.Request;
66
import com.databricks.sdk.core.http.Response;
7+
import com.databricks.sdk.core.oauth.FileTokenCache;
78
import com.databricks.sdk.core.oauth.OpenIDConnectEndpoints;
89
import com.databricks.sdk.core.oauth.TokenCache;
10+
import com.databricks.sdk.core.oauth.TokenCacheUtils;
911
import com.databricks.sdk.core.utils.Cloud;
1012
import com.databricks.sdk.core.utils.Environment;
1113
import com.fasterxml.jackson.databind.ObjectMapper;
1214
import java.io.File;
1315
import java.io.IOException;
1416
import java.lang.reflect.Field;
17+
import java.nio.file.Path;
1518
import java.util.*;
1619
import org.apache.http.HttpMessage;
1720

@@ -39,23 +42,6 @@ public class DatabricksConfig {
3942
@ConfigAttribute(env = "DATABRICKS_REDIRECT_URL", auth = "oauth")
4043
private String redirectUrl;
4144

42-
/**
43-
* The passphrase used to encrypt the OAuth token cache. This is optional and only used with token
44-
* caching.
45-
*/
46-
@ConfigAttribute(
47-
env = "DATABRICKS_OAUTH_TOKEN_CACHE_PASSPHRASE",
48-
auth = "oauth",
49-
sensitive = true)
50-
private String oAuthTokenCachePassphrase;
51-
52-
/**
53-
* Controls whether OAuth token caching is enabled. When set to false, tokens will not be cached
54-
* or loaded from cache.
55-
*/
56-
@ConfigAttribute(env = "DATABRICKS_OAUTH_TOKEN_CACHE_ENABLED", auth = "oauth")
57-
private Boolean isTokenCacheEnabled;
58-
5945
/**
6046
* The OpenID Connect discovery URL used to retrieve OIDC configuration and endpoints.
6147
*
@@ -395,13 +381,17 @@ public DatabricksConfig setAzureUseMsi(boolean azureUseMsi) {
395381
return this;
396382
}
397383

398-
/** @deprecated Use {@link #getAzureUseMsi()} instead. */
384+
/**
385+
* @deprecated Use {@link #getAzureUseMsi()} instead.
386+
*/
399387
@Deprecated()
400388
public boolean getAzureUseMSI() {
401389
return azureUseMsi;
402390
}
403391

404-
/** @deprecated Use {@link #setAzureUseMsi(boolean)} instead. */
392+
/**
393+
* @deprecated Use {@link #setAzureUseMsi(boolean)} instead.
394+
*/
405395
@Deprecated
406396
public DatabricksConfig setAzureUseMSI(boolean azureUseMsi) {
407397
this.azureUseMsi = azureUseMsi;
@@ -691,32 +681,14 @@ public DatabricksConfig newWithWorkspaceHost(String host) {
691681
return clone(fieldsToSkip).setHost(host);
692682
}
693683

694-
public String getOAuthTokenCachePassphrase() {
695-
return oAuthTokenCachePassphrase;
696-
}
697-
698-
public DatabricksConfig setOAuthTokenCachePassphrase(String oAuthPassphrase) {
699-
this.oAuthTokenCachePassphrase = oAuthPassphrase;
700-
return this;
701-
}
702-
703-
/**
704-
* Gets whether OAuth token caching is enabled. Default is true.
705-
*
706-
* @return true if token caching is enabled, false otherwise
707-
*/
708-
public boolean isTokenCacheEnabled() {
709-
return isTokenCacheEnabled == null || isTokenCacheEnabled;
710-
}
711-
712684
/**
713-
* Sets whether OAuth token caching is enabled.
685+
* Sets a custom TokenCache implementation.
714686
*
715-
* @param enabled true to enable token caching, false to disable
687+
* @param tokenCache the TokenCache implementation to use
716688
* @return this config instance
717689
*/
718-
public DatabricksConfig setTokenCacheEnabled(boolean enabled) {
719-
this.isTokenCacheEnabled = enabled;
690+
public DatabricksConfig setTokenCache(TokenCache tokenCache) {
691+
this.tokenCache = tokenCache;
720692
return this;
721693
}
722694

@@ -731,20 +703,17 @@ public String getEffectiveOAuthRedirectUrl() {
731703
}
732704

733705
/**
734-
* Gets the TokenCache instance for the current configuration. 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.
706+
* Gets the TokenCache instance for the current configuration.
707+
*
708+
* <p>If a custom TokenCache has been set, it will be returned. Otherwise, a SimpleFileTokenCache
709+
* will be created based on the configuration properties.
736710
*
737-
* @return A TokenCache instance for the current host, client ID, and scopes
711+
* @return A TokenCache instance
738712
*/
739713
public synchronized TokenCache getTokenCache() {
740714
if (tokenCache == null) {
741-
tokenCache =
742-
new TokenCache(
743-
getHost(),
744-
getClientId(),
745-
getScopes(),
746-
getOAuthTokenCachePassphrase(),
747-
isTokenCacheEnabled());
715+
Path cachePath = TokenCacheUtils.getCacheFilePath(getHost(), getClientId(), getScopes());
716+
tokenCache = new FileTokenCache(cachePath);
748717
}
749718
return tokenCache;
750719
}

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010

1111
/**
1212
* A {@code CredentialsProvider} which implements the Authorization Code + PKCE flow by opening a
13-
* browser for the user to authorize the application. When cache support is enabled with {@link
14-
* DatabricksConfig#setOAuthTokenCachePassphrase} and {@link
15-
* DatabricksConfig#setTokenCacheEnabled(boolean)}, tokens will be cached to avoid repeated
16-
* authentication.
13+
* browser for the user to authorize the application. Uses the cache from {@link
14+
* DatabricksConfig#getTokenCache()}, tokens will be cached to avoid repeated authentication.
1715
*/
1816
public class ExternalBrowserCredentialsProvider implements CredentialsProvider {
1917
private static final Logger LOGGER =
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.databricks.sdk.core.oauth;
2+
3+
import com.databricks.sdk.core.utils.SerDeUtils;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import java.nio.charset.StandardCharsets;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
import java.util.Objects;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
12+
/** A TokenCache implementation that stores tokens as plain files. */
13+
public class FileTokenCache implements TokenCache {
14+
private static final Logger LOGGER = LoggerFactory.getLogger(FileTokenCache.class);
15+
16+
private final Path cacheFile;
17+
private final ObjectMapper mapper;
18+
19+
/**
20+
* Constructs a new SimpleFileTokenCache instance.
21+
*
22+
* @param cacheFilePath The path where the token cache will be stored
23+
*/
24+
public FileTokenCache(Path cacheFilePath) {
25+
Objects.requireNonNull(cacheFilePath, "cacheFilePath must be defined");
26+
27+
this.cacheFile = cacheFilePath;
28+
this.mapper = SerDeUtils.createMapper();
29+
}
30+
31+
@Override
32+
public void save(Token token) {
33+
try {
34+
Files.createDirectories(cacheFile.getParent());
35+
36+
// Serialize token to JSON
37+
String json = mapper.writeValueAsString(token);
38+
byte[] dataToWrite = json.getBytes(StandardCharsets.UTF_8);
39+
40+
Files.write(cacheFile, dataToWrite);
41+
// Set file permissions to be readable only by the owner (equivalent to 0600)
42+
cacheFile.toFile().setReadable(false, false);
43+
cacheFile.toFile().setReadable(true, true);
44+
cacheFile.toFile().setWritable(false, false);
45+
cacheFile.toFile().setWritable(true, true);
46+
47+
LOGGER.debug("Successfully saved token to cache: {}", cacheFile);
48+
} catch (Exception e) {
49+
LOGGER.warn("Failed to save token to cache: {}", cacheFile, e);
50+
}
51+
}
52+
53+
@Override
54+
public Token load() {
55+
try {
56+
if (!Files.exists(cacheFile)) {
57+
LOGGER.debug("No token cache file found at: {}", cacheFile);
58+
return null;
59+
}
60+
61+
byte[] fileContent = Files.readAllBytes(cacheFile);
62+
63+
// Deserialize token from JSON
64+
String json = new String(fileContent, StandardCharsets.UTF_8);
65+
Token token = mapper.readValue(json, Token.class);
66+
LOGGER.debug("Successfully loaded token from cache: {}", cacheFile);
67+
return token;
68+
} catch (Exception e) {
69+
// If there's any issue loading the token, return null
70+
// to allow a fresh token to be obtained
71+
LOGGER.warn("Failed to load token from cache: {}", e.getMessage());
72+
return null;
73+
}
74+
}
75+
}

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,9 @@ protected Token refresh() {
125125

126126
// Save the refreshed token directly to cache
127127
if (tokenCache != null) {
128-
try {
129-
tokenCache.save(newToken);
130-
LOGGER.debug("Saved refreshed token to cache");
131-
} catch (Exception e) {
132-
LOGGER.warn("Failed to save token to cache: {}", e.getMessage());
133-
}
128+
tokenCache.save(newToken);
129+
LOGGER.debug("Saved refreshed token to cache");
134130
}
135-
136131
return newToken;
137132
}
138133
}

0 commit comments

Comments
 (0)