golang.org/x/oauth2@v0.18.0/google/externalaccount/basecredentials_test.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package externalaccount
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"testing"
    14  	"time"
    15  
    16  	"golang.org/x/oauth2"
    17  )
    18  
    19  const (
    20  	textBaseCredPath             = "testdata/3pi_cred.txt"
    21  	jsonBaseCredPath             = "testdata/3pi_cred.json"
    22  	baseImpersonateCredsReqBody  = "audience=32555940559.apps.googleusercontent.com&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform&subject_token=street123&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Ajwt"
    23  	baseImpersonateCredsRespBody = `{"accessToken":"Second.Access.Token","expireTime":"2020-12-28T15:01:23Z"}`
    24  )
    25  
    26  var testBaseCredSource = CredentialSource{
    27  	File:   textBaseCredPath,
    28  	Format: Format{Type: fileTypeText},
    29  }
    30  
    31  var testConfig = Config{
    32  	Audience:         "32555940559.apps.googleusercontent.com",
    33  	SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
    34  	TokenInfoURL:     "http://localhost:8080/v1/tokeninfo",
    35  	ClientSecret:     "notsosecret",
    36  	ClientID:         "rbrgnognrhongo3bi4gb9ghg9g",
    37  	CredentialSource: &testBaseCredSource,
    38  	Scopes:           []string{"https://www.googleapis.com/auth/devstorage.full_control"},
    39  }
    40  
    41  var (
    42  	baseCredsRequestBody                          = "audience=32555940559.apps.googleusercontent.com&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdevstorage.full_control&subject_token=street123&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token"
    43  	baseCredsResponseBody                         = `{"access_token":"Sample.Access.Token","issued_token_type":"urn:ietf:params:oauth:token-type:access_token","token_type":"Bearer","expires_in":3600,"scope":"https://www.googleapis.com/auth/cloud-platform"}`
    44  	workforcePoolRequestBodyWithClientId          = "audience=%2F%2Fiam.googleapis.com%2Flocations%2Feu%2FworkforcePools%2Fpool-id%2Fproviders%2Fprovider-id&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdevstorage.full_control&subject_token=street123&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token"
    45  	workforcePoolRequestBodyWithoutClientId       = "audience=%2F%2Fiam.googleapis.com%2Flocations%2Feu%2FworkforcePools%2Fpool-id%2Fproviders%2Fprovider-id&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&options=%7B%22userProject%22%3A%22myProject%22%7D&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdevstorage.full_control&subject_token=street123&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token"
    46  	correctAT                                     = "Sample.Access.Token"
    47  	expiry                                  int64 = 234852
    48  )
    49  var (
    50  	testNow = func() time.Time { return time.Unix(expiry, 0) }
    51  )
    52  
    53  type testExchangeTokenServer struct {
    54  	url           string
    55  	authorization string
    56  	contentType   string
    57  	metricsHeader string
    58  	body          string
    59  	response      string
    60  }
    61  
    62  func run(t *testing.T, config *Config, tets *testExchangeTokenServer) (*oauth2.Token, error) {
    63  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    64  		if got, want := r.URL.String(), tets.url; got != want {
    65  			t.Errorf("URL.String(): got %v but want %v", got, want)
    66  		}
    67  		headerAuth := r.Header.Get("Authorization")
    68  		if got, want := headerAuth, tets.authorization; got != want {
    69  			t.Errorf("got %v but want %v", got, want)
    70  		}
    71  		headerContentType := r.Header.Get("Content-Type")
    72  		if got, want := headerContentType, tets.contentType; got != want {
    73  			t.Errorf("got %v but want %v", got, want)
    74  		}
    75  		headerMetrics := r.Header.Get("x-goog-api-client")
    76  		if got, want := headerMetrics, tets.metricsHeader; got != want {
    77  			t.Errorf("got %v but want %v", got, want)
    78  		}
    79  		body, err := ioutil.ReadAll(r.Body)
    80  		if err != nil {
    81  			t.Fatalf("Failed reading request body: %s.", err)
    82  		}
    83  		if got, want := string(body), tets.body; got != want {
    84  			t.Errorf("Unexpected exchange payload: got %v but want %v", got, want)
    85  		}
    86  		w.Header().Set("Content-Type", "application/json")
    87  		w.Write([]byte(tets.response))
    88  	}))
    89  	defer server.Close()
    90  	config.TokenURL = server.URL
    91  
    92  	oldNow := now
    93  	defer func() { now = oldNow }()
    94  	now = testNow
    95  
    96  	ts := tokenSource{
    97  		ctx:  context.Background(),
    98  		conf: config,
    99  	}
   100  
   101  	return ts.Token()
   102  }
   103  
   104  func validateToken(t *testing.T, tok *oauth2.Token) {
   105  	if got, want := tok.AccessToken, correctAT; got != want {
   106  		t.Errorf("Unexpected access token: got %v, but wanted %v", got, want)
   107  	}
   108  	if got, want := tok.TokenType, "Bearer"; got != want {
   109  		t.Errorf("Unexpected TokenType: got %v, but wanted %v", got, want)
   110  	}
   111  
   112  	if got, want := tok.Expiry, testNow().Add(time.Duration(3600)*time.Second); got != want {
   113  		t.Errorf("Unexpected Expiry: got %v, but wanted %v", got, want)
   114  	}
   115  }
   116  
   117  func createImpersonationServer(urlWanted, authWanted, bodyWanted, response string, t *testing.T) *httptest.Server {
   118  	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   119  		if got, want := r.URL.String(), urlWanted; got != want {
   120  			t.Errorf("URL.String(): got %v but want %v", got, want)
   121  		}
   122  		headerAuth := r.Header.Get("Authorization")
   123  		if got, want := headerAuth, authWanted; got != want {
   124  			t.Errorf("got %v but want %v", got, want)
   125  		}
   126  		headerContentType := r.Header.Get("Content-Type")
   127  		if got, want := headerContentType, "application/json"; got != want {
   128  			t.Errorf("got %v but want %v", got, want)
   129  		}
   130  		body, err := ioutil.ReadAll(r.Body)
   131  		if err != nil {
   132  			t.Fatalf("Failed reading request body: %v.", err)
   133  		}
   134  		if got, want := string(body), bodyWanted; got != want {
   135  			t.Errorf("Unexpected impersonation payload: got %v but want %v", got, want)
   136  		}
   137  		w.Header().Set("Content-Type", "application/json")
   138  		w.Write([]byte(response))
   139  	}))
   140  }
   141  
   142  func createTargetServer(metricsHeaderWanted string, t *testing.T) *httptest.Server {
   143  	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   144  		if got, want := r.URL.String(), "/"; got != want {
   145  			t.Errorf("URL.String(): got %v but want %v", got, want)
   146  		}
   147  		headerAuth := r.Header.Get("Authorization")
   148  		if got, want := headerAuth, "Basic cmJyZ25vZ25yaG9uZ28zYmk0Z2I5Z2hnOWc6bm90c29zZWNyZXQ="; got != want {
   149  			t.Errorf("got %v but want %v", got, want)
   150  		}
   151  		headerContentType := r.Header.Get("Content-Type")
   152  		if got, want := headerContentType, "application/x-www-form-urlencoded"; got != want {
   153  			t.Errorf("got %v but want %v", got, want)
   154  		}
   155  		headerMetrics := r.Header.Get("x-goog-api-client")
   156  		if got, want := headerMetrics, metricsHeaderWanted; got != want {
   157  			t.Errorf("got %v but want %v", got, want)
   158  		}
   159  		body, err := ioutil.ReadAll(r.Body)
   160  		if err != nil {
   161  			t.Fatalf("Failed reading request body: %v.", err)
   162  		}
   163  		if got, want := string(body), baseImpersonateCredsReqBody; got != want {
   164  			t.Errorf("Unexpected exchange payload: got %v but want %v", got, want)
   165  		}
   166  		w.Header().Set("Content-Type", "application/json")
   167  		w.Write([]byte(baseCredsResponseBody))
   168  	}))
   169  }
   170  
   171  func getExpectedMetricsHeader(source string, saImpersonation bool, configLifetime bool) string {
   172  	return fmt.Sprintf("gl-go/%s auth/unknown google-byoid-sdk source/%s sa-impersonation/%t config-lifetime/%t", goVersion(), source, saImpersonation, configLifetime)
   173  }
   174  
   175  func TestToken(t *testing.T) {
   176  	config := Config{
   177  		Audience:         "32555940559.apps.googleusercontent.com",
   178  		SubjectTokenType: "urn:ietf:params:oauth:token-type:id_token",
   179  		ClientSecret:     "notsosecret",
   180  		ClientID:         "rbrgnognrhongo3bi4gb9ghg9g",
   181  		CredentialSource: &testBaseCredSource,
   182  		Scopes:           []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   183  	}
   184  
   185  	server := testExchangeTokenServer{
   186  		url:           "/",
   187  		authorization: "Basic cmJyZ25vZ25yaG9uZ28zYmk0Z2I5Z2hnOWc6bm90c29zZWNyZXQ=",
   188  		contentType:   "application/x-www-form-urlencoded",
   189  		metricsHeader: getExpectedMetricsHeader("file", false, false),
   190  		body:          baseCredsRequestBody,
   191  		response:      baseCredsResponseBody,
   192  	}
   193  
   194  	tok, err := run(t, &config, &server)
   195  
   196  	if err != nil {
   197  		t.Fatalf("Unexpected error: %e", err)
   198  	}
   199  	validateToken(t, tok)
   200  }
   201  
   202  func TestWorkforcePoolTokenWithClientID(t *testing.T) {
   203  	config := Config{
   204  		Audience:                 "//iam.googleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id",
   205  		SubjectTokenType:         "urn:ietf:params:oauth:token-type:id_token",
   206  		ClientSecret:             "notsosecret",
   207  		ClientID:                 "rbrgnognrhongo3bi4gb9ghg9g",
   208  		CredentialSource:         &testBaseCredSource,
   209  		Scopes:                   []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   210  		WorkforcePoolUserProject: "myProject",
   211  	}
   212  
   213  	server := testExchangeTokenServer{
   214  		url:           "/",
   215  		authorization: "Basic cmJyZ25vZ25yaG9uZ28zYmk0Z2I5Z2hnOWc6bm90c29zZWNyZXQ=",
   216  		contentType:   "application/x-www-form-urlencoded",
   217  		metricsHeader: getExpectedMetricsHeader("file", false, false),
   218  		body:          workforcePoolRequestBodyWithClientId,
   219  		response:      baseCredsResponseBody,
   220  	}
   221  
   222  	tok, err := run(t, &config, &server)
   223  
   224  	if err != nil {
   225  		t.Fatalf("Unexpected error: %e", err)
   226  	}
   227  	validateToken(t, tok)
   228  }
   229  
   230  func TestWorkforcePoolTokenWithoutClientID(t *testing.T) {
   231  	config := Config{
   232  		Audience:                 "//iam.googleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id",
   233  		SubjectTokenType:         "urn:ietf:params:oauth:token-type:id_token",
   234  		ClientSecret:             "notsosecret",
   235  		CredentialSource:         &testBaseCredSource,
   236  		Scopes:                   []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   237  		WorkforcePoolUserProject: "myProject",
   238  	}
   239  
   240  	server := testExchangeTokenServer{
   241  		url:           "/",
   242  		authorization: "",
   243  		contentType:   "application/x-www-form-urlencoded",
   244  		metricsHeader: getExpectedMetricsHeader("file", false, false),
   245  		body:          workforcePoolRequestBodyWithoutClientId,
   246  		response:      baseCredsResponseBody,
   247  	}
   248  
   249  	tok, err := run(t, &config, &server)
   250  
   251  	if err != nil {
   252  		t.Fatalf("Unexpected error: %e", err)
   253  	}
   254  	validateToken(t, tok)
   255  }
   256  
   257  func TestNonworkforceWithWorkforcePoolUserProject(t *testing.T) {
   258  	config := Config{
   259  		Audience:                 "32555940559.apps.googleusercontent.com",
   260  		SubjectTokenType:         "urn:ietf:params:oauth:token-type:id_token",
   261  		TokenURL:                 "https://sts.googleapis.com",
   262  		ClientSecret:             "notsosecret",
   263  		ClientID:                 "rbrgnognrhongo3bi4gb9ghg9g",
   264  		CredentialSource:         &testBaseCredSource,
   265  		Scopes:                   []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   266  		WorkforcePoolUserProject: "myProject",
   267  	}
   268  
   269  	_, err := NewTokenSource(context.Background(), config)
   270  
   271  	if err == nil {
   272  		t.Fatalf("Expected error but found none")
   273  	}
   274  	if got, want := err.Error(), "oauth2/google/externalaccount: Workforce pool user project should not be set for non-workforce pool credentials"; got != want {
   275  		t.Errorf("Incorrect error received.\nExpected: %s\nRecieved: %s", want, got)
   276  	}
   277  }
   278  
   279  func TestWorkforcePoolCreation(t *testing.T) {
   280  	var audienceValidatyTests = []struct {
   281  		audience      string
   282  		expectSuccess bool
   283  	}{
   284  		{"//iam.googleapis.com/locations/global/workforcePools/pool-id/providers/provider-id", true},
   285  		{"//iam.googleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id", true},
   286  		{"//iam.googleapis.com/locations/eu/workforcePools/workloadIdentityPools/providers/provider-id", true},
   287  		{"identitynamespace:1f12345:my_provider", false},
   288  		{"//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/pool-id/providers/provider-id", false},
   289  		{"//iam.googleapis.com/projects/123456/locations/eu/workloadIdentityPools/pool-id/providers/provider-id", false},
   290  		{"//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/workforcePools/providers/provider-id", false},
   291  		{"//iamgoogleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id", false},
   292  		{"//iam.googleapiscom/locations/eu/workforcePools/pool-id/providers/provider-id", false},
   293  		{"//iam.googleapis.com/locations/workforcePools/pool-id/providers/provider-id", false},
   294  		{"//iam.googleapis.com/locations/eu/workforcePool/pool-id/providers/provider-id", false},
   295  		{"//iam.googleapis.com/locations//workforcePool/pool-id/providers/provider-id", false},
   296  	}
   297  
   298  	ctx := context.Background()
   299  	for _, tt := range audienceValidatyTests {
   300  		t.Run(" "+tt.audience, func(t *testing.T) { // We prepend a space ahead of the test input when outputting for sake of readability.
   301  			config := testConfig
   302  			config.TokenURL = "https://sts.googleapis.com" // Setting the most basic acceptable tokenURL
   303  			config.ServiceAccountImpersonationURL = "https://iamcredentials.googleapis.com"
   304  			config.Audience = tt.audience
   305  			config.WorkforcePoolUserProject = "myProject"
   306  			_, err := NewTokenSource(ctx, config)
   307  
   308  			if tt.expectSuccess && err != nil {
   309  				t.Errorf("got %v but want nil", err)
   310  			} else if !tt.expectSuccess && err == nil {
   311  				t.Errorf("got nil but expected an error")
   312  			}
   313  		})
   314  	}
   315  }
   316  
   317  var impersonationTests = []struct {
   318  	name                      string
   319  	config                    Config
   320  	expectedImpersonationBody string
   321  	expectedMetricsHeader     string
   322  }{
   323  	{
   324  		name: "Base Impersonation",
   325  		config: Config{
   326  			Audience:         "32555940559.apps.googleusercontent.com",
   327  			SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
   328  			TokenInfoURL:     "http://localhost:8080/v1/tokeninfo",
   329  			ClientSecret:     "notsosecret",
   330  			ClientID:         "rbrgnognrhongo3bi4gb9ghg9g",
   331  			CredentialSource: &testBaseCredSource,
   332  			Scopes:           []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   333  		},
   334  		expectedImpersonationBody: "{\"lifetime\":\"3600s\",\"scope\":[\"https://www.googleapis.com/auth/devstorage.full_control\"]}",
   335  		expectedMetricsHeader:     getExpectedMetricsHeader("file", true, false),
   336  	},
   337  	{
   338  		name: "With TokenLifetime Set",
   339  		config: Config{
   340  			Audience:         "32555940559.apps.googleusercontent.com",
   341  			SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
   342  			TokenInfoURL:     "http://localhost:8080/v1/tokeninfo",
   343  			ClientSecret:     "notsosecret",
   344  			ClientID:         "rbrgnognrhongo3bi4gb9ghg9g",
   345  			CredentialSource: &testBaseCredSource,
   346  			Scopes:           []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   347  			ServiceAccountImpersonationLifetimeSeconds: 10000,
   348  		},
   349  		expectedImpersonationBody: "{\"lifetime\":\"10000s\",\"scope\":[\"https://www.googleapis.com/auth/devstorage.full_control\"]}",
   350  		expectedMetricsHeader:     getExpectedMetricsHeader("file", true, true),
   351  	},
   352  }
   353  
   354  func TestImpersonation(t *testing.T) {
   355  	for _, tt := range impersonationTests {
   356  		t.Run(tt.name, func(t *testing.T) {
   357  			testImpersonateConfig := tt.config
   358  			impersonateServer := createImpersonationServer("/", "Bearer Sample.Access.Token", tt.expectedImpersonationBody, baseImpersonateCredsRespBody, t)
   359  			defer impersonateServer.Close()
   360  			testImpersonateConfig.ServiceAccountImpersonationURL = impersonateServer.URL
   361  
   362  			targetServer := createTargetServer(tt.expectedMetricsHeader, t)
   363  			defer targetServer.Close()
   364  			testImpersonateConfig.TokenURL = targetServer.URL
   365  
   366  			ourTS, err := testImpersonateConfig.tokenSource(context.Background(), "http")
   367  			if err != nil {
   368  				t.Fatalf("Failed to create TokenSource: %v", err)
   369  			}
   370  
   371  			oldNow := now
   372  			defer func() { now = oldNow }()
   373  			now = testNow
   374  
   375  			tok, err := ourTS.Token()
   376  			if err != nil {
   377  				t.Fatalf("Unexpected error: %e", err)
   378  			}
   379  			if got, want := tok.AccessToken, "Second.Access.Token"; got != want {
   380  				t.Errorf("Unexpected access token: got %v, but wanted %v", got, want)
   381  			}
   382  			if got, want := tok.TokenType, "Bearer"; got != want {
   383  				t.Errorf("Unexpected TokenType: got %v, but wanted %v", got, want)
   384  			}
   385  		})
   386  	}
   387  }
   388  
   389  var newTokenTests = []struct {
   390  	name   string
   391  	config Config
   392  }{
   393  	{
   394  		name: "Missing Audience",
   395  		config: Config{
   396  			SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
   397  			TokenInfoURL:     "http://localhost:8080/v1/tokeninfo",
   398  			ClientSecret:     "notsosecret",
   399  			ClientID:         "rbrgnognrhongo3bi4gb9ghg9g",
   400  			CredentialSource: &testBaseCredSource,
   401  			Scopes:           []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   402  			ServiceAccountImpersonationLifetimeSeconds: 10000,
   403  		},
   404  	},
   405  	{
   406  		name: "Missing Subject Token Type",
   407  		config: Config{
   408  			Audience:         "32555940559.apps.googleusercontent.com",
   409  			TokenInfoURL:     "http://localhost:8080/v1/tokeninfo",
   410  			ClientSecret:     "notsosecret",
   411  			ClientID:         "rbrgnognrhongo3bi4gb9ghg9g",
   412  			CredentialSource: &testBaseCredSource,
   413  			Scopes:           []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   414  			ServiceAccountImpersonationLifetimeSeconds: 10000,
   415  		},
   416  	},
   417  	{
   418  		name: "No Cred Source",
   419  		config: Config{
   420  			Audience:         "32555940559.apps.googleusercontent.com",
   421  			SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
   422  			TokenInfoURL:     "http://localhost:8080/v1/tokeninfo",
   423  			ClientSecret:     "notsosecret",
   424  			ClientID:         "rbrgnognrhongo3bi4gb9ghg9g",
   425  			Scopes:           []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   426  			ServiceAccountImpersonationLifetimeSeconds: 10000,
   427  		},
   428  	},
   429  	{
   430  		name: "Cred Source and Supplier",
   431  		config: Config{
   432  			Audience:                       "32555940559.apps.googleusercontent.com",
   433  			SubjectTokenType:               "urn:ietf:params:oauth:token-type:jwt",
   434  			TokenInfoURL:                   "http://localhost:8080/v1/tokeninfo",
   435  			CredentialSource:               &testBaseCredSource,
   436  			AwsSecurityCredentialsSupplier: testAwsSupplier{},
   437  			ClientSecret:                   "notsosecret",
   438  			ClientID:                       "rbrgnognrhongo3bi4gb9ghg9g",
   439  			Scopes:                         []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   440  			ServiceAccountImpersonationLifetimeSeconds: 10000,
   441  		},
   442  	},
   443  }
   444  
   445  func TestNewToken(t *testing.T) {
   446  	for _, tt := range newTokenTests {
   447  		t.Run(tt.name, func(t *testing.T) {
   448  			testConfig := tt.config
   449  
   450  			_, err := NewTokenSource(context.Background(), testConfig)
   451  			if err == nil {
   452  				t.Fatalf("expected error when calling NewToken()")
   453  			}
   454  		})
   455  	}
   456  }
   457  
   458  func TestConfig_TokenURL(t *testing.T) {
   459  	tests := []struct {
   460  		tokenURL       string
   461  		universeDomain string
   462  		want           string
   463  	}{
   464  		{
   465  			tokenURL:       "https://sts.googleapis.com/v1/token",
   466  			universeDomain: "",
   467  			want:           "https://sts.googleapis.com/v1/token",
   468  		},
   469  		{
   470  			tokenURL:       "",
   471  			universeDomain: "",
   472  			want:           "https://sts.googleapis.com/v1/token",
   473  		},
   474  		{
   475  			tokenURL:       "",
   476  			universeDomain: "googleapis.com",
   477  			want:           "https://sts.googleapis.com/v1/token",
   478  		},
   479  		{
   480  			tokenURL:       "",
   481  			universeDomain: "example.com",
   482  			want:           "https://sts.example.com/v1/token",
   483  		},
   484  	}
   485  	for _, tt := range tests {
   486  		config := &Config{
   487  			Audience:         "//iam.googleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id",
   488  			SubjectTokenType: "urn:ietf:params:oauth:token-type:id_token",
   489  			CredentialSource: &testBaseCredSource,
   490  			Scopes:           []string{"https://www.googleapis.com/auth/devstorage.full_control"},
   491  		}
   492  		config.TokenURL = tt.tokenURL
   493  		config.UniverseDomain = tt.universeDomain
   494  		config.parse(context.Background())
   495  		if got := config.TokenURL; got != tt.want {
   496  			t.Errorf("got %q, want %q", got, tt.want)
   497  		}
   498  	}
   499  }