Skip to content

Commit 6f492bf

Browse files
committed
Add unit tests and comments
1 parent 2bc27a9 commit 6f492bf

File tree

5 files changed

+167
-8
lines changed

5 files changed

+167
-8
lines changed

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,25 @@
66
import org.slf4j.Logger;
77
import org.slf4j.LoggerFactory;
88

9+
/**
10+
* The DefaultCredentialsProvider is the primary authentication handler for the Databricks SDK. It
11+
* implements a chain of responsibility pattern to manage multiple authentication methods, including
12+
* Personal Access Tokens (PAT), OAuth, Azure, Google, and OpenID Connect (OIDC). The provider
13+
* attempts each authentication method in sequence until a valid credential is obtained.
14+
*/
915
public class DefaultCredentialsProvider implements CredentialsProvider {
1016
private static final Logger LOG = LoggerFactory.getLogger(DefaultCredentialsProvider.class);
1117

18+
// List of credential providers that will be tried in sequence
1219
private List<CredentialsProvider> providers = new ArrayList<>();
1320

21+
// The currently selected authentication type
1422
private String authType = "default";
1523

24+
/**
25+
* Internal class to associate an ID token source with a name for identification purposes. Used
26+
* primarily for OIDC (OpenID Connect) authentication flows.
27+
*/
1628
private static class NamedIDTokenSource {
1729
private final String name;
1830
private final IDTokenSource idTokenSource;
@@ -33,10 +45,23 @@ public IDTokenSource getIdTokenSource() {
3345

3446
public DefaultCredentialsProvider() {}
3547

48+
/**
49+
* Returns the current authentication type being used
50+
*
51+
* @return String representing the authentication type
52+
*/
3653
public String authType() {
3754
return authType;
3855
}
3956

57+
/**
58+
* Configures the credentials provider with the given Databricks configuration. This method tries
59+
* each available credential provider in sequence until one succeeds.
60+
*
61+
* @param config The Databricks configuration containing authentication details
62+
* @return HeaderFactory for making authenticated requests
63+
* @throws DatabricksException if no valid credentials can be configured
64+
*/
4065
@Override
4166
public synchronized HeaderFactory configure(DatabricksConfig config) {
4267
addDefaultCredentialsProviders(config);
@@ -69,6 +94,11 @@ public synchronized HeaderFactory configure(DatabricksConfig config) {
6994
+ " to configure credentials for your preferred authentication method");
7095
}
7196

97+
/**
98+
* Adds OpenID Connect (OIDC) based credential providers to the list of available providers.
99+
*
100+
* @param config The Databricks configuration containing OIDC settings
101+
*/
72102
private void addOIDCCredentialsProviders(DatabricksConfig config) {
73103
OpenIDConnectEndpoints endpoints = null;
74104
try {
@@ -88,6 +118,7 @@ private void addOIDCCredentialsProviders(DatabricksConfig config) {
88118
// Add new IDTokenSources and ID providers here. Example:
89119
// namedIdTokenSources.add(new NamedIDTokenSource("custom-oidc", new CustomIDTokenSource(...)));
90120

121+
// Configure OAuth token sources for each ID token source
91122
for (NamedIDTokenSource namedIdTokenSource : namedIdTokenSources) {
92123
DatabricksOAuthTokenSource oauthTokenSource =
93124
new DatabricksOAuthTokenSource.Builder(
@@ -105,11 +136,18 @@ private void addOIDCCredentialsProviders(DatabricksConfig config) {
105136
}
106137
}
107138

139+
/**
140+
* Initializes all available credential providers in the preferred order. The order of providers
141+
* determines the authentication fallback sequence.
142+
*
143+
* @param config The Databricks configuration to use for provider initialization
144+
*/
108145
private void addDefaultCredentialsProviders(DatabricksConfig config) {
109146
providers.add(new PatCredentialsProvider());
110147
providers.add(new BasicCredentialsProvider());
111148
providers.add(new OAuthM2MServicePrincipalCredentialsProvider());
112149

150+
// Add OIDC-based providers
113151
addOIDCCredentialsProviders(config);
114152

115153
providers.add(new AzureGithubOidcCredentialsProvider());

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,19 @@
99
import com.google.common.base.Strings;
1010
import java.io.IOException;
1111

12-
/** GithubIDTokenSource retrieves JWT Tokens from GitHub Actions. */
12+
/**
13+
* GithubIDTokenSource retrieves JWT Tokens from GitHub Actions. This class implements the
14+
* IDTokenSource interface and provides a method for obtaining ID tokens specifically from GitHub
15+
* Actions environment.
16+
*/
1317
public class GithubIDTokenSource implements IDTokenSource {
18+
// URL endpoint for requesting ID tokens from GitHub Actions
1419
private final String actionsIDTokenRequestURL;
20+
// Authentication token required to request ID tokens from GitHub Actions
1521
private final String actionsIDTokenRequestToken;
22+
// HTTP client for making requests to GitHub Actions
1623
private final HttpClient httpClient;
24+
// JSON mapper for parsing response data
1725
private final ObjectMapper mapper = new ObjectMapper();
1826

1927
/**
@@ -30,8 +38,18 @@ public GithubIDTokenSource(
3038
this.httpClient = httpClient;
3139
}
3240

41+
/**
42+
* Retrieves an ID token from GitHub Actions. This method makes an authenticated request to GitHub
43+
* Actions to obtain a JWT token that later can be exchanged for a Databricks access token.
44+
*
45+
* @param audience Optional audience claim for the token. If provided, it will be included in the
46+
* token request to GitHub Actions.
47+
* @return An IDToken object containing the JWT token value
48+
* @throws DatabricksException if the token request fails or if required configuration is missing
49+
*/
3350
@Override
3451
public IDToken getIDToken(String audience) {
52+
// Validate required configuration
3553
if (Strings.isNullOrEmpty(actionsIDTokenRequestURL)) {
3654
throw new DatabricksException("Missing ActionsIDTokenRequestURL");
3755
}
@@ -59,6 +77,7 @@ public IDToken getIDToken(String audience) {
5977
"Failed to request ID token from " + requestUrl + ": " + e.getMessage(), e);
6078
}
6179

80+
// Validate response status code
6281
if (resp.getStatusCode() != 200) {
6382
throw new DatabricksException(
6483
"Failed to request ID token: status code "
@@ -67,6 +86,7 @@ public IDToken getIDToken(String audience) {
6786
+ resp.getBody().toString());
6887
}
6988

89+
// Parse the JSON response
7090
ObjectNode jsonResp;
7191
try {
7292
jsonResp = mapper.readValue(resp.getBody(), ObjectNode.class);
@@ -75,6 +95,7 @@ public IDToken getIDToken(String audience) {
7595
"Failed to request ID token: corrupted token: " + e.getMessage());
7696
}
7797

98+
// Validate response structure and token value
7899
if (!jsonResp.has("value")) {
79100
throw new DatabricksException("ID token response missing 'value' field");
80101
}

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,38 @@
66
import java.util.HashMap;
77
import java.util.Map;
88

9-
/** Base class for token-based credentials providers. */
9+
/**
10+
* A credentials provider that uses a TokenSource to obtain and manage authentication tokens. This
11+
* class serves as a base implementation for token-based authentication, handling the conversion of
12+
* tokens into HTTP authorization headers.
13+
*
14+
* <p>The provider validates token availability during configuration and creates. appropriate
15+
* authorization headers for API requests.
16+
*/
1017
public class TokenSourceCredentialsProvider implements CredentialsProvider {
1118
private final TokenSource tokenSource;
1219
private final String authType;
1320

1421
/**
1522
* Creates a new TokenSourceCredentialsProvider with the specified token source and auth type.
1623
*
17-
* @param tokenSource The token source to use for token exchange
18-
* @param authType The authentication type string
24+
* @param tokenSource The token source responsible for token acquisition and management.
25+
* @param authType The authentication type identifier.
1926
*/
2027
public TokenSourceCredentialsProvider(TokenSource tokenSource, String authType) {
2128
this.tokenSource = tokenSource;
2229
this.authType = authType;
2330
}
2431

32+
/**
33+
* Configures the credentials provider and creates a HeaderFactory for generating authentication
34+
* headers. This method validates token availability by attempting to obtain an access token
35+
* before returning the HeaderFactory.
36+
*
37+
* @param config The Databricks configuration object.
38+
* @return A HeaderFactory that generates "Bearer" token authorization headers, or null if token
39+
* acquisition fails.
40+
*/
2541
@Override
2642
public HeaderFactory configure(DatabricksConfig config) {
2743
try {
@@ -38,6 +54,12 @@ public HeaderFactory configure(DatabricksConfig config) {
3854
}
3955
}
4056

57+
/**
58+
* Returns the authentication type identifier for this credentials provider. This is used to
59+
* identify the authentication method being used.
60+
*
61+
* @return The authentication type string
62+
*/
4163
@Override
4264
public String authType() {
4365
return authType;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.databricks.sdk.core.oauth;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
import static org.mockito.Mockito.*;
5+
6+
import com.databricks.sdk.core.DatabricksConfig;
7+
import com.databricks.sdk.core.DatabricksException;
8+
import com.databricks.sdk.core.HeaderFactory;
9+
import java.time.LocalDateTime;
10+
import java.util.Map;
11+
import java.util.stream.Stream;
12+
import org.junit.jupiter.params.ParameterizedTest;
13+
import org.junit.jupiter.params.provider.Arguments;
14+
import org.junit.jupiter.params.provider.MethodSource;
15+
16+
/** Tests for TokenSourceCredentialsProvider */
17+
class TokenSourceCredentialsProviderTest {
18+
private TokenSourceCredentialsProvider provider;
19+
private static final String TEST_AUTH_TYPE = "test-auth-type";
20+
private static final String TEST_ACCESS_TOKEN_VALUE = "test-access-token";
21+
private static final Token TEST_TOKEN =
22+
new Token(TEST_ACCESS_TOKEN_VALUE, "Bearer", LocalDateTime.now().plusHours(1));
23+
24+
/** Tests token retrieval scenarios */
25+
@ParameterizedTest(name = "{0}")
26+
@MethodSource("provideTokenScenarios")
27+
void testTokenScenarios(
28+
String testName,
29+
TokenSource mockTokenSource,
30+
String expectedAuthHeader,
31+
Exception expectedException) {
32+
provider = new TokenSourceCredentialsProvider(mockTokenSource, TEST_AUTH_TYPE);
33+
DatabricksConfig config = new DatabricksConfig();
34+
HeaderFactory headerFactory = provider.configure(config);
35+
36+
if (expectedException != null) {
37+
assertNull(headerFactory);
38+
} else {
39+
assertNotNull(headerFactory);
40+
Map<String, String> headers = headerFactory.headers();
41+
assertEquals(expectedAuthHeader, headers.get("Authorization"));
42+
}
43+
verify(mockTokenSource).getToken();
44+
assertEquals(TEST_AUTH_TYPE, provider.authType());
45+
}
46+
47+
/** Provides test scenarios */
48+
private static Stream<Arguments> provideTokenScenarios() {
49+
// Mock behaviour of successful token retrieval
50+
TokenSource mockSuccessTokenSource = mock(TokenSource.class);
51+
when(mockSuccessTokenSource.getToken()).thenReturn(TEST_TOKEN);
52+
53+
// Mock behaviour of failing token retrieval
54+
TokenSource mockFailureTokenSource = mock(TokenSource.class);
55+
when(mockFailureTokenSource.getToken())
56+
.thenThrow(new DatabricksException("Token retrieval failed"));
57+
58+
return Stream.of(
59+
// Success case
60+
Arguments.of(
61+
"Successful token retrieval",
62+
mockSuccessTokenSource,
63+
"Bearer " + TEST_ACCESS_TOKEN_VALUE,
64+
null),
65+
// Failure case
66+
Arguments.of(
67+
"Failed token retrieval",
68+
mockFailureTokenSource,
69+
null,
70+
new DatabricksException("Token retrieval failed")));
71+
}
72+
}

examples/docs/src/main/java/com/databricks/example/GithubOIDCAuthExample.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,26 @@
88

99
/**
1010
* Example demonstrating how to use GitHub OIDC authentication with Databricks.
11+
*
12+
* IMPORTANT: This example only works when running within GitHub Actions.
1113
*/
1214
public class GithubOIDCAuthExample {
1315

1416
public static void main(String[] args) {
1517
// Create Databricks configuration with GitHub OIDC authentication
18+
// Note: This configuration assumes the code is running in GitHub Actions
1619
DatabricksConfig config = new DatabricksConfig()
17-
.setAuthType("github-oidc")
18-
.setHost("https://accounts.cloud.databricks.com/")
19-
.setAccountId("968367da-7edd-44f7-9dea-3e0b20b0ec97")
20-
.setClientId("dd3b5d58-5a6e-45e3-a3ae-81d8f89fc6fd");
20+
.setAuthType("github-oidc") // Specifies GitHub OIDC as the authentication method
21+
.setHost("<INSERT_HOST>") // Databricks account URL
22+
.setAccountId("<INSERT_ACCOUNT_ID>") // Your Databricks account ID
23+
.setClientId("<INSERT_CLIENT_ID>"); // Service Principal ID
2124

25+
// Initialize the Account client with the OIDC configuration
2226
AccountClient account = new AccountClient(config);
2327

2428
try {
29+
// Example: List all groups in the Databricks account
30+
// This demonstrates that the OIDC authentication is working
2531
System.out.println("\nListing account groups:");
2632
Iterable<Group> groups = account.groups().list(new ListAccountGroupsRequest());
2733
for (Group group : groups) {

0 commit comments

Comments
 (0)