googleauth 0.8.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.kokoro/build.bat +9 -1
  3. data/.kokoro/continuous/linux.cfg +12 -2
  4. data/.kokoro/continuous/osx.cfg +5 -0
  5. data/.kokoro/continuous/post.cfg +30 -0
  6. data/.kokoro/continuous/windows.cfg +27 -1
  7. data/.kokoro/presubmit/linux.cfg +11 -1
  8. data/.kokoro/presubmit/osx.cfg +5 -0
  9. data/.kokoro/presubmit/windows.cfg +27 -1
  10. data/.kokoro/release.cfg +42 -1
  11. data/.kokoro/trampoline.bat +10 -0
  12. data/.repo-metadata.json +5 -0
  13. data/.rubocop.yml +10 -2
  14. data/CHANGELOG.md +34 -0
  15. data/Gemfile +8 -3
  16. data/README.md +7 -12
  17. data/Rakefile +48 -5
  18. data/googleauth.gemspec +6 -3
  19. data/integration/helper.rb +31 -0
  20. data/integration/id_tokens/key_source_test.rb +74 -0
  21. data/lib/googleauth.rb +1 -0
  22. data/lib/googleauth/application_default.rb +1 -1
  23. data/lib/googleauth/compute_engine.rb +19 -17
  24. data/lib/googleauth/credentials.rb +318 -63
  25. data/lib/googleauth/credentials_loader.rb +10 -8
  26. data/lib/googleauth/id_tokens.rb +233 -0
  27. data/lib/googleauth/id_tokens/errors.rb +71 -0
  28. data/lib/googleauth/id_tokens/key_sources.rb +394 -0
  29. data/lib/googleauth/id_tokens/verifier.rb +144 -0
  30. data/lib/googleauth/json_key_reader.rb +6 -2
  31. data/lib/googleauth/service_account.rb +16 -7
  32. data/lib/googleauth/signet.rb +8 -5
  33. data/lib/googleauth/user_authorizer.rb +6 -1
  34. data/lib/googleauth/user_refresh.rb +2 -2
  35. data/lib/googleauth/version.rb +1 -1
  36. data/lib/googleauth/web_user_authorizer.rb +13 -8
  37. data/rakelib/devsite_builder.rb +45 -0
  38. data/rakelib/link_checker.rb +64 -0
  39. data/rakelib/repo_metadata.rb +59 -0
  40. data/spec/googleauth/apply_auth_examples.rb +28 -5
  41. data/spec/googleauth/compute_engine_spec.rb +25 -13
  42. data/spec/googleauth/credentials_spec.rb +366 -161
  43. data/spec/googleauth/service_account_spec.rb +23 -16
  44. data/spec/googleauth/signet_spec.rb +46 -7
  45. data/spec/googleauth/user_authorizer_spec.rb +21 -1
  46. data/spec/googleauth/user_refresh_spec.rb +1 -1
  47. data/spec/googleauth/web_user_authorizer_spec.rb +6 -0
  48. data/test/helper.rb +33 -0
  49. data/test/id_tokens/key_sources_test.rb +240 -0
  50. data/test/id_tokens/verifier_test.rb +269 -0
  51. metadata +46 -12
  52. data/.kokoro/windows.sh +0 -4
@@ -9,7 +9,7 @@ Gem::Specification.new do |gem|
9
9
  gem.version = Google::Auth::VERSION
10
10
  gem.authors = ["Tim Emiola"]
11
11
  gem.email = "temiola@google.com"
12
- gem.homepage = "https://github.com/google/google-auth-library-ruby"
12
+ gem.homepage = "https://github.com/googleapis/google-auth-library-ruby"
13
13
  gem.summary = "Google Auth Library for Ruby"
14
14
  gem.license = "Apache-2.0"
15
15
  gem.description = <<-DESCRIPTION
@@ -25,11 +25,14 @@ Gem::Specification.new do |gem|
25
25
  end
26
26
  gem.require_paths = ["lib"]
27
27
  gem.platform = Gem::Platform::RUBY
28
+ gem.required_ruby_version = ">= 2.4.0"
28
29
 
