-
Notifications
You must be signed in to change notification settings - Fork 27
[PECOBLR-131][PECOBLR-180] Add token cache for U2M OAuth by using sdk #783
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
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
1279614
Use Token Cache implemented in the sdk
vikrantpuppala 06d588f
Update SDK to the latest version
vikrantpuppala 162993b
send as query params for DELETE
vikrantpuppala 1988739
fix ut
vikrantpuppala 8fab66b
fix logs
vikrantpuppala de79ef7
try
vikrantpuppala 9dfb445
fix issues
vikrantpuppala cfb1789
clean pom
vikrantpuppala ade1dd3
Merge branch 'refs/heads/update-sdk' into sdk-token-cache
vikrantpuppala 511841a
add vscode to gitignore
vikrantpuppala 22265bd
make oauth2 redirect url port logic robust
vikrantpuppala aafc73c
Merge branch 'update-sdk' into sdk-token-cache
vikrantpuppala 0601f2e
Merge remote-tracking branch 'databricks/main' into sdk-token-cache
vikrantpuppala d1bf867
add tests
vikrantpuppala 8a2a4b6
update
vikrantpuppala e54c25e
update
vikrantpuppala de0c00a
update sdk version
vikrantpuppala 64089ca
Merge remote-tracking branch 'refs/remotes/databricks/main' into sdk-…
vikrantpuppala da7099d
address comments
vikrantpuppala 7baea52
address comments
vikrantpuppala 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
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
179 changes: 179 additions & 0 deletions
179
src/main/java/com/databricks/jdbc/auth/EncryptedFileTokenCache.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,179 @@ | ||
| package com.databricks.jdbc.auth; | ||
|
|
||
| import com.databricks.jdbc.log.JdbcLogger; | ||
| import com.databricks.jdbc.log.JdbcLoggerFactory; | ||
| import com.databricks.sdk.core.DatabricksException; | ||
| import com.databricks.sdk.core.oauth.Token; | ||
| import com.databricks.sdk.core.oauth.TokenCache; | ||
| import com.databricks.sdk.core.utils.SerDeUtils; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import java.io.File; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.security.SecureRandom; | ||
| import java.security.spec.KeySpec; | ||
| import java.util.Base64; | ||
| import java.util.Objects; | ||
| import javax.crypto.Cipher; | ||
| import javax.crypto.SecretKey; | ||
| import javax.crypto.SecretKeyFactory; | ||
| import javax.crypto.spec.IvParameterSpec; | ||
| import javax.crypto.spec.PBEKeySpec; | ||
| import javax.crypto.spec.SecretKeySpec; | ||
|
|
||
| /** A TokenCache implementation that stores tokens in encrypted files. */ | ||
| public class EncryptedFileTokenCache implements TokenCache { | ||
| private static final JdbcLogger LOGGER = | ||
| JdbcLoggerFactory.getLogger(EncryptedFileTokenCache.class); | ||
|
|
||
| // Encryption constants | ||
| private static final String ALGORITHM = "AES"; | ||
| private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding"; | ||
| private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA256"; | ||
| private static final byte[] SALT = "DatabricksJdbcTokenCache".getBytes(); | ||
| private static final int ITERATION_COUNT = 65536; | ||
| private static final int KEY_LENGTH = 256; | ||
| private static final int IV_SIZE = 16; // 128 bits | ||
|
|
||
| private final Path cacheFile; | ||
| private final ObjectMapper mapper; | ||
| private final String passphrase; | ||
|
|
||
| /** | ||
| * Constructs a new EncryptingFileTokenCache instance. | ||
| * | ||
| * @param cacheFilePath The path where the token cache will be stored | ||
| * @param passphrase The passphrase used for encryption | ||
| */ | ||
| public EncryptedFileTokenCache(Path cacheFilePath, String passphrase) { | ||
| Objects.requireNonNull(cacheFilePath, "cacheFilePath must be defined"); | ||
| Objects.requireNonNull(passphrase, "passphrase must be defined for encrypted token cache"); | ||
|
|
||
| this.cacheFile = cacheFilePath; | ||
| this.mapper = SerDeUtils.createMapper(); | ||
| this.passphrase = passphrase; | ||
| } | ||
|
|
||
| @Override | ||
| public void save(Token token) throws DatabricksException { | ||
| try { | ||
| Files.createDirectories(cacheFile.getParent()); | ||
|
|
||
| // Serialize token to JSON | ||
| String json = mapper.writeValueAsString(token); | ||
| byte[] dataToWrite = json.getBytes(StandardCharsets.UTF_8); | ||
|
|
||
| // Encrypt data | ||
| dataToWrite = encrypt(dataToWrite); | ||
|
|
||
| Files.write(cacheFile, dataToWrite); | ||
| // Set file permissions to be readable only by the owner (equivalent to 0600) | ||
| File file = cacheFile.toFile(); | ||
| file.setReadable(false, false); | ||
| file.setReadable(true, true); | ||
| file.setWritable(false, false); | ||
| file.setWritable(true, true); | ||
vikrantpuppala marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| LOGGER.debug("Successfully saved encrypted token to cache: %s", cacheFile); | ||
| } catch (Exception e) { | ||
| throw new DatabricksException("Failed to save token cache: " + e.getMessage(), e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public Token load() { | ||
| try { | ||
| if (!Files.exists(cacheFile)) { | ||
| LOGGER.debug("No token cache file found at: %s", cacheFile); | ||
| return null; | ||
| } | ||
|
|
||
| byte[] fileContent = Files.readAllBytes(cacheFile); | ||
|
|
||
| // Decrypt data | ||
| byte[] decodedContent; | ||
| try { | ||
| decodedContent = decrypt(fileContent); | ||
| } catch (Exception e) { | ||
| LOGGER.debug("Failed to decrypt token cache: %s", e.getMessage()); | ||
| return null; | ||
| } | ||
|
|
||
| // Deserialize token from JSON | ||
| String json = new String(decodedContent, StandardCharsets.UTF_8); | ||
| Token token = mapper.readValue(json, Token.class); | ||
| LOGGER.debug("Successfully loaded encrypted token from cache: %s", cacheFile); | ||
| return token; | ||
| } catch (Exception e) { | ||
| // If there's any issue loading the token, return null | ||
| // to allow a fresh token to be obtained | ||
| LOGGER.debug("Failed to load token from cache: %s", e.getMessage()); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Generates a secret key from the passphrase using PBKDF2 with HMAC-SHA256. | ||
| * | ||
| * @return A SecretKey generated from the passphrase | ||
| * @throws Exception If an error occurs generating the key | ||
| */ | ||
| private SecretKey generateSecretKey() throws Exception { | ||
| SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM); | ||
| KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), SALT, ITERATION_COUNT, KEY_LENGTH); | ||
| return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), ALGORITHM); | ||
| } | ||
|
|
||
| /** | ||
| * Encrypts the given data using AES/CBC/PKCS5Padding encryption with a key derived from the | ||
| * passphrase. The IV is generated randomly and prepended to the encrypted data. | ||
| * | ||
| * @param data The data to encrypt | ||
| * @return The encrypted data with IV prepended | ||
| * @throws Exception If an error occurs during encryption | ||
| */ | ||
| private byte[] encrypt(byte[] data) throws Exception { | ||
| Cipher cipher = Cipher.getInstance(TRANSFORMATION); | ||
|
|
||
| // Generate a random IV | ||
| SecureRandom random = new SecureRandom(); | ||
| byte[] iv = new byte[IV_SIZE]; | ||
| random.nextBytes(iv); | ||
| IvParameterSpec ivSpec = new IvParameterSpec(iv); | ||
|
|
||
| cipher.init(Cipher.ENCRYPT_MODE, generateSecretKey(), ivSpec); | ||
| byte[] encryptedData = cipher.doFinal(data); | ||
|
|
||
| // Combine IV and encrypted data | ||
| byte[] combined = new byte[iv.length + encryptedData.length]; | ||
| System.arraycopy(iv, 0, combined, 0, iv.length); | ||
| System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length); | ||
|
|
||
| return Base64.getEncoder().encode(combined); | ||
| } | ||
|
|
||
| /** | ||
| * Decrypts the given encrypted data using AES/CBC/PKCS5Padding decryption with a key derived from | ||
| * the passphrase. The IV is extracted from the beginning of the encrypted data. | ||
| * | ||
| * @param encryptedData The encrypted data with IV prepended, Base64 encoded | ||
| * @return The decrypted data | ||
| * @throws Exception If an error occurs during decryption | ||
| */ | ||
| private byte[] decrypt(byte[] encryptedData) throws Exception { | ||
| byte[] decodedData = Base64.getDecoder().decode(encryptedData); | ||
|
|
||
| // Extract IV | ||
| byte[] iv = new byte[IV_SIZE]; | ||
| byte[] actualData = new byte[decodedData.length - IV_SIZE]; | ||
| System.arraycopy(decodedData, 0, iv, 0, IV_SIZE); | ||
| System.arraycopy(decodedData, IV_SIZE, actualData, 0, actualData.length); | ||
|
|
||
| Cipher cipher = Cipher.getInstance(TRANSFORMATION); | ||
| IvParameterSpec ivSpec = new IvParameterSpec(iv); | ||
| cipher.init(Cipher.DECRYPT_MODE, generateSecretKey(), ivSpec); | ||
|
|
||
| return cipher.doFinal(actualData); | ||
| } | ||
| } | ||
25 changes: 25 additions & 0 deletions
25
src/main/java/com/databricks/jdbc/auth/NoOpTokenCache.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,25 @@ | ||
| package com.databricks.jdbc.auth; | ||
|
|
||
| import com.databricks.jdbc.log.JdbcLogger; | ||
| import com.databricks.jdbc.log.JdbcLoggerFactory; | ||
| import com.databricks.sdk.core.oauth.Token; | ||
| import com.databricks.sdk.core.oauth.TokenCache; | ||
|
|
||
| /** | ||
| * A no-operation implementation of TokenCache that does nothing. Used when token caching is | ||
| * explicitly disabled. | ||
| */ | ||
| public class NoOpTokenCache implements TokenCache { | ||
| private static final JdbcLogger LOGGER = JdbcLoggerFactory.getLogger(NoOpTokenCache.class); | ||
|
|
||
| @Override | ||
| public void save(Token token) { | ||
| LOGGER.debug("Token caching is disabled, skipping save operation"); | ||
| } | ||
|
|
||
| @Override | ||
| public Token load() { | ||
| LOGGER.debug("Token caching is disabled, skipping load operation"); | ||
| return null; | ||
| } | ||
| } |
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
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.