googleauth 0.11.0 → 0.15.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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +7 -0
- data/.github/workflows/release.yml +36 -0
- data/.rubocop.yml +3 -1
- data/CHANGELOG.md +59 -23
- data/Gemfile +5 -2
- data/{COPYING → LICENSE} +0 -0
- data/Rakefile +21 -0
- data/googleauth.gemspec +3 -2
- data/integration/helper.rb +31 -0
- data/integration/id_tokens/key_source_test.rb +74 -0
- data/lib/googleauth.rb +1 -0
- data/lib/googleauth/application_default.rb +1 -1
- data/lib/googleauth/compute_engine.rb +40 -9
- data/lib/googleauth/credentials.rb +217 -54
- data/lib/googleauth/id_tokens.rb +233 -0
- data/lib/googleauth/id_tokens/errors.rb +71 -0
- data/lib/googleauth/id_tokens/key_sources.rb +394 -0
- data/lib/googleauth/id_tokens/verifier.rb +144 -0
- data/lib/googleauth/json_key_reader.rb +6 -2
- data/lib/googleauth/service_account.rb +39 -20
- data/lib/googleauth/signet.rb +3 -2
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +3 -6
- data/spec/googleauth/apply_auth_examples.rb +28 -5
- data/spec/googleauth/compute_engine_spec.rb +66 -13
- data/spec/googleauth/credentials_spec.rb +240 -112
- data/spec/googleauth/service_account_spec.rb +31 -16
- data/spec/googleauth/signet_spec.rb +15 -7
- data/spec/googleauth/user_refresh_spec.rb +1 -1
- data/test/helper.rb +33 -0
- data/test/id_tokens/key_sources_test.rb +240 -0
- data/test/id_tokens/verifier_test.rb +269 -0
- metadata +18 -7
@@ -51,20 +51,43 @@ 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
|
-
|
55
|
-
|
54
|
+
DEFAULT_METADATA_HOST = "169.254.169.254".freeze
|
55
|
+
|
56
|
+
# @private Unused and deprecated
|
57
|
+
COMPUTE_AUTH_TOKEN_URI =
|
58
|
+
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
59
|
+
# @private Unused and deprecated
|
60
|
+
COMPUTE_ID_TOKEN_URI =
|
61
|
+
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
|
62
|
+
# @private Unused and deprecated
|
56
63
|
COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
|
57
64
|
|
58
65
|
class << self
|
59
66
|
extend Memoist
|
60
67
|
|
68
|
+
def metadata_host
|
69
|
+
ENV.fetch "GCE_METADATA_HOST", DEFAULT_METADATA_HOST
|
70
|
+
end
|
71
|
+
|
72
|
+
def compute_check_uri
|
73
|
+
"http://#{metadata_host}".freeze
|
74
|
+
end
|
75
|
+
|
76
|
+
def compute_auth_token_uri
|
77
|
+
"#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
78
|
+
end
|
79
|
+
|
80
|
+
def compute_id_token_uri
|
81
|
+
"#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/identity".freeze
|
82
|
+
end
|
83
|
+
|
61
84
|
# Detect if this appear to be a GCE instance, by checking if metadata
|
62
85
|
# is available.
|
63
86
|
def on_gce? options = {}
|
64
87
|
# TODO: This should use google-cloud-env instead.
|
65
88
|
c = options[:connection] || Faraday.default_connection
|
66
89
|
headers = { "Metadata-Flavor" => "Google" }
|
67
|
-
resp = c.get
|
90
|
+
resp = c.get compute_check_uri, nil, headers do |req|
|
68
91
|
req.options.timeout = 1.0
|
69
92
|
req.options.open_timeout = 0.1
|
70
93
|
end
|
@@ -82,17 +105,25 @@ module Google
|
|
82
105
|
def fetch_access_token options = {}
|
83
106
|
c = options[:connection] || Faraday.default_connection
|
84
107
|
retry_with_error do
|
85
|
-
|
86
|
-
|
108
|
+
uri = target_audience ? GCECredentials.compute_id_token_uri : GCECredentials.compute_auth_token_uri
|
109
|
+
query = target_audience ? { "audience" => target_audience, "format" => "full" } : {}
|
110
|
+
query[:scopes] = Array(scope).join "," if scope
|
111
|
+
resp = c.get uri, query, "Metadata-Flavor" => "Google"
|
87
112
|
case resp.status
|
88
113
|
when 200
|
89
|
-
|
90
|
-
|
114
|
+
content_type = resp.headers["content-type"]
|
115
|
+
if content_type == "text/html"
|
116
|
+
{ (target_audience ? "id_token" : "access_token") => resp.body }
|
117
|
+
else
|
118
|
+
Signet::OAuth2.parse_credentials resp.body, content_type
|
119
|
+
end
|
120
|
+
when 403, 500
|
121
|
+
msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
|
122
|
+
raise Signet::UnexpectedStatusError, msg
|
91
123
|
when 404
|
92
124
|
raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
|
93
125
|
else
|
94
|
-
msg = "Unexpected error code #{resp.status}"
|
95
|
-
"#{UNEXPECTED_ERROR_SUFFIX}"
|
126
|
+
msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
|
96
127
|
raise Signet::AuthorizationError, msg
|
97
128
|
end
|
98
129
|
end
|
@@ -36,9 +36,46 @@ require "googleauth/credentials_loader"
|
|
36
36
|
module Google
|
37
37
|
module Auth
|
38
38
|
##
|
39
|
-
# Credentials is
|
40
|
-
#
|
41
|
-
|
39
|
+
# Credentials is a high-level base class used by Google's API client
|
40
|
+
# libraries to represent the authentication when connecting to an API.
|
41
|
+
# In most cases, it is subclassed by API-specific credential classes that
|
42
|
+
# can be instantiated by clients.
|
43
|
+
#
|
44
|
+
# ## Options
|
45
|
+
#
|
46
|
+
# Credentials classes are configured with options that dictate default
|
47
|
+
# values for parameters such as scope and audience. These defaults are
|
48
|
+
# expressed as class attributes, and may differ from endpoint to endpoint.
|
49
|
+
# Normally, an API client will provide subclasses specific to each
|
50
|
+
# endpoint, configured with appropriate values.
|
51
|
+
#
|
52
|
+
# Note that these options inherit up the class hierarchy. If a particular
|
53
|
+
# options is not set for a subclass, its superclass is queried.
|
54
|
+
#
|
55
|
+
# Some older users of this class set options via constants. This usage is
|
56
|
+
# deprecated. For example, instead of setting the `AUDIENCE` constant on
|
57
|
+
# your subclass, call the `audience=` method.
|
58
|
+
#
|
59
|
+
# ## Example
|
60
|
+
#
|
61
|
+
# class MyCredentials < Google::Auth::Credentials
|
62
|
+
# # Set the default scope for these credentials
|
63
|
+
# self.scope = "http://example.com/my_scope"
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# # creds is a credentials object suitable for Google API clients
|
67
|
+
# creds = MyCredentials.default
|
68
|
+
# creds.scope # => ["http://example.com/my_scope"]
|
69
|
+
#
|
70
|
+
# class SubCredentials < MyCredentials
|
71
|
+
# # Override the default scope for this subclass
|
72
|
+
# self.scope = "http://example.com/sub_scope"
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# creds2 = SubCredentials.default
|
76
|
+
# creds2.scope # => ["http://example.com/sub_scope"]
|
77
|
+
#
|
78
|
+
class Credentials # rubocop:disable Metrics/ClassLength
|
42
79
|
##
|
43
80
|
# The default token credential URI to be used when none is provided during initialization.
|
44
81
|
TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token".freeze
|
@@ -47,6 +84,8 @@ module Google
|
|
47
84
|
# The default target audience ID to be used when none is provided during initialization.
|
48
85
|
AUDIENCE = "https://oauth2.googleapis.com/token".freeze
|
49
86
|
|
87
|
+
@audience = @scope = @target_audience = @env_vars = @paths = @token_credential_uri = nil
|
88
|
+
|
50
89
|
##
|
51
90
|
# The default token credential URI to be used when none is provided during initialization.
|
52
91
|
# The URI is the authorization server's HTTP endpoint capable of issuing tokens and
|
@@ -55,9 +94,9 @@ module Google
|
|
55
94
|
# @return [String]
|
56
95
|
#
|
57
96
|
def self.token_credential_uri
|
58
|
-
|
59
|
-
|
60
|
-
|
97
|
+
lookup_auth_param :token_credential_uri do
|
98
|
+
lookup_local_constant :TOKEN_CREDENTIAL_URI
|
99
|
+
end
|
61
100
|
end
|
62
101
|
|
63
102
|
##
|
@@ -77,9 +116,9 @@ module Google
|
|
77
116
|
# @return [String]
|
78
117
|
#
|
79
118
|
def self.audience
|
80
|
-
|
81
|
-
|
82
|
-
|
119
|
+
lookup_auth_param :audience do
|
120
|
+
lookup_local_constant :AUDIENCE
|
121
|
+
end
|
83
122
|
end
|
84
123
|
|
85
124
|
##
|
@@ -97,20 +136,26 @@ module Google
|
|
97
136
|
# A scope is an access range defined by the authorization server.
|
98
137
|
# The scope can be a single value or a list of values.
|
99
138
|
#
|
139
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
140
|
+
# If {#scope} is set, this credential will produce access tokens.
|
141
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
142
|
+
#
|
100
143
|
# @return [String, Array<String>]
|
101
144
|
#
|
102
145
|
def self.scope
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
tmp_scope << const_get(:SCOPE) if const_defined? :SCOPE
|
108
|
-
tmp_scope.flatten.uniq
|
146
|
+
lookup_auth_param :scope do
|
147
|
+
vals = lookup_local_constant :SCOPE
|
148
|
+
vals ? Array(vals).flatten.uniq : nil
|
149
|
+
end
|
109
150
|
end
|
110
151
|
|
111
152
|
##
|
112
153
|
# Sets the default scope to be used when none is provided during initialization.
|
113
154
|
#
|
155
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
156
|
+
# If {#scope} is set, this credential will produce access tokens.
|
157
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
158
|
+
#
|
114
159
|
# @param [String, Array<String>] new_scope
|
115
160
|
# @return [String, Array<String>]
|
116
161
|
#
|
@@ -119,6 +164,34 @@ module Google
|
|
119
164
|
@scope = new_scope
|
120
165
|
end
|
121
166
|
|
167
|
+
##
|
168
|
+
# The default final target audience for ID tokens, to be used when none
|
169
|
+
# is provided during initialization.
|
170
|
+
#
|
171
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
172
|
+
# If {#scope} is set, this credential will produce access tokens.
|
173
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
174
|
+
#
|
175
|
+
# @return [String]
|
176
|
+
#
|
177
|
+
def self.target_audience
|
178
|
+
lookup_auth_param :target_audience
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Sets the default final target audience for ID tokens, to be used when none
|
183
|
+
# is provided during initialization.
|
184
|
+
#
|
185
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
186
|
+
# If {#scope} is set, this credential will produce access tokens.
|
187
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
188
|
+
#
|
189
|
+
# @param [String] new_target_audience
|
190
|
+
#
|
191
|
+
def self.target_audience= new_target_audience
|
192
|
+
@target_audience = new_target_audience
|
193
|
+
end
|
194
|
+
|
122
195
|
##
|
123
196
|
# The environment variables to search for credentials. Values can either be a file path to the
|
124
197
|
# credentials file, or the JSON contents of the credentials file.
|
@@ -126,13 +199,12 @@ module Google
|
|
126
199
|
# @return [Array<String>]
|
127
200
|
#
|
128
201
|
def self.env_vars
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
tmp_env_vars.flatten.uniq
|
202
|
+
lookup_auth_param :env_vars do
|
203
|
+
# Pull values when PATH_ENV_VARS or JSON_ENV_VARS constants exists.
|
204
|
+
path_env_vars = lookup_local_constant :PATH_ENV_VARS
|
205
|
+
json_env_vars = lookup_local_constant :JSON_ENV_VARS
|
206
|
+
(Array(path_env_vars) + Array(json_env_vars)).flatten.uniq if path_env_vars || json_env_vars
|
207
|
+
end
|
136
208
|
end
|
137
209
|
|
138
210
|
##
|
@@ -152,12 +224,11 @@ module Google
|
|
152
224
|
# @return [Array<String>]
|
153
225
|
#
|
154
226
|
def self.paths
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
tmp_paths.flatten.uniq
|
227
|
+
lookup_auth_param :paths do
|
228
|
+
# Pull in values if the DEFAULT_PATHS constant exists.
|
229
|
+
vals = lookup_local_constant :DEFAULT_PATHS
|
230
|
+
vals ? Array(vals).flatten.uniq : nil
|
231
|
+
end
|
161
232
|
end
|
162
233
|
|
163
234
|
##
|
@@ -171,6 +242,39 @@ module Google
|
|
171
242
|
@paths = new_paths
|
172
243
|
end
|
173
244
|
|
245
|
+
##
|
246
|
+
# @private
|
247
|
+
# Return the given parameter value, defaulting up the class hierarchy.
|
248
|
+
#
|
249
|
+
# First returns the value of the instance variable, if set.
|
250
|
+
# Next, calls the given block if provided. (This is generally used to
|
251
|
+
# look up legacy constant-based values.)
|
252
|
+
# Otherwise, calls the superclass method if present.
|
253
|
+
# Returns nil if all steps fail.
|
254
|
+
#
|
255
|
+
# @param [Symbol] The parameter name
|
256
|
+
# @return [Object] The value
|
257
|
+
#
|
258
|
+
def self.lookup_auth_param name
|
259
|
+
val = instance_variable_get "@#{name}".to_sym
|
260
|
+
val = yield if val.nil? && block_given?
|
261
|
+
return val unless val.nil?
|
262
|
+
return superclass.send name if superclass.respond_to? name
|
263
|
+
nil
|
264
|
+
end
|
265
|
+
|
266
|
+
##
|
267
|
+
# @private
|
268
|
+
# Return the value of the given constant if it is defined directly in
|
269
|
+
# this class, or nil if not.
|
270
|
+
#
|
271
|
+
# @param [Symbol] Name of the constant
|
272
|
+
# @return [Object] The value
|
273
|
+
#
|
274
|
+
def self.lookup_local_constant name
|
275
|
+
const_defined?(name, false) ? const_get(name) : nil
|
276
|
+
end
|
277
|
+
|
174
278
|
##
|
175
279
|
# The Signet::OAuth2::Client object the Credentials instance is using.
|
176
280
|
#
|
@@ -185,6 +289,13 @@ module Google
|
|
185
289
|
#
|
186
290
|
attr_reader :project_id
|
187
291
|
|
292
|
+
##
|
293
|
+
# Identifier for a separate project used for billing/quota, if any.
|
294
|
+
#
|
295
|
+
# @return [String,nil]
|
296
|
+
#
|
297
|
+
attr_reader :quota_project_id
|
298
|
+
|
188
299
|
# @private Delegate client methods to the client object.
|
189
300
|
extend Forwardable
|
190
301
|
|
@@ -201,6 +312,9 @@ module Google
|
|
201
312
|
# @return [String, Array<String>] The scope for this client. A scope is an access range
|
202
313
|
# defined by the authorization server. The scope can be a single value or a list of values.
|
203
314
|
#
|
315
|
+
# @!attribute [r] target_audience
|
316
|
+
# @return [String] The final target audience for ID tokens returned by this credential.
|
317
|
+
#
|
204
318
|
# @!attribute [r] issuer
|
205
319
|
# @return [String] The issuer ID associated with this client.
|
206
320
|
#
|
@@ -213,9 +327,7 @@ module Google
|
|
213
327
|
#
|
214
328
|
def_delegators :@client,
|
215
329
|
:token_credential_uri, :audience,
|
216
|
-
:scope, :issuer, :signing_key, :updater_proc
|
217
|
-
|
218
|
-
# rubocop:disable Metrics/AbcSize
|
330
|
+
:scope, :issuer, :signing_key, :updater_proc, :target_audience
|
219
331
|
|
220
332
|
##
|
221
333
|
# Creates a new Credentials instance with the provided auth credentials, and with the default
|
@@ -236,23 +348,15 @@ module Google
|
|
236
348
|
# * +:default_connection+ - the default connection to use for the client
|
237
349
|
#
|
238
350
|
def initialize keyfile, options = {}
|
239
|
-
scope = options[:scope]
|
240
351
|
verify_keyfile_provided! keyfile
|
241
352
|
@project_id = options["project_id"] || options["project"]
|
353
|
+
@quota_project_id = options["quota_project_id"]
|
242
354
|
if keyfile.is_a? Signet::OAuth2::Client
|
243
|
-
|
244
|
-
@project_id ||= keyfile.project_id if keyfile.respond_to? :project_id
|
355
|
+
update_from_signet keyfile
|
245
356
|
elsif keyfile.is_a? Hash
|
246
|
-
|
247
|
-
hash["scope"] ||= scope
|
248
|
-
@client = init_client hash, options
|
249
|
-
@project_id ||= (hash["project_id"] || hash["project"])
|
357
|
+
update_from_hash keyfile, options
|
250
358
|
else
|
251
|
-
|
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
|
359
|
+
update_from_filepath keyfile, options
|
256
360
|
end
|
257
361
|
CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
|
258
362
|
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
@@ -261,7 +365,6 @@ module Google
|
|
261
365
|
@paths = nil
|
262
366
|
@scope = nil
|
263
367
|
end
|
264
|
-
# rubocop:enable Metrics/AbcSize
|
265
368
|
|
266
369
|
##
|
267
370
|
# Creates a new Credentials instance with auth credentials acquired by searching the
|
@@ -302,8 +405,15 @@ module Google
|
|
302
405
|
env_vars.each do |env_var|
|
303
406
|
str = ENV[env_var]
|
304
407
|
next if str.nil?
|
305
|
-
|
306
|
-
|
408
|
+
io =
|
409
|
+
if ::File.file? str
|
410
|
+
::StringIO.new ::File.read str
|
411
|
+
else
|
412
|
+
json = ::JSON.parse str rescue nil
|
413
|
+
json ? ::StringIO.new(str) : nil
|
414
|
+
end
|
415
|
+
next if io.nil?
|
416
|
+
return from_io io, options
|
307
417
|
end
|
308
418
|
nil
|
309
419
|
end
|
@@ -311,11 +421,11 @@ module Google
|
|
311
421
|
##
|
312
422
|
# @private Lookup Credentials from default file paths.
|
313
423
|
def self.from_default_paths options
|
314
|
-
paths
|
315
|
-
|
316
|
-
.
|
317
|
-
|
318
|
-
|
424
|
+
paths.each do |path|
|
425
|
+
next unless path && ::File.file?(path)
|
426
|
+
io = ::StringIO.new ::File.read path
|
427
|
+
return from_io io, options
|
428
|
+
end
|
319
429
|
nil
|
320
430
|
end
|
321
431
|
|
@@ -323,13 +433,34 @@ module Google
|
|
323
433
|
# @private Lookup Credentials using Google::Auth.get_application_default.
|
324
434
|
def self.from_application_default options
|
325
435
|
scope = options[:scope] || self.scope
|
326
|
-
|
436
|
+
auth_opts = {
|
437
|
+
token_credential_uri: options[:token_credential_uri] || token_credential_uri,
|
438
|
+
audience: options[:audience] || audience,
|
439
|
+
target_audience: options[:target_audience] || target_audience,
|
440
|
+
enable_self_signed_jwt: options[:enable_self_signed_jwt] && options[:scope].nil?
|
441
|
+
}
|
442
|
+
client = Google::Auth.get_application_default scope, auth_opts
|
327
443
|
new client, options
|
328
444
|
end
|
329
445
|
|
446
|
+
# @private Read credentials from a JSON stream.
|
447
|
+
def self.from_io io, options
|
448
|
+
creds_input = {
|
449
|
+
json_key_io: io,
|
450
|
+
scope: options[:scope] || scope,
|
451
|
+
target_audience: options[:target_audience] || target_audience,
|
452
|
+
enable_self_signed_jwt: options[:enable_self_signed_jwt] && options[:scope].nil?,
|
453
|
+
token_credential_uri: options[:token_credential_uri] || token_credential_uri,
|
454
|
+
audience: options[:audience] || audience
|
455
|
+
}
|
456
|
+
client = Google::Auth::DefaultCredentials.make_creds creds_input
|
457
|
+
new client
|
458
|
+
end
|
459
|
+
|
330
460
|
private_class_method :from_env_vars,
|
331
461
|
:from_default_paths,
|
332
|
-
:from_application_default
|
462
|
+
:from_application_default,
|
463
|
+
:from_io
|
333
464
|
|
334
465
|
protected
|
335
466
|
|
@@ -362,14 +493,46 @@ module Google
|
|
362
493
|
options["token_credential_uri"] ||= self.class.token_credential_uri
|
363
494
|
options["audience"] ||= self.class.audience
|
364
495
|
options["scope"] ||= self.class.scope
|
496
|
+
options["target_audience"] ||= self.class.target_audience
|
497
|
+
|
498
|
+
if !Array(options["scope"]).empty? && options["target_audience"]
|
499
|
+
raise ArgumentError, "Cannot specify both scope and target_audience"
|
500
|
+
end
|
365
501
|
|
502
|
+
needs_scope = options["target_audience"].nil?
|
366
503
|
# client options for initializing signet client
|
367
504
|
{ token_credential_uri: options["token_credential_uri"],
|
368
505
|
audience: options["audience"],
|
369
|
-
scope: Array(options["scope"]),
|
506
|
+
scope: (needs_scope ? Array(options["scope"]) : nil),
|
507
|
+
target_audience: options["target_audience"],
|
370
508
|
issuer: options["client_email"],
|
371
509
|
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
|
372
510
|
end
|
511
|
+
|
512
|
+
def update_from_signet client
|
513
|
+
@project_id ||= client.project_id if client.respond_to? :project_id
|
514
|
+
@quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
|
515
|
+
@client = client
|
516
|
+
end
|
517
|
+
|
518
|
+
def update_from_hash hash, options
|
519
|
+
hash = stringify_hash_keys hash
|
520
|
+
hash["scope"] ||= options[:scope]
|
521
|
+
hash["target_audience"] ||= options[:target_audience]
|
522
|
+
@project_id ||= (hash["project_id"] || hash["project"])
|
523
|
+
@quota_project_id ||= hash["quota_project_id"]
|
524
|
+
@client = init_client hash, options
|
525
|
+
end
|
526
|
+
|
527
|
+
def update_from_filepath path, options
|
528
|
+
verify_keyfile_exists! path
|
529
|
+
json = JSON.parse ::File.read(path)
|
530
|
+
json["scope"] ||= options[:scope]
|
531
|
+
json["target_audience"] ||= options[:target_audience]
|
532
|
+
@project_id ||= (json["project_id"] || json["project"])
|
533
|
+
@quota_project_id ||= json["quota_project_id"]
|
534
|
+
@client = init_client json, options
|
535
|
+
end
|
373
536
|
end
|
374
537
|
end
|
375
538
|
end
|