29
- gem.add_dependency "faraday", "~> 0.12"
30
+ gem.add_dependency "faraday", ">= 0.17.3", "< 2.0"
30
31
  gem.add_dependency "jwt", ">= 1.4", "< 3.0"
31
32
  gem.add_dependency "memoist", "~> 0.16"
32
33
  gem.add_dependency "multi_json", "~> 1.11"
33
34
  gem.add_dependency "os", ">= 0.9", "< 2.0"
34
- gem.add_dependency "signet", "~> 0.7"
35
+ gem.add_dependency "signet", "~> 0.14"
36
+
37
+ gem.add_development_dependency "yard", "~> 0.9"
35
38
  end
@@ -0,0 +1,31 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are
5
+ # met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright
8
+ # notice, this list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above
10
+ # copyright notice, this list of conditions and the following disclaimer
11
+ # in the documentation and/or other materials provided with the
12
+ # distribution.
13
+ # * Neither the name of Google Inc. nor the names of its
14
+ # contributors may be used to endorse or promote products derived from
15
+ # this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ require "minitest/autorun"
30
+ require "minitest/focus"
31
+ require "googleauth"
@@ -0,0 +1,74 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are
5
+ # met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright
8
+ # notice, this list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above
10
+ # copyright notice, this list of conditions and the following disclaimer
11
+ # in the documentation and/or other materials provided with the
12
+ # distribution.
13
+ # * Neither the name of Google Inc. nor the names of its
14
+ # contributors may be used to endorse or promote products derived from
15
+ # this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ require "helper"
30
+
31
+ describe Google::Auth::IDTokens do
32
+ describe "key source" do
33
+ let(:legacy_oidc_key_source) {
34
+ Google::Auth::IDTokens::X509CertHttpKeySource.new "https://www.googleapis.com/oauth2/v1/certs"
35
+ }
36
+ let(:oidc_key_source) { Google::Auth::IDTokens.oidc_key_source }
37
+ let(:iap_key_source) { Google::Auth::IDTokens.iap_key_source }
38
+
39
+ it "Gets real keys from the OAuth2 V1 cert URL" do
40
+ keys = legacy_oidc_key_source.refresh_keys
41
+ refute_empty keys
42
+ keys.each do |key|
43
+ assert_kind_of OpenSSL::PKey::RSA, key.key
44
+ refute key.key.private?
45
+ assert_equal "RS256", key.algorithm
46
+ end
47
+ end
48
+
49
+ it "Gets real keys from the OAuth2 V3 cert URL" do
50
+ keys = oidc_key_source.refresh_keys
51
+ refute_empty keys
52
+ keys.each do |key|
53
+ assert_kind_of OpenSSL::PKey::RSA, key.key
54
+ refute key.key.private?
55
+ assert_equal "RS256", key.algorithm
56
+ end
57
+ end
58
+
59
+ it "Gets the same keys from the OAuth2 V1 and V3 cert URLs" do
60
+ keys_v1 = legacy_oidc_key_source.refresh_keys.map(&:key).map(&:export).sort
61
+ keys_v3 = oidc_key_source.refresh_keys.map(&:key).map(&:export).sort
62
+ assert_equal keys_v1, keys_v3
63
+ end
64
+
65
+ it "Gets real keys from the IAP public key URL" do
66
+ keys = iap_key_source.refresh_keys
67
+ refute_empty keys
68
+ keys.each do |key|
69
+ assert_kind_of OpenSSL::PKey::EC, key.key
70
+ assert_equal "ES256", key.algorithm
71
+ end
72
+ end
73
+ end
74
+ end
@@ -31,5 +31,6 @@ require "googleauth/application_default"
31
31
  require "googleauth/client_id"
32
32
  require "googleauth/credentials"
33
33
  require "googleauth/default_credentials"
34
+ require "googleauth/id_tokens"
34
35
  require "googleauth/user_authorizer"
35
36
  require "googleauth/web_user_authorizer"
@@ -47,7 +47,7 @@ module Google
47
47
  #
48
48
  # Use this to obtain the Application Default Credentials for accessing
49
49
  # Google APIs. Application Default Credentials are described in detail
