diff --git a/auth/token_generator.go b/auth/token_generator.go index fed6facf..db9e4106 100644 --- a/auth/token_generator.go +++ b/auth/token_generator.go @@ -188,11 +188,11 @@ func newIAMSigner(ctx context.Context, config *internal.AuthConfig) (*iamSigner, }, nil } -func (s iamSigner) Algorithm() string { +func (s *iamSigner) Algorithm() string { return algorithmRS256 } -func (s iamSigner) Sign(ctx context.Context, b []byte) ([]byte, error) { +func (s *iamSigner) Sign(ctx context.Context, b []byte) ([]byte, error) { account, err := s.Email(ctx) if err != nil { return nil, err @@ -217,13 +217,13 @@ func (s iamSigner) Sign(ctx context.Context, b []byte) ([]byte, error) { return base64.StdEncoding.DecodeString(signResponse.Signature) } -func (s iamSigner) Email(ctx context.Context) (string, error) { +func (s *iamSigner) Email(ctx context.Context) (string, error) { + s.mutex.Lock() + defer s.mutex.Unlock() if s.serviceAcct != "" { return s.serviceAcct, nil } - s.mutex.Lock() - defer s.mutex.Unlock() result, err := s.callMetadataService(ctx) if err != nil { msg := "failed to determine service account: %v; initialize the SDK with service " + @@ -237,7 +237,7 @@ func (s iamSigner) Email(ctx context.Context) (string, error) { return result, nil } -func (s iamSigner) callMetadataService(ctx context.Context) (string, error) { +func (s *iamSigner) callMetadataService(ctx context.Context) (string, error) { // Use the built-in default client without request authorization or retries for this call. noAuthClient := &internal.HTTPClient{ Client: http.DefaultClient, diff --git a/auth/token_generator_test.go b/auth/token_generator_test.go index 6ed5144e..bab41bad 100644 --- a/auth/token_generator_test.go +++ b/auth/token_generator_test.go @@ -24,6 +24,8 @@ import ( "net/http" "net/http/httptest" "strings" + "sync" + "sync/atomic" "testing" "firebase.google.com/go/v4/errorutils" @@ -359,3 +361,41 @@ func iamServer(t *testing.T, serviceAcct, signature string) *httptest.Server { }) return httptest.NewServer(handler) } + +func TestIAMSignerEmailConcurrent(t *testing.T) { + var hits int64 + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + atomic.AddInt64(&hits, 1) + w.Header().Set("Content-Type", "application/text") + w.Write([]byte("discovered-service-account")) + }) + metadata := httptest.NewServer(handler) + defer metadata.Close() + + conf := &internal.AuthConfig{ + Opts: optsWithTokenSource, + Version: testVersion, + } + signer, err := newIAMSigner(context.Background(), conf) + if err != nil { + t.Fatal(err) + } + signer.metadataHost = metadata.URL + + var wg sync.WaitGroup + for i := 0; i < 50; i++ { + wg.Add(1) + go func() { + defer wg.Done() + if _, err := signer.Email(context.Background()); err != nil { + t.Error(err) + } + }() + } + wg.Wait() + + if got := atomic.LoadInt64(&hits); got != 1 { + t.Errorf("concurrent Email() made %d metadata requests; want = 1", got) + } +}