@@ -4,10 +4,16 @@ import HiveDriverError from '../../../errors/HiveDriverError';
44import { LogLevel } from '../../../contracts/IDBSQLLogger' ;
55import OAuthToken from './OAuthToken' ;
66import AuthorizationCode from './AuthorizationCode' ;
7- import { OAuthScope , OAuthScopes } from './OAuthScope' ;
7+ import { OAuthScope , OAuthScopes , scopeDelimiter } from './OAuthScope' ;
88import IClientContext from '../../../contracts/IClientContext' ;
99
10+ export enum OAuthFlow {
11+ U2M = 'U2M' ,
12+ M2M = 'M2M' ,
13+ }
14+
1015export interface OAuthManagerOptions {
16+ flow : OAuthFlow ;
1117 host : string ;
1218 callbackPorts ?: Array < number > ;
1319 clientId ?: string ;
@@ -47,9 +53,7 @@ export default abstract class OAuthManager {
4753
4854 protected abstract getCallbackPorts ( ) : Array < number > ;
4955
50- protected getScopes ( requestedScopes : OAuthScopes ) : OAuthScopes {
51- return requestedScopes ;
52- }
56+ protected abstract getScopes ( requestedScopes : OAuthScopes ) : OAuthScopes ;
5357
5458 protected async getClient ( ) : Promise < BaseClient > {
5559 // Obtain http agent each time when we need an OAuth client
@@ -113,17 +117,11 @@ export default abstract class OAuthManager {
113117 if ( ! accessToken || ! refreshToken ) {
114118 throw new Error ( 'Failed to refresh token: invalid response' ) ;
115119 }
116- return new OAuthToken ( accessToken , refreshToken ) ;
120+ return new OAuthToken ( accessToken , refreshToken , token . scopes ) ;
117121 }
118122
119- private async refreshAccessTokenM2M ( ) : Promise < OAuthToken > {
120- const { access_token : accessToken , refresh_token : refreshToken } = await this . getTokenM2M ( ) ;
121-
122- if ( ! accessToken ) {
123- throw new Error ( 'Failed to fetch access token' ) ;
124- }
125-
126- return new OAuthToken ( accessToken , refreshToken ) ;
123+ private async refreshAccessTokenM2M ( token : OAuthToken ) : Promise < OAuthToken > {
124+ return this . getTokenM2M ( token . scopes ?? [ ] ) ;
127125 }
128126
129127 public async refreshAccessToken ( token : OAuthToken ) : Promise < OAuthToken > {
@@ -137,10 +135,16 @@ export default abstract class OAuthManager {
137135 throw error ;
138136 }
139137
140- return this . options . clientSecret === undefined ? this . refreshAccessTokenU2M ( token ) : this . refreshAccessTokenM2M ( ) ;
138+ switch ( this . options . flow ) {
139+ case OAuthFlow . U2M :
140+ return this . refreshAccessTokenU2M ( token ) ;
141+ case OAuthFlow . M2M :
142+ return this . refreshAccessTokenM2M ( token ) ;
143+ // no default
144+ }
141145 }
142146
143- private async getTokenU2M ( scopes : OAuthScopes ) {
147+ private async getTokenU2M ( scopes : OAuthScopes ) : Promise < OAuthToken > {
144148 const client = await this . getClient ( ) ;
145149
146150 const authCode = new AuthorizationCode ( {
@@ -153,37 +157,47 @@ export default abstract class OAuthManager {
153157
154158 const { code, verifier, redirectUri } = await authCode . fetch ( mappedScopes ) ;
155159
156- return client . grant ( {
160+ const { access_token : accessToken , refresh_token : refreshToken } = await client . grant ( {
157161 grant_type : 'authorization_code' ,
158162 code,
159163 code_verifier : verifier ,
160164 redirect_uri : redirectUri ,
161165 } ) ;
166+
167+ if ( ! accessToken ) {
168+ throw new Error ( 'Failed to fetch access token' ) ;
169+ }
170+ return new OAuthToken ( accessToken , refreshToken , mappedScopes ) ;
162171 }
163172
164- private async getTokenM2M ( ) {
173+ private async getTokenM2M ( scopes : OAuthScopes ) : Promise < OAuthToken > {
165174 const client = await this . getClient ( ) ;
166175
176+ const mappedScopes = this . getScopes ( scopes ) ;
177+
167178 // M2M flow doesn't really support token refreshing, and refresh should not be available
168179 // in response. Each time access token expires, client can just acquire a new one using
169180 // client secret. Here we explicitly return access token only as a sign that we're not going
170181 // to use refresh token for M2M flow anywhere later
171182 const { access_token : accessToken } = await client . grant ( {
172183 grant_type : 'client_credentials' ,
173- scope : 'all-apis' , // this is the only allowed scope for M2M flow
184+ scope : mappedScopes . join ( scopeDelimiter ) ,
174185 } ) ;
175- return { access_token : accessToken , refresh_token : undefined } ;
176- }
177-
178- public async getToken ( scopes : OAuthScopes ) : Promise < OAuthToken > {
179- const { access_token : accessToken , refresh_token : refreshToken } =
180- this . options . clientSecret === undefined ? await this . getTokenU2M ( scopes ) : await this . getTokenM2M ( ) ;
181186
182187 if ( ! accessToken ) {
183188 throw new Error ( 'Failed to fetch access token' ) ;
184189 }
190+ return new OAuthToken ( accessToken , undefined , mappedScopes ) ;
191+ }
185192
186- return new OAuthToken ( accessToken , refreshToken ) ;
193+ public async getToken ( scopes : OAuthScopes ) : Promise < OAuthToken > {
194+ switch ( this . options . flow ) {
195+ case OAuthFlow . U2M :
196+ return this . getTokenU2M ( scopes ) ;
197+ case OAuthFlow . M2M :
198+ return this . getTokenM2M ( scopes ) ;
199+ // no default
200+ }
187201 }
188202
189203 public static getManager ( options : OAuthManagerOptions ) : OAuthManager {
@@ -245,6 +259,14 @@ export class DatabricksOAuthManager extends OAuthManager {
245259 protected getCallbackPorts ( ) : Array < number > {
246260 return this . options . callbackPorts ?? DatabricksOAuthManager . defaultCallbackPorts ;
247261 }
262+
263+ protected getScopes ( requestedScopes : OAuthScopes ) : OAuthScopes {
264+ if ( this . options . flow === OAuthFlow . M2M ) {
265+ // this is the only allowed scope for M2M flow
266+ return [ OAuthScope . allAPIs ] ;
267+ }
268+ return requestedScopes ;
269+ }
248270}
249271
250272export class AzureOAuthManager extends OAuthManager {
@@ -273,7 +295,18 @@ export class AzureOAuthManager extends OAuthManager {
273295 protected getScopes ( requestedScopes : OAuthScopes ) : OAuthScopes {
274296 // There is no corresponding scopes in Azure, instead, access control will be delegated to Databricks
275297 const tenantId = this . options . azureTenantId ?? AzureOAuthManager . datatricksAzureApp ;
276- const azureScopes = [ `${ tenantId } /user_impersonation` ] ;
298+
299+ const azureScopes = [ ] ;
300+
301+ switch ( this . options . flow ) {
302+ case OAuthFlow . U2M :
303+ azureScopes . push ( `${ tenantId } /user_impersonation` ) ;
304+ break ;
305+ case OAuthFlow . M2M :
306+ azureScopes . push ( `${ tenantId } /.default` ) ;
307+ break ;
308+ // no default
309+ }
277310
278311 if ( requestedScopes . includes ( OAuthScope . offlineAccess ) ) {
279312 azureScopes . push ( OAuthScope . offlineAccess ) ;
0 commit comments