50
- # at http://goo.gl/IUuyuX.
50
+ # at https://cloud.google.com/docs/authentication/production.
51
51
  #
52
52
  # If supplied, scope is used to create the credentials instance, when it can
53
53
  # be applied. E.g, on google compute engine and for user credentials the
@@ -51,30 +51,26 @@ module Google
51
51
  class GCECredentials < Signet::OAuth2::Client
52
52
  # The IP Address is used in the URIs to speed up failures on non-GCE
53
53
  # systems.
54
- COMPUTE_AUTH_TOKEN_URI = "http://169.254.169.254/computeMetadata/v1/"\
55
- "instance/service-accounts/default/token".freeze
54
+ COMPUTE_AUTH_TOKEN_URI =
55
+ "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
56
+ COMPUTE_ID_TOKEN_URI =
57
+ "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
56
58
  COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
57
59
 
58
60
  class << self
59
61
  extend Memoist
60
62
 
61
63
  # Detect if this appear to be a GCE instance, by checking if metadata
62
- # is available
64
+ # is available.
63
65
  def on_gce? options = {}
66
+ # TODO: This should use google-cloud-env instead.
64
67
  c = options[:connection] || Faraday.default_connection
65
- resp = c.get COMPUTE_CHECK_URI do |req|
66
- # Comment from: oauth2client/client.py
67
- #
68
- # Note: the explicit `timeout` below is a workaround. The underlying
69
- # issue is that resolving an unknown host on some networks will take
70
- # 20-30 seconds; making this timeout short fixes the issue, but
71
- # could lead to false negatives in the event that we are on GCE, but
72
- # the metadata resolution was particularly slow. The latter case is
73
- # "unlikely".
74
- req.options.timeout = 0.1
68
+ headers = { "Metadata-Flavor" => "Google" }
69
+ resp = c.get COMPUTE_CHECK_URI, nil, headers do |req|
70
+ req.options.timeout = 1.0
71
+ req.options.open_timeout = 0.1
75
72
  end
76
73
  return false unless resp.status == 200
77
- return false unless resp.headers.key? "Metadata-Flavor"
78
74
  resp.headers["Metadata-Flavor"] == "Google"
79
75
  rescue Faraday::TimeoutError, Faraday::ConnectionFailed
80
76
  false
@@ -88,12 +84,18 @@ module Google
88
84
  def fetch_access_token options = {}
89
85
  c = options[:connection] || Faraday.default_connection
90
86
  retry_with_error do
87
+ uri = target_audience ? COMPUTE_ID_TOKEN_URI : COMPUTE_AUTH_TOKEN_URI
88
+ query = target_audience ? { "audience" => target_audience, "format" => "full" } : nil
91
89
  headers = { "Metadata-Flavor" => "Google" }
92
- resp = c.get COMPUTE_AUTH_TOKEN_URI, nil, headers
90
+ resp = c.get uri, query, headers
93
91
  case resp.status
94
92
  when 200
95
- Signet::OAuth2.parse_credentials(resp.body,
96
- resp.headers["content-type"])
93
+ content_type = resp.headers["content-type"]
94
+ if content_type == "text/html"
95
+ { (target_audience ? "id_token" : "access_token") => resp.body }
96
+ else
97
+ Signet::OAuth2.parse_credentials resp.body, content_type
98
+ end
97
99
  when 404
98
100
  raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
99
101
  else
@@ -35,63 +35,294 @@ require "googleauth/credentials_loader"
35
35
 
36
36
  module Google
37
37
  module Auth
38
- # This class is intended to be inherited by API-specific classes
39
- # which overrides the SCOPE constant.
38
+ ##
39
+ # Credentials is responsible for representing the authentication when connecting to an API. This
40
+ # class is also intended to be inherited by API-specific classes.
40
41
  class Credentials
42
+ ##
43
+ # The default token credential URI to be used when none is provided during initialization.
41
44
  TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token".freeze
45
+
46
+ ##
47
+ # The default target audience ID to be used when none is provided during initialization.
42
48
  AUDIENCE = "https://oauth2.googleapis.com/token".freeze
43
- SCOPE = [].freeze
44
- PATH_ENV_VARS = [].freeze
45
- JSON_ENV_VARS = [].freeze
46
- DEFAULT_PATHS = [].freeze
47
49
 
