// Copyright 2015 The oauth2 Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package google import ( "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "os" "path/filepath" "runtime" "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/jwt" "google.golang.org/cloud/compute/metadata" ) // 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." // // For more details, see: // https://developers.google.com/accounts/docs/application-default-credentials // func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) { ts, err := DefaultTokenSource(ctx, scope...) if err != nil { return nil, err } return oauth2.NewClient(ctx, ts), nil } // DefaultTokenSource is a token source that uses // "Application Default Credentials". // // It looks for credentials in the following places, // preferring the first location found: // // 1. A JSON file whose path is specified by the // GOOGLE_APPLICATION_CREDENTIALS environment variable. // 2. A JSON file in a location known to the gcloud command-line tool. // On Windows, this is %APPDATA%/gcloud/application_default_credentials.json. // On other systems, $HOME/.config/gcloud/application_default_credentials.json. // 3. On Google App Engine it uses the appengine.AccessToken function. // 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) { // First, try the environment variable. const envVar = "GOOGLE_APPLICATION_CREDENTIALS" if filename := os.Getenv(envVar); filename != "" { ts, err := tokenSourceFromFile(ctx, filename, scope) if err != nil { return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) } return ts, 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 { 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 } // Fourth, if we're on Google Compute Engine use the metadata server. if metadata.OnGCE() { return ComputeTokenSource(""), nil } // None are found; return helpful error. const url = "https://developers.google.com/accounts/docs/application-default-credentials" return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url) } func wellKnownFile() string { const f = "application_default_credentials.json" if runtime.GOOS == "windows" { return filepath.Join(os.Getenv("APPDATA"), "gcloud", f) } return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f) } func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) { b, err := ioutil.ReadFile(filename) if err != nil { return nil, err } var d struct { // Common fields Type string ClientID string `json:"client_id"` // User Credential fields ClientSecret string `json:"client_secret"` RefreshToken string `json:"refresh_token"` // Service Account fields ClientEmail string `json:"client_email"` PrivateKeyID string `json:"private_key_id"` PrivateKey string `json:"private_key"` } if err := json.Unmarshal(b, &d); err != nil { return nil, err } switch d.Type { case "authorized_user": cfg := &oauth2.Config{ ClientID: d.ClientID, ClientSecret: d.ClientSecret, Scopes: append([]string{}, scopes...), // copy Endpoint: Endpoint, } tok := &oauth2.Token{RefreshToken: d.RefreshToken} return cfg.TokenSource(ctx, tok), nil case "service_account": cfg := &jwt.Config{ Email: d.ClientEmail, PrivateKey: []byte(d.PrivateKey), Scopes: append([]string{}, scopes...), // copy TokenURL: JWTTokenURL, } return cfg.TokenSource(ctx), nil case "": return nil, errors.New("missing 'type' field in credentials") default: return nil, fmt.Errorf("unknown credential type: %q", d.Type) } }