Skip to content

Commit 52f3818

Browse files
committed
feat(databricks-jdbc-driver): Support M2M OAuth Authentication
1 parent 7c6342c commit 52f3818

File tree

3 files changed

+101
-9
lines changed

3 files changed

+101
-9
lines changed

packages/cubejs-backend-shared/src/env.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,32 @@ const variables: Record<string, (...args: any) => any> = {
997997
keyByDataSource('CUBEJS_DB_DATABRICKS_CATALOG', dataSource)
998998
],
999999

1000+
/**
1001+
* Databricks OAuth Client ID (Same as the service principal UUID)
1002+
*/
1003+
databricksOAuthClientId: ({
1004+
dataSource,
1005+
}: {
1006+
dataSource: string,
1007+
}) => (
1008+
process.env[
1009+
keyByDataSource('CUBEJS_DB_DATABRICKS_OAUTH_CLIENT_ID', dataSource)
1010+
]
1011+
),
1012+
1013+
/**
1014+
* Databricks OAuth Client Secret.
1015+
*/
1016+
databricksOAuthClientSecret: ({
1017+
dataSource,
1018+
}: {
1019+
dataSource: string,
1020+
}) => (
1021+
process.env[
1022+
keyByDataSource('CUBEJS_DB_DATABRICKS_OAUTH_CLIENT_SECRET', dataSource)
1023+
]
1024+
),
1025+
10001026
/** ****************************************************************
10011027
* Athena Driver *
10021028
***************************************************************** */

packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ export type DatabricksDriverConfiguration = JDBCDriverConfiguration &
9090
*/
9191
token?: string,
9292

93+
/**
94+
* Databricks OAuth Client ID.
95+
*/
96+
oauthClientId?: string,
97+
98+
/**
99+
* Databricks OAuth Client Secret.
100+
*/
101+
oauthClientSecret?: string,
102+
93103
/**
94104
* Azure tenant Id
95105
*/
@@ -200,6 +210,41 @@ export class DatabricksDriver extends JDBCDriver {
200210
}
201211

202212
const [uid, pwd, cleanedUrl] = extractAndRemoveUidPwdFromJdbcUrl(url);
213+
const passwd = conf?.token ||
214+
getEnv('databricksToken', { dataSource }) ||
215+
pwd;
216+
const oauthClientId = conf?.oauthClientId || getEnv('databricksOAuthClientId', { dataSource });
217+
const oauthClientSecret = conf?.oauthClientSecret || getEnv('databricksOAuthClientSecret', { dataSource });
218+
219+
if (oauthClientId && !oauthClientSecret) {
220+
throw new Error('Invalid credentials: No OAuth Client Secret provided');
221+
} else if (!oauthClientId && oauthClientSecret) {
222+
throw new Error('Invalid credentials: No OAuth Client ID provided');
223+
} else if (!oauthClientId && !oauthClientSecret && !passwd) {
224+
throw new Error('No credentials provided');
225+
}
226+
227+
const user = uid || 'token';
228+
229+
let authProps = {};
230+
231+
// OAuth has an advantage over UID+PWD
232+
// For magic numbers below - see Databricks docs:
233+
// https://docs.databricks.com/aws/en/integrations/jdbc-oss/configure#authenticate-the-driver
234+
if (oauthClientId) {
235+
authProps = {
236+
OAuth2ClientID: oauthClientId,
237+
OAuth2Secret: oauthClientSecret,
238+
AuthMech: 11,
239+
Auth_Flow: 1,
240+
};
241+
} else {
242+
authProps = {
243+
UID: user,
244+
PWD: passwd,
245+
AuthMech: 3,
246+
};
247+
}
203248

204249
const config: DatabricksDriverConfiguration = {
205250
...conf,
@@ -208,11 +253,7 @@ export class DatabricksDriver extends JDBCDriver {
208253
drivername: 'com.databricks.client.jdbc.Driver',
209254
customClassPath: undefined,
210255
properties: {
211-
UID: uid,
212-
PWD:
213-
conf?.token ||
214-
getEnv('databricksToken', { dataSource }) ||
215-
pwd,
256+
...authProps,
216257
UserAgentEntry: 'CubeDev_Cube',
217258
},
218259
catalog:
@@ -292,7 +333,31 @@ export class DatabricksDriver extends JDBCDriver {
292333
}
293334

294335
public override async testConnection() {
295-
const token = `Bearer ${this.config.properties.PWD}`;
336+
let token: string;
337+
338+
// Databricks docs on accessing REST API
339+
// https://docs.databricks.com/aws/en/dev-tools/auth/oauth-m2m
340+
if (this.config.properties.OAuth2Secret) {
341+
// Need to exchange client ID + Secret => Access token
342+
343+
const basicAuth = Buffer.from(`${this.config.properties.OAuth2ClientID}:${this.config.properties.OAuth2Secret}`).toString('base64');
344+
345+
const res = await fetch(`https://${this.parsedConnectionProperties.host}/oidc/v1/token`, {
346+
method: 'POST',
347+
headers: {
348+
Authorization: `Basic ${basicAuth}`,
349+
'Content-Type': 'application/x-www-form-urlencoded',
350+
},
351+
body: new URLSearchParams({
352+
grant_type: 'client_credentials',
353+
scope: 'all-apis',
354+
}),
355+
});
356+
const resp = await res.json();
357+
token = resp.access_token;
358+
} else {
359+
token = `Bearer ${this.config.properties.PWD}`;
360+
}
296361

297362
const res = await fetch(`https://${this.parsedConnectionProperties.host}/api/2.0/sql/warehouses/${this.parsedConnectionProperties.warehouseId}`, {
298363
headers: { Authorization: token },

packages/cubejs-databricks-jdbc-driver/src/helpers.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,21 @@ export async function resolveJDBCDriver(): Promise<string> {
3535

3636
/**
3737
* Extract if exist UID and PWD from URL and return UID, PWD and URL without these params.
38-
* New Databricks OSS driver throws an error if UID and PWD are provided in the URL and as a separate params
38+
* New Databricks OSS driver throws an error if any parameter is provided in the URL and as a separate param
3939
* passed to the driver instance. That's why we strip them out from the URL if they exist there.
4040
* @param jdbcUrl
4141
*/
4242
export function extractAndRemoveUidPwdFromJdbcUrl(jdbcUrl: string): [uid: string, pwd: string, cleanedUrl: string] {
4343
const uidMatch = jdbcUrl.match(/UID=([^;]*)/i);
4444
const pwdMatch = jdbcUrl.match(/PWD=([^;]*)/i);
4545

46-
const uid = uidMatch?.[1] || 'token';
46+
const uid = uidMatch?.[1] || '';
4747
const pwd = pwdMatch?.[1] || '';
4848

4949
const cleanedUrl = jdbcUrl
5050
.replace(/;?UID=[^;]*/i, '')
51-
.replace(/;?PWD=[^;]*/i, '');
51+
.replace(/;?PWD=[^;]*/i, '')
52+
.replace(/;?AuthMech=[^;]*/i, '');
5253

5354
return [uid, pwd, cleanedUrl];
5455
}

0 commit comments

Comments
 (0)