50
+ @audience = @scope = @target_audience = @env_vars = @paths = nil
51
+
52
+ ##
53
+ # The default token credential URI to be used when none is provided during initialization.
54
+ # The URI is the authorization server's HTTP endpoint capable of issuing tokens and
55
+ # refreshing expired tokens.
56
+ #
57
+ # @return [String]
58
+ #
59
+ def self.token_credential_uri
60
+ return @token_credential_uri unless @token_credential_uri.nil?
61
+
62
+ const_get :TOKEN_CREDENTIAL_URI if const_defined? :TOKEN_CREDENTIAL_URI
63
+ end
64
+
65
+ ##
66
+ # Set the default token credential URI to be used when none is provided during initialization.
67
+ #
68
+ # @param [String] new_token_credential_uri
69
+ # @return [String]
70
+ #
71
+ def self.token_credential_uri= new_token_credential_uri
72
+ @token_credential_uri = new_token_credential_uri
73
+ end
74
+
75
+ ##
76
+ # The default target audience ID to be used when none is provided during initialization.
77
+ # Used only by the assertion grant type.
78
+ #
79
+ # @return [String]
80
+ #
81
+ def self.audience
82
+ return @audience unless @audience.nil?
83
+
84
+ const_get :AUDIENCE if const_defined? :AUDIENCE
85
+ end
86
+
87
+ ##
88
+ # Sets the default target audience ID to be used when none is provided during initialization.
89
+ #
90
+ # @param [String] new_audience
91
+ # @return [String]
92
+ #
93
+ def self.audience= new_audience
94
+ @audience = new_audience
95
+ end
96
+
97
+ ##
98
+ # The default scope to be used when none is provided during initialization.
99
+ # A scope is an access range defined by the authorization server.
100
+ # The scope can be a single value or a list of values.
101
+ #
102
+ # Either {#scope} or {#target_audience}, but not both, should be non-nil.
103
+ # If {#scope} is set, this credential will produce access tokens.
104
+ # If {#target_audience} is set, this credential will produce ID tokens.
105
+ #
106
+ # @return [String, Array<String>]
107
+ #
108
+ def self.scope
109
+ return @scope unless @scope.nil?
110
+
111
+ Array(const_get(:SCOPE)).flatten.uniq if const_defined? :SCOPE
112
+ end
113
+
114
+ ##
115
+ # Sets the default scope to be used when none is provided during initialization.
116
+ #
117
+ # Either {#scope} or {#target_audience}, but not both, should be non-nil.
118
+ # If {#scope} is set, this credential will produce access tokens.
119
+ # If {#target_audience} is set, this credential will produce ID tokens.
120
+ #
121
+ # @param [String, Array<String>] new_scope
122
+ # @return [String, Array<String>]
123
+ #
124
+ def self.scope= new_scope
125
+ new_scope = Array new_scope unless new_scope.nil?
126
+ @scope = new_scope
127
+ end
128
+
129
+ ##
130
+ # The default final target audience for ID tokens, to be used when none
131
+ # is provided during initialization.
132
+ #
133
+ # Either {#scope} or {#target_audience}, but not both, should be non-nil.
134
+ # If {#scope} is set, this credential will produce access tokens.
135
+ # If {#target_audience} is set, this credential will produce ID tokens.
136
+ #
137
+ # @return [String]
138
+ #
139
+ def self.target_audience
140
+ @target_audience
141
+ end
142
+
143
+ ##
144
+ # Sets the default final target audience for ID tokens, to be used when none
145
+ # is provided during initialization.
146
+ #
147
+ # Either {#scope} or {#target_audience}, but not both, should be non-nil.
148
+ # If {#scope} is set, this credential will produce access tokens.
149
+ # If {#target_audience} is set, this credential will produce ID tokens.
150
+ #
151
+ # @param [String] new_target_audience
152
+ #
153
+ def self.target_audience= new_target_audience
154
+ @target_audience = new_target_audience
155
+ end
156
+
157
+ ##
158
+ # The environment variables to search for credentials. Values can either be a file path to the
159
+ # credentials file, or the JSON contents of the credentials file.
160
+ #
161
+ # @return [Array<String>]
162
+ #
163
+ def self.env_vars
164
+ return @env_vars unless @env_vars.nil?
165
+
166
+ # Pull values when PATH_ENV_VARS or JSON_ENV_VARS constants exists.
167
+ tmp_env_vars = []
168
+ tmp_env_vars << const_get(:PATH_ENV_VARS) if const_defined? :PATH_ENV_VARS
169
+ tmp_env_vars << const_get(:JSON_ENV_VARS) if const_defined? :JSON_ENV_VARS
170
+ tmp_env_vars.flatten.uniq
171
+ end
172
+
173
+ ##
174
+ # Sets the environment variables to search for credentials.
175
+ #
176
+ # @param [Array<String>] new_env_vars
177
+ # @return [Array<String>]
178
+ #
179
+ def self.env_vars= new_env_vars
180
+ new_env_vars = Array new_env_vars unless new_env_vars.nil?
181
+ @env_vars = new_env_vars
182
+ end
183
+
184
+ ##
185
+ # The file paths to search for credentials files.
186
+ #
187
+ # @return [Array<String>]
188
+ #
189
+ def self.paths
190
+ return @paths unless @paths.nil?
191
+
192
+ tmp_paths = []
193
+ # Pull in values is the DEFAULT_PATHS constant exists.
194
+ tmp_paths << const_get(:DEFAULT_PATHS) if const_defined? :DEFAULT_PATHS
195
+ tmp_paths.flatten.uniq
196
+ end
197
+
198
+ ##
199
+ # Set the file paths to search for credentials files.
200
+ #
201
+ # @param [Array<String>] new_paths
202
+ # @return [Array<String>]
203
+ #
204
+ def self.paths= new_paths
205
+ new_paths = Array new_paths unless new_paths.nil?
206
+ @paths = new_paths
207
+ end
208
+
209
+ ##
210
+ # The Signet::OAuth2::Client object the Credentials instance is using.
211
+ #
212
+ # @return [Signet::OAuth2::Client]
213
+ #
48
214
  attr_accessor :client
