googleauth 0.9.0 → 0.13.1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.kokoro/continuous/linux.cfg +12 -2
  3. data/.kokoro/continuous/osx.cfg +5 -0
  4. data/.kokoro/continuous/post.cfg +30 -0
  5. data/.kokoro/continuous/windows.cfg +10 -0
  6. data/.kokoro/presubmit/linux.cfg +11 -1
  7. data/.kokoro/presubmit/osx.cfg +5 -0
  8. data/.kokoro/presubmit/windows.cfg +10 -0
  9. data/.kokoro/release.cfg +42 -1
  10. data/.repo-metadata.json +5 -0
  11. data/.rubocop.yml +12 -35
  12. data/CHANGELOG.md +32 -0
  13. data/Gemfile +8 -3
  14. data/README.md +7 -11
  15. data/Rakefile +48 -5
  16. data/googleauth.gemspec +7 -4
  17. data/integration/helper.rb +31 -0
  18. data/integration/id_tokens/key_source_test.rb +74 -0
  19. data/lib/googleauth.rb +1 -0
  20. data/lib/googleauth/application_default.rb +9 -9
  21. data/lib/googleauth/compute_engine.rb +30 -27
  22. data/lib/googleauth/credentials.rb +92 -22
  23. data/lib/googleauth/credentials_loader.rb +14 -15
  24. data/lib/googleauth/id_tokens.rb +233 -0
  25. data/lib/googleauth/id_tokens/errors.rb +71 -0
  26. data/lib/googleauth/id_tokens/key_sources.rb +394 -0
  27. data/lib/googleauth/id_tokens/verifier.rb +144 -0
  28. data/lib/googleauth/json_key_reader.rb +6 -2
  29. data/lib/googleauth/service_account.rb +16 -7
  30. data/lib/googleauth/signet.rb +8 -6
  31. data/lib/googleauth/user_authorizer.rb +8 -3
  32. data/lib/googleauth/user_refresh.rb +1 -1
  33. data/lib/googleauth/version.rb +1 -1
  34. data/lib/googleauth/web_user_authorizer.rb +1 -1
  35. data/rakelib/devsite_builder.rb +45 -0
  36. data/rakelib/link_checker.rb +64 -0
  37. data/rakelib/repo_metadata.rb +59 -0
  38. data/spec/googleauth/apply_auth_examples.rb +28 -5
  39. data/spec/googleauth/compute_engine_spec.rb +37 -13
  40. data/spec/googleauth/credentials_spec.rb +25 -6
  41. data/spec/googleauth/service_account_spec.rb +23 -16
  42. data/spec/googleauth/signet_spec.rb +15 -7
  43. data/spec/googleauth/user_authorizer_spec.rb +21 -1
  44. data/spec/googleauth/user_refresh_spec.rb +1 -1
  45. data/test/helper.rb +33 -0
  46. data/test/id_tokens/key_sources_test.rb +240 -0
  47. data/test/id_tokens/verifier_test.rb +269 -0
  48. metadata +45 -12
@@ -1,7 +1,7 @@
1
1
  # -*- ruby -*-
2
2
  # encoding: utf-8
3
3
 
4
- $LOAD_PATH.push File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.push File.expand_path("lib", __dir__)
5
5
  require "googleauth/version"
6
6
 
7
7
  Gem::Specification.new do |gem|
@@ -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"
@@ -34,18 +34,20 @@ module Google
34
34
  # Module Auth provides classes that provide Google-specific authorization
35
35
  # used to access Google APIs.
36
36
  module Auth
37
- NOT_FOUND_ERROR = <<-ERROR_MESSAGE.freeze
38
- Could not load the default credentials. Browse to
39
- https://developers.google.com/accounts/docs/application-default-credentials
40
- for more information
41
- ERROR_MESSAGE
37
+ NOT_FOUND_ERROR = <<~ERROR_MESSAGE.freeze
38
+ Could not load the default credentials. Browse to
39
+ https://developers.google.com/accounts/docs/application-default-credentials
40
+ for more information
41
+ ERROR_MESSAGE
42
+
43
+ module_function
42
44
 
43
45
  # Obtains the default credentials implementation to use in this
44
46
  # environment.
45
47
  #
46
48
  # Use this to obtain the Application Default Credentials for accessing
47
49
  # Google APIs. Application Default Credentials are described in detail
