google: add DefaultCredentials function

This new function allows reading the project ID from a service account
JSON file without an additional disk read.

Change-Id: I1f03ca3ca39a2ae3bd6524367c17761b0f08de45
Reviewed-on: https://go-review.googlesource.com/32876
Reviewed-by: Jaana Burcu Dogan <jbd@google.com>
diff --git a/google/appengine.go b/google/appengine.go
index dc993ef..4243f4c 100644
--- a/google/appengine.go
+++ b/google/appengine.go
@@ -20,6 +20,9 @@
 // Set at init time by appengine_hook.go. If nil, we're not on App Engine.
 var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error)
 
+// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
+var appengineAppIDFunc func(c context.Context) string
+
 // AppEngineTokenSource returns a token source that fetches tokens
 // issued to the current App Engine application's service account.
 // If you are implementing a 3-legged OAuth 2.0 flow on App Engine
diff --git a/google/appengine_hook.go b/google/appengine_hook.go
index 4f42c8b..6f66411 100644
--- a/google/appengine_hook.go
+++ b/google/appengine_hook.go
@@ -10,4 +10,5 @@
 
 func init() {
 	appengineTokenFunc = appengine.AccessToken
+	appengineAppIDFunc = appengine.AppID
 }
diff --git a/google/appenginevm_hook.go b/google/appenginevm_hook.go
index 633611c..1074780 100644
--- a/google/appenginevm_hook.go
+++ b/google/appenginevm_hook.go
@@ -11,4 +11,5 @@
 func init() {
 	appengineVM = true
 	appengineTokenFunc = appengine.AccessToken
+	appengineAppIDFunc = appengine.AppID
 }
diff --git a/google/default.go b/google/default.go
index c572d1a..b45e796 100644
--- a/google/default.go
+++ b/google/default.go
@@ -18,16 +18,16 @@
 	"golang.org/x/oauth2"
 )
 
-// DefaultClient returns an HTTP Client that uses the
-// DefaultTokenSource to obtain authentication credentials.
-//
-// This client should be used when developing services
-// that run on Google App Engine or Google Compute Engine
-// and use "Application Default Credentials."
-//
+// DefaultCredentials holds "Application Default Credentials".
 // For more details, see:
 // https://developers.google.com/accounts/docs/application-default-credentials
-//
+type DefaultCredentials struct {
+	ProjectID   string // may be empty
+	TokenSource oauth2.TokenSource
+}
+
+// DefaultClient returns an HTTP Client that uses the
+// DefaultTokenSource to obtain authentication credentials.
 func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
 	ts, err := DefaultTokenSource(ctx, scope...)
 	if err != nil {
@@ -36,8 +36,18 @@
 	return oauth2.NewClient(ctx, ts), nil
 }
 
-// DefaultTokenSource is a token source that uses
+// DefaultTokenSource returns the token source for
 // "Application Default Credentials".
+// It is a shortcut for FindDefaultCredentials(ctx, scope).TokenSource.
+func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
+	creds, err := FindDefaultCredentials(ctx, scope...)
+	if err != nil {
+		return nil, err
+	}
+	return creds.TokenSource, nil
+}
+
+// FindDefaultCredentials searches for "Application Default Credentials".
 //
 // It looks for credentials in the following places,
 // preferring the first location found:
@@ -51,45 +61,40 @@
 //   4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
 //      credentials from the metadata server.
 //      (In this final case any provided scopes are ignored.)
-//
-// For more details, see:
-// https://developers.google.com/accounts/docs/application-default-credentials
-//
-func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
+func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCredentials, error) {
 	// First, try the environment variable.
 	const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
 	if filename := os.Getenv(envVar); filename != "" {
-		ts, err := tokenSourceFromFile(ctx, filename, scope)
+		creds, err := readCredentialsFile(ctx, filename, scope)
 		if err != nil {
 			return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
 		}
-		return ts, nil
+		return creds, nil
 	}
 
 	// Second, try a well-known file.
 	filename := wellKnownFile()
-	_, err := os.Stat(filename)
-	if err == nil {
-		ts, err2 := tokenSourceFromFile(ctx, filename, scope)
-		if err2 == nil {
-			return ts, nil
-		}
-		err = err2
-	} else if os.IsNotExist(err) {
-		err = nil // ignore this error
-	}
-	if err != nil {
+	if creds, err := readCredentialsFile(ctx, filename, scope); err == nil {
+		return creds, nil
+	} else if !os.IsNotExist(err) {
 		return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err)
 	}
 
 	// Third, if we're on Google App Engine use those credentials.
 	if appengineTokenFunc != nil && !appengineVM {
-		return AppEngineTokenSource(ctx, scope...), nil
+		return &DefaultCredentials{
+			ProjectID:   appengineAppIDFunc(ctx),
+			TokenSource: AppEngineTokenSource(ctx, scope...),
+		}, nil
 	}
 
 	// Fourth, if we're on Google Compute Engine use the metadata server.
 	if metadata.OnGCE() {
-		return ComputeTokenSource(""), nil
+		id, _ := metadata.ProjectID()
+		return &DefaultCredentials{
+			ProjectID:   id,
+			TokenSource: ComputeTokenSource(""),
+		}, nil
 	}
 
 	// None are found; return helpful error.
@@ -105,7 +110,7 @@
 	return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f)
 }
 
-func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) {
+func readCredentialsFile(ctx context.Context, filename string, scopes []string) (*DefaultCredentials, error) {
 	b, err := ioutil.ReadFile(filename)
 	if err != nil {
 		return nil, err
@@ -114,5 +119,12 @@
 	if err := json.Unmarshal(b, &f); err != nil {
 		return nil, err
 	}
-	return f.tokenSource(ctx, scopes)
+	ts, err := f.tokenSource(ctx, append([]string(nil), scopes...))
+	if err != nil {
+		return nil, err
+	}
+	return &DefaultCredentials{
+		ProjectID:   f.ProjectID,
+		TokenSource: ts,
+	}, nil
 }
diff --git a/google/google.go b/google/google.go
index 0704732..66a8b0e 100644
--- a/google/google.go
+++ b/google/google.go
@@ -112,6 +112,7 @@
 	PrivateKeyID string `json:"private_key_id"`
 	PrivateKey   string `json:"private_key"`
 	TokenURL     string `json:"token_uri"`
+	ProjectID    string `json:"project_id"`
 
 	// User Credential fields
 	// (These typically come from gcloud auth.)