49
- attr_reader :project_id
50
215
 
51
- # Delegate client methods to the client object.
216
+ ##
217
+ # Identifier for the project the client is authenticating with.
218
+ #
219
+ # @return [String]
220
+ #
221
+ attr_reader :project_id
222
+
223
+ ##
224
+ # Identifier for a separate project used for billing/quota, if any.
225
+ #
226
+ # @return [String,nil]
227
+ #
228
+ attr_reader :quota_project_id
229
+
230
+ # @private Delegate client methods to the client object.
52
231
  extend Forwardable
232
+
233
+ ##
234
+ # @!attribute [r] token_credential_uri
235
+ # @return [String] The token credential URI. The URI is the authorization server's HTTP
236
+ # endpoint capable of issuing tokens and refreshing expired tokens.
237
+ #
238
+ # @!attribute [r] audience
239
+ # @return [String] The target audience ID when issuing assertions. Used only by the
240
+ # assertion grant type.
241
+ #
242
+ # @!attribute [r] scope
243
+ # @return [String, Array<String>] The scope for this client. A scope is an access range
244
+ # defined by the authorization server. The scope can be a single value or a list of values.
245
+ #
246
+ # @!attribute [r] target_audience
247
+ # @return [String] The final target audience for ID tokens returned by this credential.
248
+ #
249
+ # @!attribute [r] issuer
250
+ # @return [String] The issuer ID associated with this client.
251
+ #
252
+ # @!attribute [r] signing_key
253
+ # @return [String, OpenSSL::PKey] The signing key associated with this client.
254
+ #
255
+ # @!attribute [r] updater_proc
256
+ # @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
257
+ # suitable for passing as a closure.
258
+ #
53
259
  def_delegators :@client,
54
260
  :token_credential_uri, :audience,
55
- :scope, :issuer, :signing_key, :updater_proc
261
+ :scope, :issuer, :signing_key, :updater_proc, :target_audience
56
262
 
