diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java index 8388c218e..006dee40d 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java @@ -5,6 +5,7 @@ import com.databricks.sdk.core.http.Request; import com.databricks.sdk.core.http.Response; import com.databricks.sdk.core.oauth.ErrorTokenSource; +import com.databricks.sdk.core.oauth.HostMetadata; import com.databricks.sdk.core.oauth.OAuthHeaderFactory; import com.databricks.sdk.core.oauth.OpenIDConnectEndpoints; import com.databricks.sdk.core.oauth.TokenSource; @@ -800,6 +801,32 @@ public OpenIDConnectEndpoints getDatabricksOidcEndpoints() throws IOException { return fetchOidcEndpointsFromDiscovery(); } + /** + * [Experimental] Fetch the raw Databricks well-known configuration from + * {host}/.well-known/databricks-config. + * + *

Note: This API is experimental and may change or be removed in future releases + * without notice. + * + * @return Parsed {@link HostMetadata} as returned by the server. + * @throws DatabricksException if the request fails or the server returns a non-200 status. + */ + HostMetadata getHostMetadata() throws IOException { + String url = host + "/.well-known/databricks-config"; + try { + Request request = new Request("GET", url); + Response resp = getHttpClient().execute(request); + if (resp.getStatusCode() != 200) { + throw new DatabricksException( + "Failed to fetch host metadata from " + url + ": HTTP " + resp.getStatusCode()); + } + return new ObjectMapper().readValue(resp.getBody(), HostMetadata.class); + } catch (IOException e) { + throw new DatabricksException( + "Failed to fetch host metadata from " + url + ": " + e.getMessage(), e); + } + } + private OpenIDConnectEndpoints fetchOidcEndpointsFromDiscovery() { try { Request request = new Request("GET", discoveryUrl); diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/HostMetadata.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/HostMetadata.java new file mode 100644 index 000000000..d45844262 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/HostMetadata.java @@ -0,0 +1,42 @@ +package com.databricks.sdk.core.oauth; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * [Experimental] Parsed response from the /.well-known/databricks-config discovery endpoint. + * + *

Note: This API is experimental and may change or be removed in future releases without + * notice. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class HostMetadata { + @JsonProperty("oidc_endpoint") + private String oidcEndpoint; + + @JsonProperty("account_id") + private String accountId; + + @JsonProperty("workspace_id") + private String workspaceId; + + public HostMetadata() {} + + public HostMetadata(String oidcEndpoint, String accountId, String workspaceId) { + this.oidcEndpoint = oidcEndpoint; + this.accountId = accountId; + this.workspaceId = workspaceId; + } + + public String getOidcEndpoint() { + return oidcEndpoint; + } + + public String getAccountId() { + return accountId; + } + + public String getWorkspaceId() { + return workspaceId; + } +} diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksConfigTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksConfigTest.java index ec7f0395f..f7b2d247b 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksConfigTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksConfigTest.java @@ -6,6 +6,7 @@ import com.databricks.sdk.core.commons.CommonsHttpClient; import com.databricks.sdk.core.http.HttpClient; import com.databricks.sdk.core.oauth.ErrorTokenSource; +import com.databricks.sdk.core.oauth.HostMetadata; import com.databricks.sdk.core.oauth.OAuthHeaderFactory; import com.databricks.sdk.core.oauth.OpenIDConnectEndpoints; import com.databricks.sdk.core.oauth.Token; @@ -421,4 +422,93 @@ public void testGetClientTypeAccountOnUnified() { .setExperimentalIsUnifiedHost(true) .getClientType()); } + + // --- HostMetadata tests --- + + private static final String DUMMY_ACCOUNT_ID = "00000000-0000-0000-0000-000000000001"; + private static final String DUMMY_WORKSPACE_ID = "111111111111111"; + + private static Environment emptyEnv() { + return new Environment(new HashMap<>(), new ArrayList<>(), System.getProperty("os.name")); + } + + @Test + public void testGetHostMetadataWorkspaceStaticOidcEndpoint() throws IOException { + String response = + "{\"oidc_endpoint\":\"https://ws.databricks.com/oidc\"," + + "\"account_id\":\"" + + DUMMY_ACCOUNT_ID + + "\"," + + "\"workspace_id\":\"" + + DUMMY_WORKSPACE_ID + + "\"}"; + try (FixtureServer server = + new FixtureServer().with("GET", "/.well-known/databricks-config", response, 200)) { + DatabricksConfig config = new DatabricksConfig().setHost(server.getUrl()); + config.resolve(emptyEnv()); + HostMetadata meta = config.getHostMetadata(); + assertEquals("https://ws.databricks.com/oidc", meta.getOidcEndpoint()); + assertEquals(DUMMY_ACCOUNT_ID, meta.getAccountId()); + assertEquals(DUMMY_WORKSPACE_ID, meta.getWorkspaceId()); + } + } + + @Test + public void testGetHostMetadataAccountRawOidcTemplate() throws IOException { + String response = + "{\"oidc_endpoint\":\"https://acc.databricks.com/oidc/accounts/{account_id}\"}"; + try (FixtureServer server = + new FixtureServer().with("GET", "/.well-known/databricks-config", response, 200)) { + DatabricksConfig config = new DatabricksConfig().setHost(server.getUrl()); + config.resolve(emptyEnv()); + HostMetadata meta = config.getHostMetadata(); + assertEquals("https://acc.databricks.com/oidc/accounts/{account_id}", meta.getOidcEndpoint()); + assertNull(meta.getAccountId()); + assertNull(meta.getWorkspaceId()); + } + } + + @Test + public void testGetHostMetadataRaisesOnHttpError() throws IOException { + try (FixtureServer server = + new FixtureServer().with("GET", "/.well-known/databricks-config", "{}", 404)) { + DatabricksConfig config = new DatabricksConfig().setHost(server.getUrl()); + config.resolve(emptyEnv()); + DatabricksException ex = + assertThrows(DatabricksException.class, () -> config.getHostMetadata()); + assertTrue(ex.getMessage().contains("Failed to fetch host metadata")); + } + } + + // --- discoveryUrl / OIDC endpoint tests --- + + @Test + public void testDiscoveryUrlFromEnv() { + Map env = new HashMap<>(); + env.put("DATABRICKS_DISCOVERY_URL", "https://custom.idp.example.com/oidc"); + DatabricksConfig config = new DatabricksConfig(); + config.resolve(new Environment(env, new ArrayList<>(), System.getProperty("os.name"))); + assertEquals("https://custom.idp.example.com/oidc", config.getDiscoveryUrl()); + } + + @Test + public void testDatabricksOidcEndpointsUsesDiscoveryUrl() throws IOException { + String discoveryUrlSuffix = "/oidc"; + String discoveryUrlResponse = + "{\"authorization_endpoint\":\"https://ws.databricks.com/oidc/v1/authorize\"," + + "\"token_endpoint\":\"https://ws.databricks.com/oidc/v1/token\"}"; + try (FixtureServer server = + new FixtureServer().with("GET", discoveryUrlSuffix, discoveryUrlResponse, 200)) { + String discoveryUrl = server.getUrl() + discoveryUrlSuffix; + OpenIDConnectEndpoints endpoints = + new DatabricksConfig() + .setHost(server.getUrl()) + .setDiscoveryUrl(discoveryUrl) + .setHttpClient(new CommonsHttpClient.Builder().withTimeoutSeconds(30).build()) + .getDatabricksOidcEndpoints(); + assertEquals( + "https://ws.databricks.com/oidc/v1/authorize", endpoints.getAuthorizationEndpoint()); + assertEquals("https://ws.databricks.com/oidc/v1/token", endpoints.getTokenEndpoint()); + } + } }