sdk.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // Copyright 2015 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. package google
  5. import (
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "net/http"
  10. "os"
  11. "os/user"
  12. "path/filepath"
  13. "runtime"
  14. "strings"
  15. "time"
  16. "golang.org/x/net/context"
  17. "golang.org/x/oauth2"
  18. "golang.org/x/oauth2/internal"
  19. )
  20. type sdkCredentials struct {
  21. Data []struct {
  22. Credential struct {
  23. ClientID string `json:"client_id"`
  24. ClientSecret string `json:"client_secret"`
  25. AccessToken string `json:"access_token"`
  26. RefreshToken string `json:"refresh_token"`
  27. TokenExpiry *time.Time `json:"token_expiry"`
  28. } `json:"credential"`
  29. Key struct {
  30. Account string `json:"account"`
  31. Scope string `json:"scope"`
  32. } `json:"key"`
  33. }
  34. }
  35. // An SDKConfig provides access to tokens from an account already
  36. // authorized via the Google Cloud SDK.
  37. type SDKConfig struct {
  38. conf oauth2.Config
  39. initialToken *oauth2.Token
  40. }
  41. // NewSDKConfig creates an SDKConfig for the given Google Cloud SDK
  42. // account. If account is empty, the account currently active in
  43. // Google Cloud SDK properties is used.
  44. // Google Cloud SDK credentials must be created by running `gcloud auth`
  45. // before using this function.
  46. // The Google Cloud SDK is available at https://cloud.google.com/sdk/.
  47. func NewSDKConfig(account string) (*SDKConfig, error) {
  48. configPath, err := sdkConfigPath()
  49. if err != nil {
  50. return nil, fmt.Errorf("oauth2/google: error getting SDK config path: %v", err)
  51. }
  52. credentialsPath := filepath.Join(configPath, "credentials")
  53. f, err := os.Open(credentialsPath)
  54. if err != nil {
  55. return nil, fmt.Errorf("oauth2/google: failed to load SDK credentials: %v", err)
  56. }
  57. defer f.Close()
  58. var c sdkCredentials
  59. if err := json.NewDecoder(f).Decode(&c); err != nil {
  60. return nil, fmt.Errorf("oauth2/google: failed to decode SDK credentials from %q: %v", credentialsPath, err)
  61. }
  62. if len(c.Data) == 0 {
  63. return nil, fmt.Errorf("oauth2/google: no credentials found in %q, run `gcloud auth login` to create one", credentialsPath)
  64. }
  65. if account == "" {
  66. propertiesPath := filepath.Join(configPath, "properties")
  67. f, err := os.Open(propertiesPath)
  68. if err != nil {
  69. return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err)
  70. }
  71. defer f.Close()
  72. ini, err := internal.ParseINI(f)
  73. if err != nil {
  74. return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err)
  75. }
  76. core, ok := ini["core"]
  77. if !ok {
  78. return nil, fmt.Errorf("oauth2/google: failed to find [core] section in %v", ini)
  79. }
  80. active, ok := core["account"]
  81. if !ok {
  82. return nil, fmt.Errorf("oauth2/google: failed to find %q attribute in %v", "account", core)
  83. }
  84. account = active
  85. }
  86. for _, d := range c.Data {
  87. if account == "" || d.Key.Account == account {
  88. if d.Credential.AccessToken == "" && d.Credential.RefreshToken == "" {
  89. return nil, fmt.Errorf("oauth2/google: no token available for account %q", account)
  90. }
  91. var expiry time.Time
  92. if d.Credential.TokenExpiry != nil {
  93. expiry = *d.Credential.TokenExpiry
  94. }
  95. return &SDKConfig{
  96. conf: oauth2.Config{
  97. ClientID: d.Credential.ClientID,
  98. ClientSecret: d.Credential.ClientSecret,
  99. Scopes: strings.Split(d.Key.Scope, " "),
  100. Endpoint: Endpoint,
  101. RedirectURL: "oob",
  102. },
  103. initialToken: &oauth2.Token{
  104. AccessToken: d.Credential.AccessToken,
  105. RefreshToken: d.Credential.RefreshToken,
  106. Expiry: expiry,
  107. },
  108. }, nil
  109. }
  110. }
  111. return nil, fmt.Errorf("oauth2/google: no such credentials for account %q", account)
  112. }
  113. // Client returns an HTTP client using Google Cloud SDK credentials to
  114. // authorize requests. The token will auto-refresh as necessary. The
  115. // underlying http.RoundTripper will be obtained using the provided
  116. // context. The returned client and its Transport should not be
  117. // modified.
  118. func (c *SDKConfig) Client(ctx context.Context) *http.Client {
  119. return &http.Client{
  120. Transport: &oauth2.Transport{
  121. Source: c.TokenSource(ctx),
  122. },
  123. }
  124. }
  125. // TokenSource returns an oauth2.TokenSource that retrieve tokens from
  126. // Google Cloud SDK credentials using the provided context.
  127. // It will returns the current access token stored in the credentials,
  128. // and refresh it when it expires, but it won't update the credentials
  129. // with the new access token.
  130. func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource {
  131. return c.conf.TokenSource(ctx, c.initialToken)
  132. }
  133. // Scopes are the OAuth 2.0 scopes the current account is authorized for.
  134. func (c *SDKConfig) Scopes() []string {
  135. return c.conf.Scopes
  136. }
  137. // sdkConfigPath tries to guess where the gcloud config is located.
  138. // It can be overridden during tests.
  139. var sdkConfigPath = func() (string, error) {
  140. if runtime.GOOS == "windows" {
  141. return filepath.Join(os.Getenv("APPDATA"), "gcloud"), nil
  142. }
  143. homeDir := guessUnixHomeDir()
  144. if homeDir == "" {
  145. return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty")
  146. }
  147. return filepath.Join(homeDir, ".config", "gcloud"), nil
  148. }
  149. func guessUnixHomeDir() string {
  150. usr, err := user.Current()
  151. if err == nil {
  152. return usr.HomeDir
  153. }
  154. return os.Getenv("HOME")
  155. }