57
- # rubocop:disable Metrics/AbcSize
263
+ ##
264
+ # Creates a new Credentials instance with the provided auth credentials, and with the default
265
+ # values configured on the class.
266
+ #
267
+ # @param [String, Hash, Signet::OAuth2::Client] keyfile
268
+ # The keyfile can be provided as one of the following:
269
+ #
270
+ # * The path to a JSON keyfile (as a +String+)
271
+ # * The contents of a JSON keyfile (as a +Hash+)
272
+ # * A +Signet::OAuth2::Client+ object
273
+ # @param [Hash] options
274
+ # The options for configuring the credentials instance. The following is supported:
275
+ #
276
+ # * +:scope+ - the scope for the client
277
+ # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
278
+ # * +:connection_builder+ - the connection builder to use for the client
279
+ # * +:default_connection+ - the default connection to use for the client
280
+ #
58
281
  def initialize keyfile, options = {}
59
- scope = options[:scope]
60
282
  verify_keyfile_provided! keyfile
61
283
  @project_id = options["project_id"] || options["project"]
284
+ @quota_project_id = options["quota_project_id"]
62
285
  if keyfile.is_a? Signet::OAuth2::Client
63
- @client = keyfile
64
- @project_id ||= keyfile.project_id if keyfile.respond_to? :project_id
286
+ update_from_signet keyfile
65
287
  elsif keyfile.is_a? Hash
66
- hash = stringify_hash_keys keyfile
67
- hash["scope"] ||= scope
68
- @client = init_client hash, options
69
- @project_id ||= (hash["project_id"] || hash["project"])
288
+ update_from_hash keyfile, options
70
289
  else
71
- verify_keyfile_exists! keyfile
72
- json = JSON.parse ::File.read(keyfile)
73
- json["scope"] ||= scope
74
- @project_id ||= (json["project_id"] || json["project"])
75
- @client = init_client json, options
290
+ update_from_filepath keyfile, options
76
291
  end
77
292
  CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
78
293
  @project_id ||= CredentialsLoader.load_gcloud_project_id
79
294
  @client.fetch_access_token!
295
+ @env_vars = nil
296
+ @paths = nil
297
+ @scope = nil
80
298
  end
81
- # rubocop:enable Metrics/AbcSize
82
299
 
83
- # Returns the default credentials checking, in this order, the path env
84
- # evironment variables, json environment variables, default paths. If the
85
- # previously stated locations do not contain keyfile information,
86
- # this method defaults to use the application default.
300
+ ##
301
+ # Creates a new Credentials instance with auth credentials acquired by searching the
302
+ # environment variables and paths configured on the class, and with the default values
303
+ # configured on the class.
304
+ #
305
+ # The auth credentials are searched for in the following order:
306
+ #
307
+ # 1. configured environment variables (see {Credentials.env_vars})
308
+ # 2. configured default file paths (see {Credentials.paths})
309
+ # 3. application default (see {Google::Auth.get_application_default})
310
+ #
311
+ # @param [Hash] options
312
+ # The options for configuring the credentials instance. The following is supported:
313
+ #
314
+ # * +:scope+ - the scope for the client
315
+ # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
316
+ # * +:connection_builder+ - the connection builder to use for the client
317
+ # * +:default_connection+ - the default connection to use for the client
318
+ #
319
+ # @return [Credentials]
320
+ #
87
321
  def self.default options = {}
88
- # First try to find keyfile file from environment variables.
89
- client = from_path_vars options
90
-
91
- # Second try to find keyfile json from environment variables.
92
- client ||= from_json_vars options
322
+ # First try to find keyfile file or json from environment variables.
323
+ client = from_env_vars options
93
324
 
94
- # Third try to find keyfile file from known file paths.
325
+ # Second try to find keyfile file from known file paths.
95
326
  client ||= from_default_paths options
96
327
 
97
328
  # Finally get instantiated client from Google::Auth
@@ -99,33 +330,22 @@ module Google
99
330
  client
100
331
  end
101
332
 
