googleauth 0.9.0 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
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