48
- # at http://goo.gl/IUuyuX.
50
+ # at https://cloud.google.com/docs/authentication/production.
49
51
  #
50
52
  # If supplied, scope is used to create the credentials instance, when it can
51
53
  # be applied. E.g, on google compute engine and for user credentials the
@@ -73,9 +75,7 @@ ERROR_MESSAGE
73
75
  GCECredentials.unmemoize_all
74
76
  raise NOT_FOUND_ERROR
75
77
  end
76
- GCECredentials.new
78
+ GCECredentials.new scope: scope
77
79
  end
78
-
79
- module_function :get_application_default
80
80
  end
81
81
  end
@@ -35,46 +35,42 @@ module Google
35
35
  # Module Auth provides classes that provide Google-specific authorization
36
36
  # used to access Google APIs.
37
37
  module Auth
38
- NO_METADATA_SERVER_ERROR = <<-ERROR.freeze
39
- Error code 404 trying to get security access token
40
- from Compute Engine metadata for the default service account. This
41
- may be because the virtual machine instance does not have permission
42
- scopes specified.
43
- ERROR
44
- UNEXPECTED_ERROR_SUFFIX = <<-ERROR.freeze
45
- trying to get security access token from Compute Engine metadata for
46
- the default service account
47
- ERROR
38
+ NO_METADATA_SERVER_ERROR = <<~ERROR.freeze
39
+ Error code 404 trying to get security access token
40
+ from Compute Engine metadata for the default service account. This
41
+ may be because the virtual machine instance does not have permission
42
+ scopes specified.
43
+ ERROR
44
+ UNEXPECTED_ERROR_SUFFIX = <<~ERROR.freeze
45
+ trying to get security access token from Compute Engine metadata for
46
+ the default service account
47
+ ERROR
48
48
 
49
49
  # Extends Signet::OAuth2::Client so that the auth token is obtained from
50
50
  # the GCE metadata server.
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,19 @@ ERROR
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" } : {}
89
+ query[:scopes] = Array(scope).join " " if scope
91
90
  headers = { "Metadata-Flavor" => "Google" }
92
- resp = c.get COMPUTE_AUTH_TOKEN_URI, nil, headers
91
+ resp = c.get uri, query, headers
93
92
  case resp.status
94
93
  when 200
95
- Signet::OAuth2.parse_credentials(resp.body,
96
- resp.headers["content-type"])
94
+ content_type = resp.headers["content-type"]
95
+ if content_type == "text/html"
96
+ { (target_audience ? "id_token" : "access_token") => resp.body }
97
+ else
98
+ Signet::OAuth2.parse_credentials resp.body, content_type
99
+ end
97
100
  when 404
98
101
  raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
99
102
  else
@@ -47,6 +47,8 @@ module Google
47
47
  # The default target audience ID to be used when none is provided during initialization.
48
48
  AUDIENCE = "https://oauth2.googleapis.com/token".freeze
49
49
 
50
+ @audience = @scope = @target_audience = @env_vars = @paths = nil
51
+
50
52
  ##
51
53
  # The default token credential URI to be used when none is provided during initialization.
52
54
  # The URI is the authorization server's HTTP endpoint capable of issuing tokens and
@@ -97,20 +99,25 @@ module Google
97
99
  # A scope is an access range defined by the authorization server.
98
100
  # The scope can be a single value or a list of values.
99
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
+ #
100
106
  # @return [String, Array<String>]
101
107
  #
102
108
  def self.scope
103
109
  return @scope unless @scope.nil?
104
110
 
105
- tmp_scope = []
106
- # Pull in values is the SCOPE constant exists.
107
- tmp_scope << const_get(:SCOPE) if const_defined? :SCOPE
108
- tmp_scope.flatten.uniq
111
+ Array(const_get(:SCOPE)).flatten.uniq if const_defined? :SCOPE
109
112
  end
110
113
 
111
114
  ##
112
115
  # Sets the default scope to be used when none is provided during initialization.
113
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
+ #
114
121
  # @param [String, Array<String>] new_scope
115
122
  # @return [String, Array<String>]
116
123
  #
@@ -119,6 +126,34 @@ module Google
119
126
  @scope = new_scope
120
127
  end
121
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
+
122
157
  ##
123
158
  # The environment variables to search for credentials. Values can either be a file path to the
