Skip to content

Commit 5b01d59

Browse files
[PECO-1431] Support Databricks OAuth in Azure (#223)
* Support Databricks InHouse OAuth in Azure Signed-off-by: Jacky Hu <jacky.hu@databricks.com> * Rename AWSOAuthManager to DatabricksOAuthManager Signed-off-by: Jacky Hu <jacky.hu@databricks.com> * Remove dead code Signed-off-by: Jacky Hu <jacky.hu@databricks.com> * Format fix Signed-off-by: Jacky Hu <jacky.hu@databricks.com> * Fix domain list Signed-off-by: Jacky Hu <jacky.hu@databricks.com> * Refine code Signed-off-by: Levko Kravets <levko.ne@gmail.com> * Refine code Signed-off-by: Levko Kravets <levko.ne@gmail.com> --------- Signed-off-by: Jacky Hu <jacky.hu@databricks.com> Signed-off-by: Levko Kravets <levko.ne@gmail.com> Co-authored-by: Levko Kravets <levko.ne@gmail.com>
1 parent 8c590d7 commit 5b01d59

File tree

5 files changed

+62
-18
lines changed

5 files changed

+62
-18
lines changed

lib/DBSQLClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I
128128
azureTenantId: options.azureTenantId,
129129
clientId: options.oauthClientId,
130130
clientSecret: options.oauthClientSecret,
131+
useDatabricksOAuthInAzure: options.useDatabricksOAuthInAzure,
131132
context: this,
132133
});
133134
case 'custom':

lib/connection/auth/DatabricksOAuth/OAuthManager.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface OAuthManagerOptions {
1313
clientId?: string;
1414
azureTenantId?: string;
1515
clientSecret?: string;
16+
useDatabricksOAuthInAzure?: boolean;
1617
context: IClientContext;
1718
}
1819

