You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

193 lines
5.9 KiB

  1. // Copyright 2014 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. "strings"
  10. "time"
  11. "cloud.google.com/go/compute/metadata"
  12. "golang.org/x/net/context"
  13. "golang.org/x/oauth2"
  14. "golang.org/x/oauth2/jwt"
  15. )
  16. // Endpoint is Google's OAuth 2.0 endpoint.
  17. var Endpoint = oauth2.Endpoint{
  18. AuthURL: "https://accounts.google.com/o/oauth2/auth",
  19. TokenURL: "https://accounts.google.com/o/oauth2/token",
  20. }
  21. // JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
  22. const JWTTokenURL = "https://accounts.google.com/o/oauth2/token"
  23. // ConfigFromJSON uses a Google Developers Console client_credentials.json
  24. // file to construct a config.
  25. // client_credentials.json can be downloaded from
  26. // https://console.developers.google.com, under "Credentials". Download the Web
  27. // application credentials in the JSON format and provide the contents of the
  28. // file as jsonKey.
  29. func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) {
  30. type cred struct {
  31. ClientID string `json:"client_id"`
  32. ClientSecret string `json:"client_secret"`
  33. RedirectURIs []string `json:"redirect_uris"`
  34. AuthURI string `json:"auth_uri"`
  35. TokenURI string `json:"token_uri"`
  36. }
  37. var j struct {
  38. Web *cred `json:"web"`
  39. Installed *cred `json:"installed"`
  40. }
  41. if err := json.Unmarshal(jsonKey, &j); err != nil {
  42. return nil, err
  43. }
  44. var c *cred
  45. switch {
  46. case j.Web != nil:
  47. c = j.Web
  48. case j.Installed != nil:
  49. c = j.Installed
  50. default:
  51. return nil, fmt.Errorf("oauth2/google: no credentials found")
  52. }
  53. if len(c.RedirectURIs) < 1 {
  54. return nil, errors.New("oauth2/google: missing redirect URL in the client_credentials.json")
  55. }
  56. return &oauth2.Config{
  57. ClientID: c.ClientID,
  58. ClientSecret: c.ClientSecret,
  59. RedirectURL: c.RedirectURIs[0],
  60. Scopes: scope,
  61. Endpoint: oauth2.Endpoint{
  62. AuthURL: c.AuthURI,
  63. TokenURL: c.TokenURI,
  64. },
  65. }, nil
  66. }
  67. // JWTConfigFromJSON uses a Google Developers service account JSON key file to read
  68. // the credentials that authorize and authenticate the requests.
  69. // Create a service account on "Credentials" for your project at
  70. // https://console.developers.google.com to download a JSON key file.
  71. func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) {
  72. var f credentialsFile
  73. if err := json.Unmarshal(jsonKey, &f); err != nil {
  74. return nil, err
  75. }
  76. if f.Type != serviceAccountKey {
  77. return nil, fmt.Errorf("google: read JWT from JSON credentials: 'type' field is %q (expected %q)", f.Type, serviceAccountKey)
  78. }
  79. scope = append([]string(nil), scope...) // copy
  80. return f.jwtConfig(scope), nil
  81. }
  82. // JSON key file types.
  83. const (
  84. serviceAccountKey = "service_account"
  85. userCredentialsKey = "authorized_user"
  86. )
  87. // credentialsFile is the unmarshalled representation of a credentials file.
  88. type credentialsFile struct {
  89. Type string `json:"type"` // serviceAccountKey or userCredentialsKey
  90. // Service Account fields
  91. ClientEmail string `json:"client_email"`
  92. PrivateKeyID string `json:"private_key_id"`
  93. PrivateKey string `json:"private_key"`
  94. TokenURL string `json:"token_uri"`
  95. ProjectID string `json:"project_id"`
  96. // User Credential fields
  97. // (These typically come from gcloud auth.)
  98. ClientSecret string `json:"client_secret"`
  99. ClientID string `json:"client_id"`
  100. RefreshToken string `json:"refresh_token"`
  101. }
  102. func (f *credentialsFile) jwtConfig(scopes []string) *jwt.Config {
  103. cfg := &jwt.Config{
  104. Email: f.ClientEmail,
  105. PrivateKey: []byte(f.PrivateKey),
  106. PrivateKeyID: f.PrivateKeyID,
  107. Scopes: scopes,
  108. TokenURL: f.TokenURL,
  109. }
  110. if cfg.TokenURL == "" {
  111. cfg.TokenURL = JWTTokenURL
  112. }
  113. return cfg
  114. }
  115. func (f *credentialsFile) tokenSource(ctx context.Context, scopes []string) (oauth2.TokenSource, error) {
  116. switch f.Type {
  117. case serviceAccountKey:
  118. cfg := f.jwtConfig(scopes)
  119. return cfg.TokenSource(ctx), nil
  120. case userCredentialsKey:
  121. cfg := &oauth2.Config{
  122. ClientID: f.ClientID,
  123. ClientSecret: f.ClientSecret,
  124. Scopes: scopes,
  125. Endpoint: Endpoint,
  126. }
  127. tok := &oauth2.Token{RefreshToken: f.RefreshToken}
  128. return cfg.TokenSource(ctx, tok), nil
  129. case "":
  130. return nil, errors.New("missing 'type' field in credentials")
  131. default:
  132. return nil, fmt.Errorf("unknown credential type: %q", f.Type)
  133. }
  134. }
  135. // ComputeTokenSource returns a token source that fetches access tokens
  136. // from Google Compute Engine (GCE)'s metadata server. It's only valid to use
  137. // this token source if your program is running on a GCE instance.
  138. // If no account is specified, "default" is used.
  139. // Further information about retrieving access tokens from the GCE metadata
  140. // server can be found at https://cloud.google.com/compute/docs/authentication.
  141. func ComputeTokenSource(account string) oauth2.TokenSource {
  142. return oauth2.ReuseTokenSource(nil, computeSource{account: account})
  143. }
  144. type computeSource struct {
  145. account string
  146. }
  147. func (cs computeSource) Token() (*oauth2.Token, error) {
  148. if !metadata.OnGCE() {
  149. return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE")
  150. }
  151. acct := cs.account
  152. if acct == "" {
  153. acct = "default"
  154. }
  155. tokenJSON, err := metadata.Get("instance/service-accounts/" + acct + "/token")
  156. if err != nil {
  157. return nil, err
  158. }
  159. var res struct {
  160. AccessToken string `json:"access_token"`
  161. ExpiresInSec int `json:"expires_in"`
  162. TokenType string `json:"token_type"`
  163. }
  164. err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res)
  165. if err != nil {
  166. return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err)
  167. }
  168. if res.ExpiresInSec == 0 || res.AccessToken == "" {
  169. return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata")
  170. }
  171. return &oauth2.Token{
  172. AccessToken: res.AccessToken,
  173. TokenType: res.TokenType,
  174. Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
  175. }, nil
  176. }