...

Source file src/cloud.google.com/go/auth/credentials/detect_test.go

Documentation: cloud.google.com/go/auth/credentials

     1  // Copyright 2023 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package credentials
    16  
    17  import (
    18  	"context"
    19  	"encoding/base64"
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"os"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"cloud.google.com/go/auth"
    30  	"cloud.google.com/go/auth/credentials/internal/gdch"
    31  	"cloud.google.com/go/auth/internal"
    32  	"cloud.google.com/go/auth/internal/credsfile"
    33  	"cloud.google.com/go/auth/internal/jwt"
    34  )
    35  
    36  type tokResp struct {
    37  	AccessToken string `json:"access_token"`
    38  	TokenType   string `json:"token_type"`
    39  	ExpiresIn   int    `json:"expires_in"`
    40  }
    41  
    42  func TestDefaultCredentials_GdchServiceAccountKey(t *testing.T) {
    43  	ctx := context.Background()
    44  	aud := "http://sample-aud.com/"
    45  	b, err := os.ReadFile("../internal/testdata/gdch.json")
    46  	if err != nil {
    47  		t.Fatal(err)
    48  	}
    49  	f, err := credsfile.ParseGDCHServiceAccount(b)
    50  	if err != nil {
    51  		t.Fatal(err)
    52  	}
    53  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    54  		if r.Method != "POST" {
    55  			t.Errorf("unexpected request method: %v", r.Method)
    56  		}
    57  		if err := r.ParseForm(); err != nil {
    58  			t.Error(err)
    59  		}
    60  		parts := strings.Split(r.FormValue("subject_token"), ".")
    61  		var header jwt.Header
    62  		var claims jwt.Claims
    63  		b, err = base64.RawURLEncoding.DecodeString(parts[0])
    64  		if err != nil {
    65  			t.Fatal(err)
    66  		}
    67  		if err := json.Unmarshal(b, &header); err != nil {
    68  			t.Fatal(err)
    69  		}
    70  		b, err = base64.RawURLEncoding.DecodeString(parts[1])
    71  		if err != nil {
    72  			t.Fatal(err)
    73  		}
    74  		if err := json.Unmarshal(b, &claims); err != nil {
    75  			t.Fatal(err)
    76  		}
    77  
    78  		if got := r.FormValue("audience"); got != aud {
    79  			t.Errorf("got audience %v, want %v", got, gdch.GrantType)
    80  		}
    81  		if want := jwt.HeaderAlgRSA256; header.Algorithm != want {
    82  			t.Errorf("got alg %q, want %q", header.Algorithm, want)
    83  		}
    84  		if want := jwt.HeaderType; header.Type != want {
    85  			t.Errorf("got typ %q, want %q", header.Type, want)
    86  		}
    87  		if want := "abcdef1234567890"; header.KeyID != want {
    88  			t.Errorf("got kid %q, want %q", header.KeyID, want)
    89  		}
    90  
    91  		if want := "system:serviceaccount:fake_project:sa_name"; claims.Iss != want {
    92  			t.Errorf("got iss %q, want %q", claims.Iss, want)
    93  		}
    94  		if want := "system:serviceaccount:fake_project:sa_name"; claims.Sub != want {
    95  			t.Errorf("got sub %q, want %q", claims.Sub, want)
    96  		}
    97  		if want := fmt.Sprintf("http://%s", r.Host); claims.Aud != want {
    98  			t.Errorf("got aud %q, want %q", claims.Aud, want)
    99  		}
   100  		resp := &tokResp{
   101  			AccessToken: "a_fake_token",
   102  			TokenType:   internal.TokenTypeBearer,
   103  			ExpiresIn:   60,
   104  		}
   105  		if err := json.NewEncoder(w).Encode(&resp); err != nil {
   106  			t.Fatal(err)
   107  		}
   108  	}))
   109  	f.TokenURL = ts.URL
   110  	f.CertPath = "../internal/testdata/cert.pem"
   111  	b, err = json.Marshal(&f)
   112  	if err != nil {
   113  		t.Fatal(err)
   114  	}
   115  
   116  	if _, err := DetectDefault(&DetectOptions{CredentialsJSON: b}); err == nil {
   117  		t.Fatal("STSAudience should be required")
   118  	}
   119  	creds, err := DetectDefault(&DetectOptions{
   120  		CredentialsJSON: b,
   121  		STSAudience:     aud,
   122  	})
   123  	if err != nil {
   124  		t.Fatal(err)
   125  	}
   126  	got, err := creds.ProjectID(ctx)
   127  	if err != nil {
   128  		t.Fatal(err)
   129  	}
   130  	if want := "fake_project"; got != want {
   131  		t.Fatalf("got %q, want %q", got, want)
   132  	}
   133  	got, err = creds.UniverseDomain(ctx)
   134  	if err != nil {
   135  		t.Fatal(err)
   136  	}
   137  	if want := "googleapis.com"; got != want {
   138  		t.Fatalf("got %q, want %q", got, want)
   139  	}
   140  	tok, err := creds.Token(context.Background())
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	if want := "a_fake_token"; tok.Value != want {
   145  		t.Fatalf("got AccessToken %q, want %q", tok.Value, want)
   146  	}
   147  	if want := internal.TokenTypeBearer; tok.Type != want {
   148  		t.Fatalf("got TokenType %q, want %q", tok.Type, want)
   149  	}
   150  }
   151  
   152  func TestDefaultCredentials_ImpersonatedServiceAccountKey(t *testing.T) {
   153  	ctx := context.Background()
   154  	b, err := os.ReadFile("../internal/testdata/imp.json")
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	f, err := credsfile.ParseImpersonatedServiceAccount(b)
   159  	if err != nil {
   160  		t.Fatal(err)
   161  	}
   162  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   163  		resp := &struct {
   164  			AccessToken string `json:"accessToken"`
   165  			ExpireTime  string `json:"expireTime"`
   166  		}{
   167  			AccessToken: "a_fake_token",
   168  			ExpireTime:  "2006-01-02T15:04:05Z",
   169  		}
   170  		if err := json.NewEncoder(w).Encode(&resp); err != nil {
   171  			t.Fatal(err)
   172  		}
   173  	}))
   174  	f.ServiceAccountImpersonationURL = ts.URL
   175  	b, err = json.Marshal(f)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	creds, err := DetectDefault(&DetectOptions{
   181  		CredentialsJSON:  b,
   182  		Scopes:           []string{"https://www.googleapis.com/auth/cloud-platform"},
   183  		UseSelfSignedJWT: true,
   184  	})
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  	got, err := creds.UniverseDomain(ctx)
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  	if want := "googleapis.com"; got != want {
   193  		t.Fatalf("got %q, want %q", got, want)
   194  	}
   195  	tok, err := creds.Token(context.Background())
   196  	if err != nil {
   197  		t.Fatalf("creds.Token() = %v", err)
   198  	}
   199  	if want := "a_fake_token"; tok.Value != want {
   200  		t.Fatalf("got %q, want %q", tok.Value, want)
   201  	}
   202  	if want := internal.TokenTypeBearer; tok.Type != want {
   203  		t.Fatalf("got %q, want %q", tok.Type, want)
   204  	}
   205  }
   206  
   207  func TestDefaultCredentials_UserCredentialsKey(t *testing.T) {
   208  	ctx := context.Background()
   209  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   210  		w.Header().Set("Content-Type", "application/json")
   211  		resp := &tokResp{
   212  			AccessToken: "a_fake_token",
   213  			TokenType:   internal.TokenTypeBearer,
   214  			ExpiresIn:   60,
   215  		}
   216  		if err := json.NewEncoder(w).Encode(&resp); err != nil {
   217  			t.Fatal(err)
   218  		}
   219  	}))
   220  
   221  	creds, err := DetectDefault(&DetectOptions{
   222  		CredentialsFile: "../internal/testdata/user.json",
   223  		Scopes:          []string{"https://www.googleapis.com/auth/cloud-platform"},
   224  		TokenURL:        ts.URL,
   225  	})
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  	got, err := creds.QuotaProjectID(ctx)
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	if want := "fake_project2"; got != want {
   234  		t.Fatalf("got %q, want %q", got, want)
   235  	}
   236  	got, err = creds.UniverseDomain(ctx)
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  	if want := "googleapis.com"; got != want {
   241  		t.Fatalf("got %q, want %q", got, want)
   242  	}
   243  	tok, err := creds.Token(context.Background())
   244  	if err != nil {
   245  		t.Fatalf("creds.Token() = %v", err)
   246  	}
   247  	if want := "a_fake_token"; tok.Value != want {
   248  		t.Fatalf("got %q, want %q", tok.Value, want)
   249  	}
   250  	if want := internal.TokenTypeBearer; tok.Type != want {
   251  		t.Fatalf("got %q, want %q", tok.Type, want)
   252  	}
   253  }
   254  
   255  func TestDefaultCredentials_UserCredentialsKey_UniverseDomain(t *testing.T) {
   256  	ctx := context.Background()
   257  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   258  		w.Header().Set("Content-Type", "application/json")
   259  		resp := &tokResp{
   260  			AccessToken: "a_fake_token",
   261  			TokenType:   internal.TokenTypeBearer,
   262  			ExpiresIn:   60,
   263  		}
   264  		if err := json.NewEncoder(w).Encode(&resp); err != nil {
   265  			t.Fatal(err)
   266  		}
   267  	}))
   268  
   269  	creds, err := DetectDefault(&DetectOptions{
   270  		CredentialsFile: "../internal/testdata/user_universe_domain.json",
   271  		Scopes:          []string{"https://www.googleapis.com/auth/cloud-platform"},
   272  		TokenURL:        ts.URL,
   273  	})
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	got, err := creds.QuotaProjectID(ctx)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	if want := "fake_project2"; got != want {
   282  		t.Fatalf("got %q, want %q", got, want)
   283  	}
   284  	got, err = creds.UniverseDomain(ctx)
   285  	if err != nil {
   286  		t.Fatal(err)
   287  	}
   288  	if want := "example.com"; got != want {
   289  		t.Fatalf("got %q, want %q", got, want)
   290  	}
   291  	tok, err := creds.Token(context.Background())
   292  	if err != nil {
   293  		t.Fatalf("creds.Token() = %v", err)
   294  	}
   295  	if want := "a_fake_token"; tok.Value != want {
   296  		t.Fatalf("got %q, want %q", tok.Value, want)
   297  	}
   298  	if want := internal.TokenTypeBearer; tok.Type != want {
   299  		t.Fatalf("got %q, want %q", tok.Type, want)
   300  	}
   301  }
   302  
   303  func TestDefaultCredentials_ServiceAccountKey(t *testing.T) {
   304  	ctx := context.Background()
   305  	b, err := os.ReadFile("../internal/testdata/sa.json")
   306  	if err != nil {
   307  		t.Fatal(err)
   308  	}
   309  	f, err := credsfile.ParseServiceAccount(b)
   310  	if err != nil {
   311  		t.Fatal(err)
   312  	}
   313  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   314  		resp := &tokResp{
   315  			AccessToken: "a_fake_token",
   316  			TokenType:   internal.TokenTypeBearer,
   317  			ExpiresIn:   60,
   318  		}
   319  		if err := json.NewEncoder(w).Encode(&resp); err != nil {
   320  			t.Fatal(err)
   321  		}
   322  	}))
   323  	f.TokenURL = ts.URL
   324  	b, err = json.Marshal(f)
   325  	if err != nil {
   326  		t.Fatal(err)
   327  	}
   328  
   329  	creds, err := DetectDefault(&DetectOptions{
   330  		CredentialsJSON: b,
   331  		Scopes:          []string{"https://www.googleapis.com/auth/cloud-platform"},
   332  	})
   333  	if err != nil {
   334  		t.Fatal(err)
   335  	}
   336  	got, err := creds.ProjectID(ctx)
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  	if want := "fake_project"; got != want {
   341  		t.Fatalf("got %q, want %q", got, want)
   342  	}
   343  	got, err = creds.UniverseDomain(ctx)
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  	if want := "googleapis.com"; got != want {
   348  		t.Fatalf("got %q, want %q", got, want)
   349  	}
   350  	tok, err := creds.Token(context.Background())
   351  	if err != nil {
   352  		t.Fatalf("creds.Token() = %v", err)
   353  	}
   354  	if want := "a_fake_token"; tok.Value != want {
   355  		t.Fatalf("got %q, want %q", tok.Value, want)
   356  	}
   357  	if want := internal.TokenTypeBearer; tok.Type != want {
   358  		t.Fatalf("got %q, want %q", tok.Type, want)
   359  	}
   360  }
   361  
   362  func TestDefaultCredentials_ServiceAccountKeySelfSigned(t *testing.T) {
   363  	ctx := context.Background()
   364  	b, err := os.ReadFile("../internal/testdata/sa.json")
   365  	if err != nil {
   366  		t.Fatal(err)
   367  	}
   368  	oldNow := now
   369  	now = func() time.Time { return time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC) }
   370  	defer func() { now = oldNow }()
   371  	wantTok := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiY2RlZjEyMzQ1Njc4OTAifQ.eyJpc3MiOiJnb3BoZXJAZmFrZV9wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic2NvcGUiOiJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9hdXRoL2Nsb3VkLXBsYXRmb3JtIiwiZXhwIjo5NDk0MTE4MDAsImlhdCI6OTQ5NDA4MjAwLCJhdWQiOiIiLCJzdWIiOiJnb3BoZXJAZmFrZV9wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIn0.n9Hggd-1Vw4WTQiWkh7q9r5eDsz-khU5vwkZl2VmgdUF3ZxDq1ARzchCNtTifeorzbp9C0i0vCr855G7FZkVCJXPVMcnxbwfMSafUYmVsmutbQiV9eTWfWM0_Ljiwa9GEbv1bN06Lz4LrelPKEaxsDbY6tU8LJUiome_gSMLfLk"
   372  
   373  	creds, err := DetectDefault(&DetectOptions{
   374  		CredentialsJSON:  b,
   375  		Scopes:           []string{"https://www.googleapis.com/auth/cloud-platform"},
   376  		UseSelfSignedJWT: true,
   377  	})
   378  	if err != nil {
   379  		t.Fatal(err)
   380  	}
   381  
   382  	got, err := creds.ProjectID(ctx)
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	if want := "fake_project"; got != want {
   387  		t.Fatalf("got %q, want %q", got, want)
   388  	}
   389  	got, err = creds.UniverseDomain(ctx)
   390  	if err != nil {
   391  		t.Fatal(err)
   392  	}
   393  	if want := "googleapis.com"; got != want {
   394  		t.Fatalf("got %q, want %q", got, want)
   395  	}
   396  	tok, err := creds.Token(context.Background())
   397  	if err != nil {
   398  		t.Fatalf("creds.Token() = %v", err)
   399  	}
   400  	if tok.Value != wantTok {
   401  		t.Fatalf("got %q, want %q", tok.Value, wantTok)
   402  	}
   403  	if want := internal.TokenTypeBearer; tok.Type != want {
   404  		t.Fatalf("got %q, want %q", tok.Type, want)
   405  	}
   406  }
   407  
   408  func TestDefaultCredentials_ServiceAccountKeySelfSigned_UniverseDomain(t *testing.T) {
   409  	ctx := context.Background()
   410  	b, err := os.ReadFile("../internal/testdata/sa_universe_domain.json")
   411  	if err != nil {
   412  		t.Fatal(err)
   413  	}
   414  	oldNow := now
   415  	now = func() time.Time { return time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC) }
   416  	defer func() { now = oldNow }()
   417  	wantTok := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiY2RlZjEyMzQ1Njc4OTAifQ.eyJpc3MiOiJnb3BoZXJAZmFrZV9wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic2NvcGUiOiJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9hdXRoL2Nsb3VkLXBsYXRmb3JtIiwiZXhwIjo5NDk0MTE4MDAsImlhdCI6OTQ5NDA4MjAwLCJhdWQiOiIiLCJzdWIiOiJnb3BoZXJAZmFrZV9wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIn0.n9Hggd-1Vw4WTQiWkh7q9r5eDsz-khU5vwkZl2VmgdUF3ZxDq1ARzchCNtTifeorzbp9C0i0vCr855G7FZkVCJXPVMcnxbwfMSafUYmVsmutbQiV9eTWfWM0_Ljiwa9GEbv1bN06Lz4LrelPKEaxsDbY6tU8LJUiome_gSMLfLk"
   418  
   419  	creds, err := DetectDefault(&DetectOptions{
   420  		CredentialsJSON:  b,
   421  		Scopes:           []string{"https://www.googleapis.com/auth/cloud-platform"},
   422  		UseSelfSignedJWT: true,
   423  	})
   424  	if err != nil {
   425  		t.Fatal(err)
   426  	}
   427  	got, err := creds.ProjectID(ctx)
   428  	if err != nil {
   429  		t.Fatal(err)
   430  	}
   431  	if want := "fake_project"; got != want {
   432  		t.Fatalf("got %q, want %q", got, want)
   433  	}
   434  	got, err = creds.UniverseDomain(ctx)
   435  	if err != nil {
   436  		t.Fatal(err)
   437  	}
   438  	if want := "example.com"; got != want {
   439  		t.Fatalf("got %q, want %q", got, want)
   440  	}
   441  	tok, err := creds.Token(context.Background())
   442  	if err != nil {
   443  		t.Fatalf("creds.Token() = %v", err)
   444  	}
   445  	if tok.Value != wantTok {
   446  		t.Fatalf("got %q, want %q", tok.Value, wantTok)
   447  	}
   448  	if want := internal.TokenTypeBearer; tok.Type != want {
   449  		t.Fatalf("got %q, want %q", tok.Type, want)
   450  	}
   451  }
   452  
   453  func TestDefaultCredentials_ClientCredentials(t *testing.T) {
   454  	ctx := context.Background()
   455  	b, err := os.ReadFile("../internal/testdata/clientcreds_installed.json")
   456  	if err != nil {
   457  		t.Fatal(err)
   458  	}
   459  	f, err := credsfile.ParseClientCredentials(b)
   460  	if err != nil {
   461  		t.Fatal(err)
   462  	}
   463  
   464  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   465  		w.Header().Set("Content-Type", "application/json")
   466  		resp := &tokResp{
   467  			AccessToken: "a_fake_token",
   468  			TokenType:   internal.TokenTypeBearer,
   469  			ExpiresIn:   60,
   470  		}
   471  		if err := json.NewEncoder(w).Encode(&resp); err != nil {
   472  			t.Fatal(err)
   473  		}
   474  	}))
   475  	f.Installed.TokenURI = ts.URL
   476  	b, err = json.Marshal(f)
   477  	if err != nil {
   478  		t.Fatal(err)
   479  	}
   480  
   481  	creds, err := DetectDefault(&DetectOptions{
   482  		CredentialsJSON: b,
   483  		Scopes:          []string{"https://www.googleapis.com/auth/cloud-platform"},
   484  		TokenURL:        ts.URL,
   485  		AuthHandlerOptions: &auth.AuthorizationHandlerOptions{
   486  			Handler: func(authCodeURL string) (code string, state string, err error) {
   487  				return "code", "state", nil
   488  			},
   489  			State: "state",
   490  			PKCEOpts: &auth.PKCEOptions{
   491  				Challenge:       "codeChallenge",
   492  				ChallengeMethod: "plain",
   493  				Verifier:        "codeChallenge",
   494  			},
   495  		},
   496  	})
   497  	if err != nil {
   498  		t.Fatal(err)
   499  	}
   500  	got, err := creds.UniverseDomain(ctx)
   501  	if err != nil {
   502  		t.Fatal(err)
   503  	}
   504  	if want := "googleapis.com"; got != want {
   505  		t.Fatalf("got %q, want %q", got, want)
   506  	}
   507  	tok, err := creds.Token(context.Background())
   508  	if err != nil {
   509  		t.Fatalf("creds.Token() = %v", err)
   510  	}
   511  	if want := "a_fake_token"; tok.Value != want {
   512  		t.Fatalf("got %q, want %q", tok.Value, want)
   513  	}
   514  	if want := internal.TokenTypeBearer; tok.Type != want {
   515  		t.Fatalf("got %q, want %q", tok.Type, want)
   516  	}
   517  }
   518  
   519  // Better coverage of all external account features tested in the sub-package.
   520  func TestDefaultCredentials_ExternalAccountKey(t *testing.T) {
   521  	ctx := context.Background()
   522  	b, err := os.ReadFile("../internal/testdata/exaccount_url.json")
   523  	if err != nil {
   524  		t.Fatal(err)
   525  	}
   526  	f, err := credsfile.ParseExternalAccount(b)
   527  	if err != nil {
   528  		t.Fatal(err)
   529  	}
   530  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   531  		defer r.Body.Close()
   532  		if r.URL.Path == "/token" {
   533  			resp := &struct {
   534  				Token string `json:"id_token"`
   535  			}{
   536  				Token: "a_fake_token_base",
   537  			}
   538  			if err := json.NewEncoder(w).Encode(&resp); err != nil {
   539  				t.Error(err)
   540  			}
   541  		} else if r.URL.Path == "/sts" {
   542  			r.ParseForm()
   543  			if got, want := r.Form.Get("subject_token"), "a_fake_token_base"; got != want {
   544  				t.Errorf("got %q, want %q", got, want)
   545  			}
   546  
   547  			resp := &struct {
   548  				AccessToken string `json:"access_token"`
   549  				ExpiresIn   int    `json:"expires_in"`
   550  			}{
   551  				AccessToken: "a_fake_token_sts",
   552  				ExpiresIn:   60,
   553  			}
   554  			if err := json.NewEncoder(w).Encode(&resp); err != nil {
   555  				t.Error(err)
   556  			}
   557  		} else if r.URL.Path == "/impersonate" {
   558  			if want := "a_fake_token_sts"; !strings.Contains(r.Header.Get("Authorization"), want) {
   559  				t.Errorf("missing sts token: got %q, want %q", r.Header.Get("Authorization"), want)
   560  			}
   561  
   562  			resp := &struct {
   563  				AccessToken string `json:"accessToken"`
   564  				ExpireTime  string `json:"expireTime"`
   565  			}{
   566  				AccessToken: "a_fake_token",
   567  				ExpireTime:  "2006-01-02T15:04:05Z",
   568  			}
   569  			if err := json.NewEncoder(w).Encode(&resp); err != nil {
   570  				t.Error(err)
   571  			}
   572  		} else {
   573  			t.Errorf("unexpected call to %q", r.URL.Path)
   574  		}
   575  	}))
   576  	f.ServiceAccountImpersonationURL = ts.URL + "/impersonate"
   577  	f.CredentialSource.URL = ts.URL + "/token"
   578  	f.TokenURL = ts.URL + "/sts"
   579  	b, err = json.Marshal(f)
   580  	if err != nil {
   581  		t.Fatal(err)
   582  	}
   583  
   584  	creds, err := DetectDefault(&DetectOptions{
   585  		CredentialsJSON:  b,
   586  		Scopes:           []string{"https://www.googleapis.com/auth/cloud-platform"},
   587  		UseSelfSignedJWT: true,
   588  	})
   589  	if err != nil {
   590  		t.Fatal(err)
   591  	}
   592  	got, err := creds.UniverseDomain(ctx)
   593  	if err != nil {
   594  		t.Fatal(err)
   595  	}
   596  	if want := "googleapis.com"; got != want {
   597  		t.Fatalf("got %q, want %q", got, want)
   598  	}
   599  	tok, err := creds.Token(ctx)
   600  	if err != nil {
   601  		t.Fatalf("creds.Token() = %v", err)
   602  	}
   603  	if want := "a_fake_token"; tok.Value != want {
   604  		t.Fatalf("got %q, want %q", tok.Value, want)
   605  	}
   606  	if want := internal.TokenTypeBearer; tok.Type != want {
   607  		t.Fatalf("got %q, want %q", tok.Type, want)
   608  	}
   609  }
   610  func TestDefaultCredentials_ExternalAccountAuthorizedUserKey(t *testing.T) {
   611  	b, err := os.ReadFile("../internal/testdata/exaccount_user.json")
   612  	if err != nil {
   613  		t.Fatal(err)
   614  	}
   615  	f, err := credsfile.ParseExternalAccountAuthorizedUser(b)
   616  	if err != nil {
   617  		t.Fatal(err)
   618  	}
   619  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   620  		defer r.Body.Close()
   621  		if got, want := r.URL.Path, "/sts"; got != want {
   622  			t.Errorf("got %q, want %q", got, want)
   623  		}
   624  		r.ParseForm()
   625  		if got, want := r.Form.Get("refresh_token"), "refreshing"; got != want {
   626  			t.Errorf("got %q, want %q", got, want)
   627  		}
   628  		if got, want := r.Form.Get("grant_type"), "refresh_token"; got != want {
   629  			t.Errorf("got %q, want %q", got, want)
   630  		}
   631  
   632  		resp := &struct {
   633  			AccessToken string `json:"access_token"`
   634  			ExpiresIn   int    `json:"expires_in"`
   635  		}{
   636  			AccessToken: "a_fake_token",
   637  			ExpiresIn:   60,
   638  		}
   639  		if err := json.NewEncoder(w).Encode(&resp); err != nil {
   640  			t.Error(err)
   641  		}
   642  	}))
   643  	f.TokenURL = ts.URL + "/sts"
   644  	b, err = json.Marshal(f)
   645  	if err != nil {
   646  		t.Fatal(err)
   647  	}
   648  
   649  	creds, err := DetectDefault(&DetectOptions{
   650  		CredentialsJSON:  b,
   651  		Scopes:           []string{"https://www.googleapis.com/auth/cloud-platform"},
   652  		UseSelfSignedJWT: true,
   653  	})
   654  	if err != nil {
   655  		t.Fatal(err)
   656  	}
   657  	tok, err := creds.Token(context.Background())
   658  	if err != nil {
   659  		t.Fatalf("creds.Token() = %v", err)
   660  	}
   661  	if want := "a_fake_token"; tok.Value != want {
   662  		t.Fatalf("got %q, want %q", tok.Value, want)
   663  	}
   664  	if want := internal.TokenTypeBearer; tok.Type != want {
   665  		t.Fatalf("got %q, want %q", tok.Type, want)
   666  	}
   667  }
   668  
   669  func TestDefaultCredentials_Fails(t *testing.T) {
   670  	t.Setenv(credsfile.GoogleAppCredsEnvVar, "nothingToSeeHere")
   671  	t.Setenv("HOME", "nothingToSeeHere")
   672  	t.Setenv("APPDATA", "nothingToSeeHere")
   673  	allowOnGCECheck = false
   674  	defer func() { allowOnGCECheck = true }()
   675  	if _, err := DetectDefault(&DetectOptions{
   676  		Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
   677  	}); !strings.Contains(err.Error(), adcSetupURL) {
   678  		t.Fatalf("got %v, wanted to contain %v", err, adcSetupURL)
   679  	}
   680  }
   681  
   682  func TestDefaultCredentials_BadFiletype(t *testing.T) {
   683  	if _, err := DetectDefault(&DetectOptions{
   684  		CredentialsJSON: []byte(`{"type":"42"}`),
   685  		Scopes:          []string{"https://www.googleapis.com/auth/cloud-platform"},
   686  	}); err == nil {
   687  		t.Fatal("got nil, want non-nil err")
   688  	}
   689  }
   690  
   691  func TestDefaultCredentials_BadFileName(t *testing.T) {
   692  	if _, err := DetectDefault(&DetectOptions{
   693  		CredentialsFile: "a/bad/filepath",
   694  		Scopes:          []string{"https://www.googleapis.com/auth/cloud-platform"},
   695  	}); err == nil {
   696  		t.Fatal("got nil, want non-nil err")
   697  	}
   698  }
   699  
   700  func TestDefaultCredentials_Validate(t *testing.T) {
   701  	tests := []struct {
   702  		name string
   703  		opts *DetectOptions
   704  	}{
   705  		{
   706  			name: "missing options",
   707  		},
   708  		{
   709  			name: "scope and audience provided",
   710  			opts: &DetectOptions{
   711  				Scopes:   []string{"scope"},
   712  				Audience: "aud",
   713  			},
   714  		},
   715  		{
   716  			name: "file and json provided",
   717  			opts: &DetectOptions{
   718  				Scopes:          []string{"scope"},
   719  				CredentialsFile: "path",
   720  				CredentialsJSON: []byte(`{"some":"json"}`),
   721  			},
   722  		},
   723  	}
   724  	for _, tt := range tests {
   725  		t.Run(tt.name, func(t *testing.T) {
   726  			if _, err := DetectDefault(tt.opts); err == nil {
   727  				t.Error("got nil, want an error")
   728  			}
   729  		})
   730  	}
   731  }
   732  
   733  func TestDefaultCredentials_UniverseDomain(t *testing.T) {
   734  	ctx := context.Background()
   735  	tests := []struct {
   736  		name string
   737  		opts *DetectOptions
   738  		want string
   739  	}{
   740  		{
   741  			name: "service account json",
   742  			opts: &DetectOptions{
   743  				CredentialsFile: "../internal/testdata/sa.json",
   744  			},
   745  			want: "googleapis.com",
   746  		},
   747  		{
   748  			name: "service account json with file universe domain",
   749  			opts: &DetectOptions{
   750  				CredentialsFile:  "../internal/testdata/sa_universe_domain.json",
   751  				UseSelfSignedJWT: true,
   752  			},
   753  			want: "example.com",
   754  		},
   755  		{
   756  			name: "service account json with options universe domain",
   757  			opts: &DetectOptions{
   758  				CredentialsFile:  "../internal/testdata/sa.json",
   759  				UseSelfSignedJWT: true,
   760  				UniverseDomain:   "foo.com",
   761  			},
   762  			want: "foo.com",
   763  		},
   764  		{
   765  			name: "service account json with file and options universe domain",
   766  			opts: &DetectOptions{
   767  				CredentialsFile:  "../internal/testdata/sa_universe_domain.json",
   768  				UseSelfSignedJWT: true,
   769  				UniverseDomain:   "foo.com",
   770  			},
   771  			want: "foo.com",
   772  		},
   773  		{
   774  			name: "user json",
   775  			opts: &DetectOptions{
   776  				CredentialsFile: "../internal/testdata/user.json",
   777  				TokenURL:        "example.com",
   778  			},
   779  			want: "googleapis.com",
   780  		},
   781  		{
   782  			name: "user json with options universe domain",
   783  			opts: &DetectOptions{
   784  				CredentialsFile: "../internal/testdata/user.json",
   785  				UniverseDomain:  "foo.com",
   786  			},
   787  			want: "googleapis.com",
   788  		},
   789  		{
   790  			name: "user json with file universe domain",
   791  			opts: &DetectOptions{
   792  				CredentialsFile: "../internal/testdata/user_universe_domain.json",
   793  				TokenURL:        "example.com",
   794  			},
   795  			want: "example.com",
   796  		},
   797  		{
   798  			name: "user json with file and options universe domain",
   799  			opts: &DetectOptions{
   800  				CredentialsFile: "../internal/testdata/user_universe_domain.json",
   801  				UniverseDomain:  "foo.com",
   802  			},
   803  			want: "example.com",
   804  		},
   805  		{
   806  			name: "external account json",
   807  			opts: &DetectOptions{
   808  				CredentialsFile: "../internal/testdata/exaccount_url.json",
   809  			},
   810  			want: "googleapis.com",
   811  		},
   812  		{
   813  			name: "external account json with file universe domain",
   814  			opts: &DetectOptions{
   815  				CredentialsFile: "../internal/testdata/exaccount_url_universe_domain.json",
   816  			},
   817  			want: "example.com",
   818  		},
   819  		{
   820  			name: "external account json with options universe domain",
   821  			opts: &DetectOptions{
   822  				CredentialsFile: "../internal/testdata/exaccount_url.json",
   823  				UniverseDomain:  "foo.com",
   824  			},
   825  			want: "foo.com",
   826  		},
   827  		{
   828  			name: "external account json with file and options universe domain",
   829  			opts: &DetectOptions{
   830  				CredentialsFile: "../internal/testdata/exaccount_url_universe_domain.json",
   831  				UniverseDomain:  "foo.com",
   832  			},
   833  			want: "foo.com",
   834  		},
   835  		{
   836  			name: "external account user json",
   837  			opts: &DetectOptions{
   838  				CredentialsFile: "../internal/testdata/exaccount_user.json",
   839  			},
   840  			want: "googleapis.com",
   841  		},
   842  		{
   843  			name: "external account user json with file universe domain",
   844  			opts: &DetectOptions{
   845  				CredentialsFile: "../internal/testdata/exaccount_user_universe_domain.json",
   846  			},
   847  			want: "example.com",
   848  		},
   849  		{
   850  			name: "external account user json with options universe domain",
   851  			opts: &DetectOptions{
   852  				CredentialsFile: "../internal/testdata/exaccount_user.json",
   853  				UniverseDomain:  "foo.com",
   854  			},
   855  			want: "googleapis.com",
   856  		},
   857  		{
   858  			name: "external account user json with file and options universe domain",
   859  			opts: &DetectOptions{
   860  				CredentialsFile: "../internal/testdata/exaccount_user_universe_domain.json",
   861  				UniverseDomain:  "foo.com",
   862  			},
   863  			want: "example.com",
   864  		},
   865  		{
   866  			name: "impersonated service account json",
   867  			opts: &DetectOptions{
   868  				CredentialsFile:  "../internal/testdata/imp.json",
   869  				UseSelfSignedJWT: true,
   870  			},
   871  			want: "googleapis.com",
   872  		},
   873  		{
   874  			name: "impersonated service account json with file universe domain",
   875  			opts: &DetectOptions{
   876  				CredentialsFile: "../internal/testdata/imp_universe_domain.json",
   877  			},
   878  			want: "example.com",
   879  		},
   880  		{
   881  			name: "impersonated service account json with options universe domain",
   882  			opts: &DetectOptions{
   883  				CredentialsFile:  "../internal/testdata/imp.json",
   884  				UseSelfSignedJWT: true,
   885  				UniverseDomain:   "foo.com",
   886  			},
   887  			want: "foo.com",
   888  		},
   889  		{
   890  			name: "impersonated service account json with file and options universe domain",
   891  			opts: &DetectOptions{
   892  				CredentialsFile: "../internal/testdata/imp_universe_domain.json",
   893  				UniverseDomain:  "foo.com",
   894  			},
   895  			want: "foo.com",
   896  		},
   897  	}
   898  	for _, tt := range tests {
   899  		t.Run(tt.name, func(t *testing.T) {
   900  			creds, err := DetectDefault(tt.opts)
   901  			if err != nil {
   902  				t.Fatalf("%v", err)
   903  			}
   904  			ud, err := creds.UniverseDomain(ctx)
   905  			if err != nil {
   906  				t.Fatal(err)
   907  			}
   908  			if ud != tt.want {
   909  				t.Fatalf("got %q, want %q", ud, tt.want)
   910  			}
   911  		})
   912  	}
   913  }
   914  

View as plain text