102
- def self.from_path_vars options
103
- self::PATH_ENV_VARS
104
- .map { |v| ENV[v] }
105
- .compact
106
- .select { |p| ::File.file? p }
107
- .each do |file|
108
- return new file, options
109
- end
110
- nil
111
- end
112
-
113
- def self.from_json_vars options
114
- json = lambda do |v|
115
- unless ENV[v].nil?
116
- begin
117
- JSON.parse ENV[v]
118
- rescue StandardError
119
- nil
120
- end
121
- end
333
+ ##
334
+ # @private Lookup Credentials from environment variables.
335
+ def self.from_env_vars options
336
+ env_vars.each do |env_var|
337
+ str = ENV[env_var]
338
+ next if str.nil?
339
+ return new str, options if ::File.file? str
340
+ return new ::JSON.parse(str), options rescue nil
122
341
  end
123
- self::JSON_ENV_VARS.map(&json).compact.each { |hash| return new hash, options }
124
342
  nil
125
343
  end
126
344
 
345
+ ##
346
+ # @private Lookup Credentials from default file paths.
127
347
  def self.from_default_paths options
128
- self::DEFAULT_PATHS
348
+ paths
129
349
  .select { |p| ::File.file? p }
130
350
  .each do |file|
131
351
  return new file, options
@@ -133,13 +353,16 @@ module Google
133
353
  nil
134
354
  end
135
355
 
356
+ ##
357
+ # @private Lookup Credentials using Google::Auth.get_application_default.
136
358
  def self.from_application_default options
137
- scope = options[:scope] || self::SCOPE
138
- client = Google::Auth.get_application_default scope
359
+ scope = options[:scope] || self.scope
360
+ auth_opts = { target_audience: options[:target_audience] || target_audience }
361
+ client = Google::Auth.get_application_default scope, auth_opts
139
362
  new client, options
140
363
  end
141
- private_class_method :from_path_vars,
142
- :from_json_vars,
364
+
365
+ private_class_method :from_env_vars,
143
366
  :from_default_paths,
144
367
  :from_application_default
145
368
 
@@ -171,17 +394,49 @@ module Google
171
394
 
172
395
  def client_options options
173
396
  # Keyfile options have higher priority over constructor defaults
174
- options["token_credential_uri"] ||= self.class::TOKEN_CREDENTIAL_URI
175
- options["audience"] ||= self.class::AUDIENCE
176
- options["scope"] ||= self.class::SCOPE
397
+ options["token_credential_uri"] ||= self.class.token_credential_uri
398
+ options["audience"] ||= self.class.audience
399
+ options["scope"] ||= self.class.scope
400
+ options["target_audience"] ||= self.class.target_audience
401
+
402
+ if !Array(options["scope"]).empty? && options["target_audience"]
403
+ raise ArgumentError, "Cannot specify both scope and target_audience"
404
+ end
177
405
 
406
+ needs_scope = options["target_audience"].nil?
178
407
  # client options for initializing signet client
179
408
  { token_credential_uri: options["token_credential_uri"],
180
409
  audience: options["audience"],
181
- scope: Array(options["scope"]),
410
+ scope: (needs_scope ? Array(options["scope"]) : nil),
411
+ target_audience: options["target_audience"],
182
412
  issuer: options["client_email"],
183
413
  signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
184
414
  end
415
+
416
+ def update_from_signet client
417
+ @project_id ||= client.project_id if client.respond_to? :project_id
418
+ @quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
419
+ @client = client
420
+ end
421
+
422
+ def update_from_hash hash, options
423
+ hash = stringify_hash_keys hash
424
+ hash["scope"] ||= options[:scope]
425
+ hash["target_audience"] ||= options[:target_audience]
426
+ @project_id ||= (hash["project_id"] || hash["project"])
427
+ @quota_project_id ||= hash["quota_project_id"]
428
+ @client = init_client hash, options
429
+ end
430
+
431
+ def update_from_filepath path, options
432
+ verify_keyfile_exists! path
433
+ json = JSON.parse ::File.read(path)
434
+ json["scope"] ||= options[:scope]
435
+ json["target_audience"] ||= options[:target_audience]
436
+ @project_id ||= (json["project_id"] || json["project"])
437
+ @quota_project_id ||= json["quota_project_id"]
438
+ @client = init_client json, options
439
+ end
185
440
  end
186
441
  end
187
442
  end