124
159
  # credentials file, or the JSON contents of the credentials file.
@@ -185,6 +220,13 @@ module Google
185
220
  #
186
221
  attr_reader :project_id
187
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
+
188
230
  # @private Delegate client methods to the client object.
189
231
  extend Forwardable
190
232
 
@@ -201,6 +243,9 @@ module Google
201
243
  # @return [String, Array<String>] The scope for this client. A scope is an access range
202
244
  # defined by the authorization server. The scope can be a single value or a list of values.
203
245
  #
246
+ # @!attribute [r] target_audience
247
+ # @return [String] The final target audience for ID tokens returned by this credential.
248
+ #
204
249
  # @!attribute [r] issuer
205
250
  # @return [String] The issuer ID associated with this client.
206
251
  #
@@ -213,9 +258,7 @@ module Google
213
258
  #
214
259
  def_delegators :@client,
215
260
  :token_credential_uri, :audience,
216
- :scope, :issuer, :signing_key, :updater_proc
217
-
218
- # rubocop:disable Metrics/AbcSize
261
+ :scope, :issuer, :signing_key, :updater_proc, :target_audience
219
262
 
220
263
  ##
221
264
  # Creates a new Credentials instance with the provided auth credentials, and with the default
@@ -236,29 +279,23 @@ module Google
236
279
  # * +:default_connection+ - the default connection to use for the client
237
280
  #
238
281
  def initialize keyfile, options = {}
239
- scope = options[:scope]
240
282
  verify_keyfile_provided! keyfile
241
283
  @project_id = options["project_id"] || options["project"]
284
+ @quota_project_id = options["quota_project_id"]
242
285
  if keyfile.is_a? Signet::OAuth2::Client
243
- @client = keyfile
244
- @project_id ||= keyfile.project_id if keyfile.respond_to? :project_id
286
+ update_from_signet keyfile
245
287
  elsif keyfile.is_a? Hash
246
- hash = stringify_hash_keys keyfile
247
- hash["scope"] ||= scope
248
- @client = init_client hash, options
249
- @project_id ||= (hash["project_id"] || hash["project"])
288
+ update_from_hash keyfile, options
250
289
  else
251
- verify_keyfile_exists! keyfile
252
- json = JSON.parse ::File.read(keyfile)
253
- json["scope"] ||= scope
254
- @project_id ||= (json["project_id"] || json["project"])
255
- @client = init_client json, options
290
+ update_from_filepath keyfile, options
256
291
  end
257
292
  CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
258
293
  @project_id ||= CredentialsLoader.load_gcloud_project_id
259
294
  @client.fetch_access_token!
295
+ @env_vars = nil
296
+ @paths = nil
297
+ @scope = nil
260
298
  end
261
- # rubocop:enable Metrics/AbcSize
262
299
 
263
300
  ##
264
301
  # Creates a new Credentials instance with auth credentials acquired by searching the
@@ -320,7 +357,8 @@ module Google
320
357
  # @private Lookup Credentials using Google::Auth.get_application_default.
321
358
  def self.from_application_default options
322
359
  scope = options[:scope] || self.scope
323
- client = Google::Auth.get_application_default scope
360
+ auth_opts = { target_audience: options[:target_audience] || target_audience }
361
+ client = Google::Auth.get_application_default scope, auth_opts
324
362
  new client, options
325
363
  end
326
364
 
@@ -359,14 +397,46 @@ module Google
359
397
  options["token_credential_uri"] ||= self.class.token_credential_uri
360
398
  options["audience"] ||= self.class.audience
361
399
  options["scope"] ||= self.class.scope
400
+ options["target_audience"] ||= self.class.target_audience
362
401
 
402
+ if !Array(options["scope"]).empty? && options["target_audience"]
403
+ raise ArgumentError, "Cannot specify both scope and target_audience"
404
+ end
405
+
406
+ needs_scope = options["target_audience"].nil?
363
407
  # client options for initializing signet client
364
408
  { token_credential_uri: options["token_credential_uri"],
365
409
  audience: options["audience"],
366
- scope: Array(options["scope"]),
410
+ scope: (needs_scope ? Array(options["scope"]) : nil),
411
+ target_audience: options["target_audience"],
367
412
  issuer: options["client_email"],
368
413
  signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
369
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
370
440
  end
371
441
  end
372
442
  end