@@ -189,24 +190,35 @@ export default abstract class OAuthManager {
189190
// normalize
190191
const host = options.host.toLowerCase().replace('https://', '').split('/')[0];
191192

192-
// eslint-disable-next-line @typescript-eslint/no-use-before-define
193-
const managers = [AWSOAuthManager, AzureOAuthManager];
193+
const awsDomains = ['.cloud.databricks.com', '.dev.databricks.com'];
194+
const isAWSDomain = awsDomains.some((domain) => host.endsWith(domain));
195+
if (isAWSDomain) {
196+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
197+
return new DatabricksOAuthManager(options);
198+
}
194199

195-
for (const OAuthManagerClass of managers) {
196-
for (const domain of OAuthManagerClass.domains) {
197-
if (host.endsWith(domain)) {
198-
return new OAuthManagerClass(options);
199-
}
200+
if (options.useDatabricksOAuthInAzure) {
201+
const domains = ['.azuredatabricks.net'];
202+
const isSupportedDomain = domains.some((domain) => host.endsWith(domain));
203+
if (isSupportedDomain) {
204+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
205+
return new DatabricksOAuthManager(options);
200206
}
201207
}
202208

209+
const azureDomains = ['.azuredatabricks.net', '.databricks.azure.us', '.databricks.azure.cn'];
210+
const isAzureDomain = azureDomains.some((domain) => host.endsWith(domain));
211+
if (isAzureDomain) {
212+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
213+
return new AzureOAuthManager(options);
214+
}
215+
203216
throw new Error(`OAuth is not supported for ${options.host}`);
204217
}
205218
}
206219

207-
export class AWSOAuthManager extends OAuthManager {
208-
public static domains = ['.cloud.databricks.com', '.dev.databricks.com'];
209-
220+
// Databricks InHouse OAuth Manager
221+
export class DatabricksOAuthManager extends OAuthManager {
210222
public static defaultClientId = 'databricks-sql-connector';
211223

212224
public static defaultCallbackPorts = [8030];
@@ -220,17 +232,15 @@ export class AWSOAuthManager extends OAuthManager {
220232
}
221233

222234
protected getClientId(): string {
223-
return this.options.clientId ?? AWSOAuthManager.defaultClientId;
235+
return this.options.clientId ?? DatabricksOAuthManager.defaultClientId;
224236
}
225237

226238
protected getCallbackPorts(): Array<number> {
227-
return this.options.callbackPorts ?? AWSOAuthManager.defaultCallbackPorts;
239+
return this.options.callbackPorts ?? DatabricksOAuthManager.defaultCallbackPorts;
228240
}
229241
}
230242

231243
export class AzureOAuthManager extends OAuthManager {
232-
public static domains = ['.azuredatabricks.net', '.databricks.azure.cn', '.databricks.azure.us'];
233-
234244
public static defaultClientId = '96eecda7-19ea-49cc-abb5-240097d554f5';
235245

236246
public static defaultCallbackPorts = [8030];

lib/contracts/IDBSQLClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type AuthOptions =
1919
azureTenantId?: string;
2020
oauthClientId?: string;
2121
oauthClientSecret?: string;
22+
useDatabricksOAuthInAzure?: boolean;
2223
}
2324
| {
2425
authType: 'custom';

tests/unit/DBSQLClient.test.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ const DBSQLSession = require('../../dist/DBSQLSession').default;
55

66
const PlainHttpAuthentication = require('../../dist/connection/auth/PlainHttpAuthentication').default;
77
const DatabricksOAuth = require('../../dist/connection/auth/DatabricksOAuth').default;
8-
const { AWSOAuthManager, AzureOAuthManager } = require('../../dist/connection/auth/DatabricksOAuth/OAuthManager');
8+
const {
9+
DatabricksOAuthManager,
10+
AzureOAuthManager,
11+
} = require('../../dist/connection/auth/DatabricksOAuth/OAuthManager');
912

1013
const HttpConnectionModule = require('../../dist/connection/connections/HttpConnection');
14+
1115
const { default: HttpConnection } = HttpConnectionModule;
1216

1317
class AuthProviderMock {
@@ -343,7 +347,7 @@ describe('DBSQLClient.initAuthProvider', () => {
343347
});
344348

345349
expect(provider).to.be.instanceOf(DatabricksOAuth);
346-
expect(provider.manager).to.be.instanceOf(AWSOAuthManager);
350+
expect(provider.manager).to.be.instanceOf(DatabricksOAuthManager);
347351
});
348352

349353
it('should use Databricks OAuth method (Azure)', () => {
@@ -359,6 +363,34 @@ describe('DBSQLClient.initAuthProvider', () => {
359363
expect(provider.manager).to.be.instanceOf(AzureOAuthManager);
360364
});
361365

366+
it('should use Databricks InHouse OAuth method (Azure)', () => {
367+
const client = new DBSQLClient();
368+
369+
case1: {
370+
const provider = client.initAuthProvider({
371+
authType: 'databricks-oauth',
372+
// host is used when creating OAuth manager, so make it look like a real Azure instance
373+
host: 'example.azuredatabricks.net',
374+
useDatabricksOAuthInAzure: true,
375+
});
376+
377+
expect(provider).to.be.instanceOf(DatabricksOAuth);
378+
expect(provider.manager).to.be.instanceOf(DatabricksOAuthManager);
379+
}
380+
381+
case2: {
382+
const provider = client.initAuthProvider({
383+
authType: 'databricks-oauth',
384+
// host is used when creating OAuth manager, so make it look like a real Azure instance
385+
host: 'example.databricks.azure.us',
386+
useDatabricksOAuthInAzure: true,
387+
});
388+
389+
expect(provider).to.be.instanceOf(DatabricksOAuth);
390+
expect(provider.manager).to.be.instanceOf(AzureOAuthManager);
391+
}
392+
});
393+
362394
it('should throw error when OAuth not supported for host', () => {
363395
const client = new DBSQLClient();
364396

tests/unit/connection/auth/DatabricksOAuth/OAuthManager.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const sinon = require('sinon');
33
const openidClientLib = require('openid-client');
44
const { DBSQLLogger, LogLevel } = require('../../../../../dist');
55
const {
6-
AWSOAuthManager,
6+
DatabricksOAuthManager,
77
AzureOAuthManager,
88
} = require('../../../../../dist/connection/auth/DatabricksOAuth/OAuthManager');
99
const OAuthToken = require('../../../../../dist/connection/auth/DatabricksOAuth/OAuthToken').default;
@@ -110,7 +110,7 @@ class OAuthClientMock {
110110
}
111111
}
112112

113-
[AWSOAuthManager, AzureOAuthManager].forEach((OAuthManagerClass) => {
113+
[DatabricksOAuthManager, AzureOAuthManager].forEach((OAuthManagerClass) => {
114114
function prepareTestInstances(options) {
115115
const oauthClient = new OAuthClientMock();
116116
sinon.stub(oauthClient, 'grant').callThrough();

0 commit comments

Comments
 (0)