creds.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // Copyright 2017 Google LLC.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package internal
  5. import (
  6. "context"
  7. "encoding/json"
  8. "fmt"
  9. "io/ioutil"
  10. "golang.org/x/oauth2"
  11. "google.golang.org/api/internal/impersonate"
  12. "golang.org/x/oauth2/google"
  13. )
  14. // Creds returns credential information obtained from DialSettings, or if none, then
  15. // it returns default credential information.
  16. func Creds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
  17. creds, err := baseCreds(ctx, ds)
  18. if err != nil {
  19. return nil, err
  20. }
  21. if ds.ImpersonationConfig != nil {
  22. return impersonateCredentials(ctx, creds, ds)
  23. }
  24. return creds, nil
  25. }
  26. func baseCreds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
  27. if ds.Credentials != nil {
  28. return ds.Credentials, nil
  29. }
  30. if ds.CredentialsJSON != nil {
  31. return credentialsFromJSON(ctx, ds.CredentialsJSON, ds)
  32. }
  33. if ds.CredentialsFile != "" {
  34. data, err := ioutil.ReadFile(ds.CredentialsFile)
  35. if err != nil {
  36. return nil, fmt.Errorf("cannot read credentials file: %v", err)
  37. }
  38. return credentialsFromJSON(ctx, data, ds)
  39. }
  40. if ds.TokenSource != nil {
  41. return &google.Credentials{TokenSource: ds.TokenSource}, nil
  42. }
  43. cred, err := google.FindDefaultCredentials(ctx, ds.GetScopes()...)
  44. if err != nil {
  45. return nil, err
  46. }
  47. if len(cred.JSON) > 0 {
  48. return credentialsFromJSON(ctx, cred.JSON, ds)
  49. }
  50. // For GAE and GCE, the JSON is empty so return the default credentials directly.
  51. return cred, nil
  52. }
  53. // JSON key file type.
  54. const (
  55. serviceAccountKey = "service_account"
  56. )
  57. // credentialsFromJSON returns a google.Credentials based on the input.
  58. //
  59. // - A self-signed JWT auth flow will be executed if: the data file is a service
  60. // account, no user are scopes provided, an audience is provided, a user
  61. // specified endpoint is not provided, and credentials will not be
  62. // impersonated.
  63. //
  64. // - Otherwise, executes a stanard OAuth 2.0 flow.
  65. func credentialsFromJSON(ctx context.Context, data []byte, ds *DialSettings) (*google.Credentials, error) {
  66. cred, err := google.CredentialsFromJSON(ctx, data, ds.GetScopes()...)
  67. if err != nil {
  68. return nil, err
  69. }
  70. // Standard OAuth 2.0 Flow
  71. if len(data) == 0 ||
  72. len(ds.Scopes) > 0 ||
  73. (ds.DefaultAudience == "" && len(ds.Audiences) == 0) ||
  74. ds.ImpersonationConfig != nil ||
  75. ds.Endpoint != "" {
  76. return cred, nil
  77. }
  78. // Check if JSON is a service account and if so create a self-signed JWT.
  79. var f struct {
  80. Type string `json:"type"`
  81. // The rest JSON fields are omitted because they are not used.
  82. }
  83. if err := json.Unmarshal(cred.JSON, &f); err != nil {
  84. return nil, err
  85. }
  86. if f.Type == serviceAccountKey {
  87. ts, err := selfSignedJWTTokenSource(data, ds.DefaultAudience, ds.Audiences)
  88. if err != nil {
  89. return nil, err
  90. }
  91. cred.TokenSource = ts
  92. }
  93. return cred, err
  94. }
  95. func selfSignedJWTTokenSource(data []byte, defaultAudience string, audiences []string) (oauth2.TokenSource, error) {
  96. audience := defaultAudience
  97. if len(audiences) > 0 {
  98. // TODO(shinfan): Update golang oauth to support multiple audiences.
  99. if len(audiences) > 1 {
  100. return nil, fmt.Errorf("multiple audiences support is not implemented")
  101. }
  102. audience = audiences[0]
  103. }
  104. return google.JWTAccessTokenSourceFromJSON(data, audience)
  105. }
  106. // QuotaProjectFromCreds returns the quota project from the JSON blob in the provided credentials.
  107. //
  108. // NOTE(cbro): consider promoting this to a field on google.Credentials.
  109. func QuotaProjectFromCreds(cred *google.Credentials) string {
  110. var v struct {
  111. QuotaProject string `json:"quota_project_id"`
  112. }
  113. if err := json.Unmarshal(cred.JSON, &v); err != nil {
  114. return ""
  115. }
  116. return v.QuotaProject
  117. }
  118. func impersonateCredentials(ctx context.Context, creds *google.Credentials, ds *DialSettings) (*google.Credentials, error) {
  119. if len(ds.ImpersonationConfig.Scopes) == 0 {
  120. ds.ImpersonationConfig.Scopes = ds.GetScopes()
  121. }
  122. ts, err := impersonate.TokenSource(ctx, creds.TokenSource, ds.ImpersonationConfig)
  123. if err != nil {
  124. return nil, err
  125. }
  126. return &google.Credentials{
  127. TokenSource: ts,
  128. ProjectID: creds.ProjectID,
  129. }, nil
  130. }