@@ -275,6 +275,7 @@ func TestDatabricksOidcTokenSource(t *testing.T) {
275275 ClientID : tc .clientID ,
276276 AccountID : tc .accountID ,
277277 Host : tc .host ,
278+ Scopes : []string {"all-apis" },
278279 TokenEndpointProvider : tc .oidcEndpointProvider ,
279280 Audience : tc .tokenAudience ,
280281 IDTokenSource : IDTokenSourceFn (func (ctx context.Context , aud string ) (* IDToken , error ) {
@@ -319,3 +320,134 @@ func TestDatabricksOidcTokenSource(t *testing.T) {
319320 })
320321 }
321322}
323+
324+ func TestWIF_Scopes (t * testing.T ) {
325+ tests := []struct {
326+ name string
327+ clientID string
328+ accountID string
329+ host string
330+ audience string
331+ scopes []string
332+ tokenEndpoint string
333+ expectedClientID string
334+ expectedScope string
335+ expectedAccessToken string
336+ }{
337+ {
338+ name : "single scope" ,
339+ clientID : "client-id" ,
340+ host : "http://host.com" ,
341+ audience : "token-audience" ,
342+ scopes : []string {"dashboards" },
343+ tokenEndpoint : "https://host.com/oidc/v1/token" ,
344+ expectedClientID : "client-id" ,
345+ expectedScope : "dashboards" ,
346+ expectedAccessToken : "test-token" ,
347+ },
348+ {
349+ name : "multiple scopes sorted" ,
350+ clientID : "client-id" ,
351+ host : "http://host.com" ,
352+ audience : "token-audience" ,
353+ scopes : []string {"files" , "jobs" , "mlflow" },
354+ tokenEndpoint : "https://host.com/oidc/v1/token" ,
355+ expectedClientID : "client-id" ,
356+ expectedScope : "files jobs mlflow" ,
357+ expectedAccessToken : "test-token" ,
358+ },
359+ {
360+ name : "workspace-level WIF" ,
361+ clientID : "client-id" ,
362+ host : "https://my-workspace.cloud.databricks.com" ,
363+ audience : "workspace-audience" ,
364+ scopes : []string {"genie" },
365+ tokenEndpoint : "https://my-workspace.cloud.databricks.com/oidc/v1/token" ,
366+ expectedClientID : "client-id" ,
367+ expectedScope : "genie" ,
368+ expectedAccessToken : "workspace-token" ,
369+ },
370+ {
371+ name : "account-level WIF" ,
372+ clientID : "client-id" ,
373+ accountID : "my-account" ,
374+ host : "https://accounts.cloud.databricks.com" ,
375+ audience : "account-audience" ,
376+ scopes : []string {"files" , "iam" },
377+ tokenEndpoint : "https://accounts.cloud.databricks.com/oidc/accounts/my-account/v1/token" ,
378+ expectedClientID : "client-id" ,
379+ expectedScope : "files iam" ,
380+ expectedAccessToken : "account-token" ,
381+ },
382+ {
383+ name : "account-wide token federation (no ClientID)" ,
384+ clientID : "" ,
385+ host : "http://host.com" ,
386+ audience : "token-audience" ,
387+ scopes : []string {"workspaces" },
388+ tokenEndpoint : "https://host.com/oidc/v1/token" ,
389+ expectedClientID : "" ,
390+ expectedScope : "workspaces" ,
391+ expectedAccessToken : "account-wide-token" ,
392+ },
393+ }
394+
395+ for _ , tt := range tests {
396+ t .Run (tt .name , func (t * testing.T ) {
397+ cfg := DatabricksOIDCTokenSourceConfig {
398+ ClientID : tt .clientID ,
399+ AccountID : tt .accountID ,
400+ Host : tt .host ,
401+ TokenEndpointProvider : func (ctx context.Context ) (* u2m.OAuthAuthorizationServer , error ) {
402+ return & u2m.OAuthAuthorizationServer {
403+ TokenEndpoint : tt .tokenEndpoint ,
404+ }, nil
405+ },
406+ Audience : tt .audience ,
407+ IDTokenSource : IDTokenSourceFn (func (ctx context.Context , aud string ) (* IDToken , error ) {
408+ return & IDToken {Value : "id-token" }, nil
409+ }),
410+ Scopes : tt .scopes ,
411+ }
412+
413+ ts := NewDatabricksOIDCTokenSource (cfg )
414+
415+ expectedRequest := url.Values {
416+ "scope" : {tt .expectedScope },
417+ "subject_token_type" : {"urn:ietf:params:oauth:token-type:jwt" },
418+ "subject_token" : {"id-token" },
419+ "grant_type" : {"urn:ietf:params:oauth:grant-type:token-exchange" },
420+ }
421+ if tt .expectedClientID != "" {
422+ expectedRequest ["client_id" ] = []string {tt .expectedClientID }
423+ }
424+
425+ endpointURL , _ := url .Parse (tt .tokenEndpoint )
426+ endpointPath := "POST " + endpointURL .Path
427+
428+ ctx := context .WithValue (context .Background (), oauth2 .HTTPClient , & http.Client {
429+ Transport : fixtures.MappingTransport {
430+ endpointPath : {
431+ Status : http .StatusOK ,
432+ ExpectedHeaders : map [string ]string {
433+ "Content-Type" : "application/x-www-form-urlencoded" ,
434+ },
435+ ExpectedRequest : expectedRequest ,
436+ Response : map [string ]string {
437+ "token_type" : "Bearer" ,
438+ "access_token" : tt .expectedAccessToken ,
439+ },
440+ },
441+ },
442+ })
443+
444+ token , err := ts .Token (ctx )
445+ if err != nil {
446+ t .Fatalf ("Token(ctx): got error %q, want none" , err )
447+ }
448+ if token .AccessToken != tt .expectedAccessToken {
449+ t .Errorf ("Token(ctx): got access token %q, want %q" , token .AccessToken , tt .expectedAccessToken )
450+ }
451+ })
452+ }
453+ }
0 commit comments