@@ -3,41 +3,62 @@ import HiveDriverError from '../../../errors/HiveDriverError';
3
3
import IDBSQLLogger , { LogLevel } from '../../../contracts/IDBSQLLogger' ;
4
4
import OAuthToken from './OAuthToken' ;
5
5
import AuthorizationCode from './AuthorizationCode' ;
6
-
7
- const oidcConfigPath = 'oidc/.well-known/oauth-authorization-server' ;
6
+ import { OAuthScope , OAuthScopes } from './OAuthScope' ;
8
7
9
8
export interface OAuthManagerOptions {
10
9
host : string ;
11
- callbackPorts : Array < number > ;
12
- clientId : string ;
10
+ callbackPorts ?: Array < number > ;
11
+ clientId ?: string ;
12
+ azureTenantId ?: string ;
13
13
logger ?: IDBSQLLogger ;
14
14
}
15
15
16
- export default class OAuthManager {
17
- private readonly options : OAuthManagerOptions ;
16
+ function getDatabricksOIDCUrl ( host : string ) : string {
17
+ const schema = host . startsWith ( 'https://' ) ? '' : 'https://' ;
18
+ const trailingSlash = host . endsWith ( '/' ) ? '' : '/' ;
19
+ return `${ schema } ${ host } ${ trailingSlash } oidc` ;
20
+ }
21
+
22
+ export default abstract class OAuthManager {
23
+ protected readonly options : OAuthManagerOptions ;
18
24
19
- private readonly logger ?: IDBSQLLogger ;
25
+ protected readonly logger ?: IDBSQLLogger ;
20
26
21
- private issuer ?: Issuer ;
27
+ protected issuer ?: Issuer ;
22
28
23
- private client ?: BaseClient ;
29
+ protected client ?: BaseClient ;
24
30
25
31
constructor ( options : OAuthManagerOptions ) {
26
32
this . options = options ;
27
33
this . logger = options . logger ;
28
34
}
29
35
30
- private async getClient ( ) : Promise < BaseClient > {
36
+ protected abstract getOIDCConfigUrl ( ) : string ;
37
+
38
+ protected abstract getAuthorizationUrl ( ) : string ;
39
+
40
+ protected abstract getClientId ( ) : string ;
41
+
42
+ protected abstract getCallbackPorts ( ) : Array < number > ;
43
+
44
+ protected getScopes ( requestedScopes : OAuthScopes ) : OAuthScopes {
45
+ return requestedScopes ;
46
+ }
47
+
48
+ protected async getClient ( ) : Promise < BaseClient > {
31
49
if ( ! this . issuer ) {
32
- const { host } = this . options ;
33
- const schema = host . startsWith ( 'https://' ) ? '' : 'https://' ;
34
- const trailingSlash = host . endsWith ( '/' ) ? '' : '/' ;
35
- this . issuer = await Issuer . discover ( `${ schema } ${ host } ${ trailingSlash } ${ oidcConfigPath } ` ) ;
50
+ const issuer = await Issuer . discover ( this . getOIDCConfigUrl ( ) ) ;
51
+ // Overwrite `authorization_endpoint` in default config (specifically needed for Azure flow
52
+ // where this URL has to be different)
53
+ this . issuer = new Issuer ( {
54
+ ...issuer . metadata ,
55
+ authorization_endpoint : this . getAuthorizationUrl ( ) ,
56
+ } ) ;
36
57
}
37
58
38
59
if ( ! this . client ) {
39
60
this . client = new this . issuer . Client ( {
40
- client_id : this . options . clientId ,
61
+ client_id : this . getClientId ( ) ,
41
62
token_endpoint_auth_method : 'none' ,
42
63
} ) ;
43
64
}
@@ -76,15 +97,17 @@ export default class OAuthManager {
76
97
return new OAuthToken ( accessToken , refreshToken ) ;
77
98
}
78
99
79
- public async getToken ( scopes : Array < string > ) : Promise < OAuthToken > {
100
+ public async getToken ( scopes : OAuthScopes ) : Promise < OAuthToken > {
80
101
const client = await this . getClient ( ) ;
81
102
const authCode = new AuthorizationCode ( {
82
103
client,
83
- ports : this . options . callbackPorts ,
104
+ ports : this . getCallbackPorts ( ) ,
84
105
logger : this . logger ,
85
106
} ) ;
86
107
87
- const { code, verifier, redirectUri } = await authCode . fetch ( scopes ) ;
108
+ const mappedScopes = this . getScopes ( scopes ) ;
109
+
110
+ const { code, verifier, redirectUri } = await authCode . fetch ( mappedScopes ) ;
88
111
89
112
const { access_token : accessToken , refresh_token : refreshToken } = await client . grant ( {
90
113
grant_type : 'authorization_code' ,
@@ -99,4 +122,84 @@ export default class OAuthManager {
99
122
100
123
return new OAuthToken ( accessToken , refreshToken ) ;
101
124
}
125
+
126
+ public static getManager ( options : OAuthManagerOptions ) : OAuthManager {
127
+ // normalize
128
+ const host = options . host . toLowerCase ( ) . replace ( 'https://' , '' ) . split ( '/' ) [ 0 ] ;
129
+
130
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
131
+ const managers = [ AWSOAuthManager , AzureOAuthManager ] ;
132
+
133
+ for ( const OAuthManagerClass of managers ) {
134
+ for ( const domain of OAuthManagerClass . domains ) {
135
+ if ( host . endsWith ( domain ) ) {
136
+ return new OAuthManagerClass ( options ) ;
137
+ }
138
+ }
139
+ }
140
+
141
+ throw new Error ( `OAuth is not supported for ${ options . host } ` ) ;
142
+ }
143
+ }
144
+
145
+ export class AWSOAuthManager extends OAuthManager {
146
+ public static domains = [ '.cloud.databricks.com' , '.dev.databricks.com' ] ;
147
+
148
+ public static defaultClientId = 'databricks-sql-connector' ;
149
+
150
+ public static defaultCallbackPorts = [ 8030 ] ;
151
+
152
+ protected getOIDCConfigUrl ( ) : string {
153
+ return `${ getDatabricksOIDCUrl ( this . options . host ) } /.well-known/oauth-authorization-server` ;
154
+ }
155
+
156
+ protected getAuthorizationUrl ( ) : string {
157
+ return `${ getDatabricksOIDCUrl ( this . options . host ) } /oauth2/v2.0/authorize` ;
158
+ }
159
+
160
+ protected getClientId ( ) : string {
161
+ return this . options . clientId ?? AWSOAuthManager . defaultClientId ;
162
+ }
163
+
164
+ protected getCallbackPorts ( ) : Array < number > {
165
+ return this . options . callbackPorts ?? AWSOAuthManager . defaultCallbackPorts ;
166
+ }
167
+ }
168
+
169
+ export class AzureOAuthManager extends OAuthManager {
170
+ public static domains = [ '.azuredatabricks.net' , '.databricks.azure.cn' , '.databricks.azure.us' ] ;
171
+
172
+ public static defaultClientId = '96eecda7-19ea-49cc-abb5-240097d554f5' ;
173
+
174
+ public static defaultCallbackPorts = [ 8030 ] ;
175
+
176
+ public static datatricksAzureApp = '2ff814a6-3304-4ab8-85cb-cd0e6f879c1d' ;
177
+
178
+ protected getOIDCConfigUrl ( ) : string {
179
+ return 'https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration' ;
180
+ }
181
+
182
+ protected getAuthorizationUrl ( ) : string {
183
+ return `${ getDatabricksOIDCUrl ( this . options . host ) } /oauth2/v2.0/authorize` ;
184
+ }
185
+
186
+ protected getClientId ( ) : string {
187
+ return this . options . clientId ?? AzureOAuthManager . defaultClientId ;
188
+ }
189
+
190
+ protected getCallbackPorts ( ) : Array < number > {
191
+ return this . options . callbackPorts ?? AzureOAuthManager . defaultCallbackPorts ;
192
+ }
193
+
194
+ protected getScopes ( requestedScopes : OAuthScopes ) : OAuthScopes {
195
+ // There is no corresponding scopes in Azure, instead, access control will be delegated to Databricks
196
+ const tenantId = this . options . azureTenantId ?? AzureOAuthManager . datatricksAzureApp ;
197
+ const azureScopes = [ `${ tenantId } /user_impersonation` ] ;
198
+
199
+ if ( requestedScopes . includes ( OAuthScope . offlineAccess ) ) {
200
+ azureScopes . push ( OAuthScope . offlineAccess ) ;
201
+ }
202
+
203
+ return azureScopes ;
204
+ }
102
205
}
0 commit comments