googleauth 0.1.0 → 0.16.2

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 (78) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +7 -0
  3. data/.github/CONTRIBUTING.md +74 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  6. data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
  7. data/.github/renovate.json +6 -0
  8. data/.github/sync-repo-settings.yaml +18 -0
  9. data/.github/workflows/ci.yml +55 -0
  10. data/.github/workflows/release-please.yml +39 -0
  11. data/.gitignore +3 -0
  12. data/.kokoro/populate-secrets.sh +76 -0
  13. data/.kokoro/release.cfg +52 -0
  14. data/.kokoro/release.sh +18 -0
  15. data/.kokoro/trampoline_v2.sh +489 -0
  16. data/.repo-metadata.json +5 -0
  17. data/.rubocop.yml +17 -0
  18. data/.toys/.toys.rb +45 -0
  19. data/.toys/ci.rb +43 -0
  20. data/.toys/kokoro/.toys.rb +66 -0
  21. data/.toys/kokoro/publish-docs.rb +67 -0
  22. data/.toys/kokoro/publish-gem.rb +53 -0
  23. data/.toys/linkinator.rb +43 -0
  24. data/.trampolinerc +48 -0
  25. data/CHANGELOG.md +199 -0
  26. data/CODE_OF_CONDUCT.md +43 -0
  27. data/Gemfile +22 -1
  28. data/{COPYING → LICENSE} +0 -0
  29. data/README.md +140 -17
  30. data/googleauth.gemspec +28 -28
  31. data/integration/helper.rb +31 -0
  32. data/integration/id_tokens/key_source_test.rb +74 -0
  33. data/lib/googleauth.rb +7 -37
  34. data/lib/googleauth/application_default.rb +81 -0
  35. data/lib/googleauth/client_id.rb +104 -0
  36. data/lib/googleauth/compute_engine.rb +73 -26
  37. data/lib/googleauth/credentials.rb +561 -0
  38. data/lib/googleauth/credentials_loader.rb +207 -0
  39. data/lib/googleauth/default_credentials.rb +93 -0
  40. data/lib/googleauth/iam.rb +75 -0
  41. data/lib/googleauth/id_tokens.rb +233 -0
  42. data/lib/googleauth/id_tokens/errors.rb +71 -0
  43. data/lib/googleauth/id_tokens/key_sources.rb +396 -0
  44. data/lib/googleauth/id_tokens/verifier.rb +142 -0
  45. data/lib/googleauth/json_key_reader.rb +50 -0
  46. data/lib/googleauth/scope_util.rb +61 -0
  47. data/lib/googleauth/service_account.rb +177 -67
  48. data/lib/googleauth/signet.rb +69 -8
  49. data/lib/googleauth/stores/file_token_store.rb +65 -0
  50. data/lib/googleauth/stores/redis_token_store.rb +96 -0
  51. data/lib/googleauth/token_store.rb +69 -0
  52. data/lib/googleauth/user_authorizer.rb +285 -0
  53. data/lib/googleauth/user_refresh.rb +129 -0
  54. data/lib/googleauth/version.rb +1 -1
  55. data/lib/googleauth/web_user_authorizer.rb +295 -0
  56. data/spec/googleauth/apply_auth_examples.rb +96 -94
  57. data/spec/googleauth/client_id_spec.rb +160 -0
  58. data/spec/googleauth/compute_engine_spec.rb +125 -55
  59. data/spec/googleauth/credentials_spec.rb +600 -0
  60. data/spec/googleauth/get_application_default_spec.rb +232 -80
  61. data/spec/googleauth/iam_spec.rb +80 -0
  62. data/spec/googleauth/scope_util_spec.rb +77 -0
  63. data/spec/googleauth/service_account_spec.rb +422 -68
  64. data/spec/googleauth/signet_spec.rb +101 -25
  65. data/spec/googleauth/stores/file_token_store_spec.rb +57 -0
  66. data/spec/googleauth/stores/redis_token_store_spec.rb +50 -0
  67. data/spec/googleauth/stores/store_examples.rb +58 -0
  68. data/spec/googleauth/user_authorizer_spec.rb +343 -0
  69. data/spec/googleauth/user_refresh_spec.rb +359 -0
  70. data/spec/googleauth/web_user_authorizer_spec.rb +172 -0
  71. data/spec/spec_helper.rb +51 -10
  72. data/test/helper.rb +33 -0
  73. data/test/id_tokens/key_sources_test.rb +240 -0
  74. data/test/id_tokens/verifier_test.rb +269 -0
  75. metadata +114 -75
  76. data/.travis.yml +0 -18
  77. data/CONTRIBUTING.md +0 -32
  78. data/Rakefile +0 -15
@@ -27,116 +27,470 @@
27
27
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
28
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
 
30
- spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
31
- $LOAD_PATH.unshift(spec_dir)
30
+ spec_dir = File.expand_path File.join(File.dirname(__FILE__))
31
+ $LOAD_PATH.unshift spec_dir
32
32
  $LOAD_PATH.uniq!
33
33
 
34
- require 'apply_auth_examples'
35
- require 'fileutils'
36
- require 'googleauth/service_account'
37
- require 'jwt'
38
- require 'multi_json'
39
- require 'openssl'
40
- require 'spec_helper'
41
- require 'tmpdir'
34
+ require "apply_auth_examples"
35
+ require "fakefs/safe"
36
+ require "fileutils"
37
+ require "googleauth/service_account"
38
+ require "jwt"
39
+ require "multi_json"
40
+ require "openssl"
41
+ require "spec_helper"
42
+ require "tmpdir"
43
+ require "os"
44
+
45
+ include Google::Auth::CredentialsLoader
46
+
47
+ shared_examples "jwt header auth" do
48
+ context "when jwt_aud_uri is present" do
49
+ let(:test_uri) { "https://www.googleapis.com/myservice" }
50
+ let(:auth_prefix) { "Bearer " }
51
+ let(:auth_key) { ServiceAccountJwtHeaderCredentials::AUTH_METADATA_KEY }
52
+ let(:jwt_uri_key) { ServiceAccountJwtHeaderCredentials::JWT_AUD_URI_KEY }
53
+
54
+ def expect_is_encoded_jwt hdr
55
+ expect(hdr).to_not be_nil
56
+ expect(hdr.start_with?(auth_prefix)).to be true
57
+ authorization = hdr[auth_prefix.length..-1]
58
+ payload, = JWT.decode authorization, @key.public_key, true, algorithm: "RS256"
59
+ expect(payload["aud"]).to eq(test_uri)
60
+ expect(payload["iss"]).to eq(client_email)
61
+ end
62
+
63
+ describe "#apply!" do
64
+ it "should update the target hash with a jwt token" do
65
+ md = { foo: "bar" }
66
+ md[jwt_uri_key] = test_uri
67
+ @client.apply! md
68
+ auth_header = md[auth_key]
69
+ expect_is_encoded_jwt auth_header
70
+ expect(md[jwt_uri_key]).to be_nil
71
+ end
72
+ end
73
+
74
+ describe "updater_proc" do
75
+ it "should provide a proc that updates a hash with a jwt token" do
76
+ md = { foo: "bar" }
77
+ md[jwt_uri_key] = test_uri
78
+ the_proc = @client.updater_proc
79
+ got = the_proc.call md
80
+ auth_header = got[auth_key]
81
+ expect_is_encoded_jwt auth_header
82
+ expect(got[jwt_uri_key]).to be_nil
83
+ expect(md[jwt_uri_key]).to_not be_nil
84
+ end
85
+ end
86
+
87
+ describe "#apply" do
88
+ it "should not update the original hash with a jwt token" do
89
+ md = { foo: "bar" }
90
+ md[jwt_uri_key] = test_uri
91
+ the_proc = @client.updater_proc
92
+ got = the_proc.call md
93
+ auth_header = md[auth_key]
94
+ expect(auth_header).to be_nil
95
+ expect(got[jwt_uri_key]).to be_nil
96
+ expect(md[jwt_uri_key]).to_not be_nil
97
+ end
98
+
99
+ it "should add a jwt token to the returned hash" do
100
+ md = { foo: "bar" }
101
+ md[jwt_uri_key] = test_uri
102
+ got = @client.apply md
103
+ auth_header = got[auth_key]
104
+ expect_is_encoded_jwt auth_header
105
+ end
106
+ end
107
+ end
108
+ end
42
109
 
43
110
  describe Google::Auth::ServiceAccountCredentials do
44
111
  ServiceAccountCredentials = Google::Auth::ServiceAccountCredentials
112
+ let(:client_email) { "app@developer.gserviceaccount.com" }
113
+ let :cred_json do
114
+ {
115
+ private_key_id: "a_private_key_id",
116
+ private_key: @key.to_pem,
117
+ client_email: client_email,
118
+ client_id: "app.apps.googleusercontent.com",
119
+ type: "service_account",
120
+ project_id: "a_project_id",
121
+ quota_project_id: "b_project_id"
122
+ }
123
+ end
45
124
 
46
- before(:example) do
47
- @key = OpenSSL::PKey::RSA.new(2048)
48
- @client = ServiceAccountCredentials.new(
49
- 'https://www.googleapis.com/auth/userinfo.profile',
50
- StringIO.new(cred_json_text))
125
+ before :example do
126
+ @key = OpenSSL::PKey::RSA.new 2048
127
+ @client = ServiceAccountCredentials.make_creds(
128
+ json_key_io: StringIO.new(cred_json_text),
129
+ scope: "https://www.googleapis.com/auth/userinfo.profile"
130
+ )
131
+ @id_client = ServiceAccountCredentials.make_creds(
132
+ json_key_io: StringIO.new(cred_json_text),
133
+ target_audience: "https://pubsub.googleapis.com/"
134
+ )
51
135
  end
52
136
 
53
- def make_auth_stubs(access_token: '')
54
- Faraday::Adapter::Test::Stubs.new do |stub|
55
- stub.post('/oauth2/v3/token') do |env|
56
- params = Addressable::URI.form_unencode(env[:body])
57
- _claim, _header = JWT.decode(params.assoc('assertion').last,
58
- @key.public_key)
59
- want = ['grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer']
60
- expect(params.assoc('grant_type')).to eq(want)
61
- build_access_token_json(access_token)
62
- end
137
+ def make_auth_stubs opts
138
+ body_fields = { "token_type" => "Bearer", "expires_in" => 3600 }
139
+ body_fields["access_token"] = opts[:access_token] if opts[:access_token]
140
+ body_fields["id_token"] = opts[:id_token] if opts[:id_token]
141
+ body = MultiJson.dump body_fields
142
+ blk = proc do |request|
143
+ params = Addressable::URI.form_unencode request.body
144
+ claim, _header = JWT.decode(params.assoc("assertion").last,
145
+ @key.public_key, true,
146
+ algorithm: "RS256")
147
+ !opts[:id_token] || claim["target_audience"] == "https://pubsub.googleapis.com/"
63
148
  end
149
+ stub_request(:post, "https://www.googleapis.com/oauth2/v4/token")
150
+ .with(body: hash_including(
151
+ "grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
152
+ ), &blk)
153
+ .to_return(body: body,
154
+ status: 200,
155
+ headers: { "Content-Type" => "application/json" })
64
156
  end
65
157
 
66
158
  def cred_json_text
67
- cred_json = {
68
- private_key_id: 'a_private_key_id',
69
- private_key: @key.to_pem,
70
- client_email: 'app@developer.gserviceaccount.com',
71
- client_id: 'app.apps.googleusercontent.com',
72
- type: 'service_account'
73
- }
74
- MultiJson.dump(cred_json)
159
+ MultiJson.dump cred_json
160
+ end
161
+
162
+ it_behaves_like "apply/apply! are OK"
163
+
164
+ context "when scope is nil" do
165
+ before :example do
166
+ @client.scope = nil
167
+ end
168
+
169
+ it_behaves_like "jwt header auth"
170
+ end
171
+
172
+ context "when enable_self_signed_jwt is set" do
173
+ before :example do
174
+ @client.instance_variable_set(:@enable_self_signed_jwt, true)
175
+ end
176
+
177
+ it_behaves_like "jwt header auth"
75
178
  end
76
179
 
77
- it_behaves_like 'apply/apply! are OK'
180
+ describe "#from_env" do
181
+ before :example do
182
+ @var_name = ENV_VAR
183
+ @credential_vars = [
184
+ ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR
185
+ ]
186
+ @original_env_vals = {}
187
+ @credential_vars.each { |var| @original_env_vals[var] = ENV[var] }
188
+ ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
78
189
 
79
- describe '#from_env' do
80
- before(:example) do
81
- @var_name = ServiceAccountCredentials::ENV_VAR
82
- @orig = ENV[@var_name]
83
- @scope = 'https://www.googleapis.com/auth/userinfo.profile'
190
+ @scope = "https://www.googleapis.com/auth/userinfo.profile"
191
+ @clz = ServiceAccountCredentials
84
192
  end
85
193
 
86
- after(:example) do
87
- ENV[@var_name] = @orig unless @orig.nil?
194
+ after :example do
195
+ @credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
88
196
  end
89
197
 
90
- it 'returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset' do
91
- ENV.delete(@var_name) unless ENV[@var_name].nil?
198
+ it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset" do
199
+ ENV.delete @var_name unless ENV[@var_name].nil?
92
200
  expect(ServiceAccountCredentials.from_env(@scope)).to be_nil
93
201
  end
94
202
 
95
- it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist' do
96
- ENV.delete(@var_name) unless ENV[@var_name].nil?
203
+ it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is empty" do
204
+ ENV[@var_name] = ""
205
+ expect(ServiceAccountCredentials.from_env(@scope)).to be_nil
206
+ end
207
+
208
+ it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
209
+ ENV.delete @var_name unless ENV[@var_name].nil?
97
210
  expect(ServiceAccountCredentials.from_env(@scope)).to be_nil
98
211
  Dir.mktmpdir do |dir|
99
- key_path = File.join(dir, 'does-not-exist')
212
+ key_path = File.join dir, "does-not-exist"
100
213
  ENV[@var_name] = key_path
101
- expect { sac.from_env(@scope) }.to raise_error
214
+ expect { @clz.from_env @scope }.to raise_error RuntimeError
102
215
  end
103
216
  end
104
217
 
105
- it 'succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid' do
106
- sac = ServiceAccountCredentials # shortens name
218
+ it "succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
107
219
  Dir.mktmpdir do |dir|
108
- key_path = File.join(dir, 'my_cert_file')
109
- FileUtils.mkdir_p(File.dirname(key_path))
110
- File.write(key_path, cred_json_text)
220
+ key_path = File.join dir, "my_cert_file"
221
+ FileUtils.mkdir_p File.dirname(key_path)
222
+ File.write key_path, cred_json_text
111
223
  ENV[@var_name] = key_path
112
- expect(sac.from_env(@scope)).to_not be_nil
224
+ expect(@clz.from_env(@scope)).to_not be_nil
113
225
  end
114
226
  end
227
+
228
+ it "succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are"\
229
+ " valid" do
230
+ ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
231
+ ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
232
+ expect(@clz.from_env(@scope)).to_not be_nil
233
+ end
234
+
235
+ it "sets project_id when the PROJECT_ID_VAR env var is set" do
236
+ ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
237
+ ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
238
+ ENV[PROJECT_ID_VAR] = cred_json[:project_id]
239
+ ENV[ENV_VAR] = nil
240
+ credentials = @clz.from_env @scope
241
+ expect(credentials.project_id).to eq(cred_json[:project_id])
242
+ end
243
+
244
+ it "succeeds when GOOGLE_PRIVATE_KEY is escaped" do
245
+ escaped_key = cred_json[:private_key].gsub "\n", '\n'
246
+ ENV[PRIVATE_KEY_VAR] = "\"#{escaped_key}\""
247
+ ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
248
+ expect(@clz.from_env(@scope)).to_not be_nil
249
+ end
250
+
251
+ it "propagates default_connection option" do
252
+ ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
253
+ ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
254
+ connection = Faraday.new headers: { "User-Agent" => "hello" }
255
+ creds = @clz.from_env @scope, default_connection: connection
256
+ expect(creds.build_default_connection).to be connection
257
+ end
115
258
  end
116
259
 
117
- describe '#from_well_known_path' do
118
- before(:example) do
119
- @home = ENV['HOME']
120
- @scope = 'https://www.googleapis.com/auth/userinfo.profile'
260
+ describe "#from_well_known_path" do
261
+ before :example do
262
+ @home = ENV["HOME"]
263
+ @app_data = ENV["APPDATA"]
264
+ @scope = "https://www.googleapis.com/auth/userinfo.profile"
265
+ @known_path = WELL_KNOWN_PATH
266
+ @clz = ServiceAccountCredentials
121
267
  end
122
268
 
123
- after(:example) do
124
- ENV['HOME'] = @home unless @home == ENV['HOME']
269
+ after :example do
270
+ ENV["HOME"] = @home unless @home == ENV["HOME"]
271
+ ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
125
272
  end
126
273
 
127
- it 'is nil if no file exists' do
128
- ENV['HOME'] = File.dirname(__FILE__)
274
+ it "is nil if no file exists" do
275
+ ENV["HOME"] = File.dirname __FILE__
129
276
  expect(ServiceAccountCredentials.from_well_known_path(@scope)).to be_nil
130
277
  end
131
278
 
132
- it 'successfully loads the file when it is present' do
133
- sac = ServiceAccountCredentials # shortens name
279
+ it "successfully loads the file when it is present" do
280
+ Dir.mktmpdir do |dir|
281
+ key_path = File.join dir, ".config", @known_path
282
+ key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
283
+ FileUtils.mkdir_p File.dirname(key_path)
284
+ File.write key_path, cred_json_text
285
+ ENV["HOME"] = dir
286
+ ENV["APPDATA"] = dir
287
+ expect(@clz.from_well_known_path(@scope)).to_not be_nil
288
+ end
289
+ end
290
+
291
+ it "successfully sets project_id when file is present" do
292
+ Dir.mktmpdir do |dir|
293
+ key_path = File.join dir, ".config", @known_path
294
+ key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
295
+ FileUtils.mkdir_p File.dirname(key_path)
296
+ File.write key_path, cred_json_text
297
+ ENV["HOME"] = dir
298
+ ENV["APPDATA"] = dir
299
+ credentials = @clz.from_well_known_path @scope
300
+ expect(credentials.project_id).to eq(cred_json[:project_id])
301
+ expect(credentials.quota_project_id).to eq(cred_json[:quota_project_id])
302
+ end
303
+ end
304
+
305
+ it "propagates default_connection option" do
306
+ Dir.mktmpdir do |dir|
307
+ key_path = File.join dir, ".config", @known_path
308
+ key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
309
+ FileUtils.mkdir_p File.dirname(key_path)
310
+ File.write key_path, cred_json_text
311
+ ENV["HOME"] = dir
312
+ ENV["APPDATA"] = dir
313
+ connection = Faraday.new headers: { "User-Agent" => "hello" }
314
+ creds = @clz.from_well_known_path @scope, default_connection: connection
315
+ expect(creds.build_default_connection).to be connection
316
+ end
317
+ end
318
+ end
319
+
320
+ describe "#from_system_default_path" do
321
+ before :example do
322
+ @scope = "https://www.googleapis.com/auth/userinfo.profile"
323
+ @program_data = ENV["ProgramData"]
324
+ @prefix = OS.windows? ? "/etc/Google/Auth/" : "/etc/google/auth/"
325
+ @path = File.join @prefix, CREDENTIALS_FILE_NAME
326
+ @clz = ServiceAccountCredentials
327
+ end
328
+
329
+ after :example do
330
+ ENV["ProgramData"] = @program_data
331
+ end
332
+
333
+ it "is nil if no file exists" do
334
+ FakeFS do
335
+ expect(ServiceAccountCredentials.from_system_default_path(@scope))
336
+ .to be_nil
337
+ end
338
+ end
339
+
340
+ it "successfully loads the file when it is present" do
341
+ FakeFS do
342
+ ENV["ProgramData"] = "/etc"
343
+ FileUtils.mkdir_p File.dirname(@path)
344
+ File.write @path, cred_json_text
345
+ expect(@clz.from_system_default_path(@scope)).to_not be_nil
346
+ File.delete @path
347
+ end
348
+ end
349
+
350
+ it "propagates default_connection option" do
351
+ FakeFS do
352
+ ENV["ProgramData"] = "/etc"
353
+ FileUtils.mkdir_p File.dirname(@path)
354
+ File.write @path, cred_json_text
355
+ connection = Faraday.new headers: { "User-Agent" => "hello" }
356
+ creds = @clz.from_system_default_path @scope, default_connection: connection
357
+ expect(creds.build_default_connection).to be connection
358
+ File.delete @path
359
+ end
360
+ end
361
+ end
362
+ end
363
+
364
+ describe Google::Auth::ServiceAccountJwtHeaderCredentials do
365
+ ServiceAccountJwtHeaderCredentials =
366
+ Google::Auth::ServiceAccountJwtHeaderCredentials
367
+
368
+ let(:client_email) { "app@developer.gserviceaccount.com" }
369
+ let(:clz) { Google::Auth::ServiceAccountJwtHeaderCredentials }
370
+ let :cred_json do
371
+ {
372
+ private_key_id: "a_private_key_id",
373
+ private_key: @key.to_pem,
374
+ client_email: client_email,
375
+ client_id: "app.apps.googleusercontent.com",
376
+ type: "service_account",
377
+ project_id: "a_project_id"
378
+ }
379
+ end
380
+
381
+ before :example do
382
+ @key = OpenSSL::PKey::RSA.new 2048
383
+ @client = clz.make_creds json_key_io: StringIO.new(cred_json_text)
384
+ end
385
+
386
+ def cred_json_text
387
+ MultiJson.dump cred_json
388
+ end
389
+
390
+ it_behaves_like "jwt header auth"
391
+
392
+ describe "#from_env" do
393
+ before :example do
394
+ @var_name = ENV_VAR
395
+ @credential_vars = [
396
+ ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR
397
+ ]
398
+ @original_env_vals = {}
399
+ @credential_vars.each { |var| @original_env_vals[var] = ENV[var] }
400
+ ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
401
+ end
402
+
403
+ after :example do
404
+ @credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
405
+ end
406
+
407
+ it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset" do
408
+ ENV.delete @var_name unless ENV[@var_name].nil?
409
+ expect(clz.from_env).to be_nil
410
+ end
411
+
412
+ it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is empty" do
413
+ ENV[@var_name] = ""
414
+ expect(clz.from_env).to be_nil
415
+ end
416
+
417
+ it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
418
+ ENV.delete @var_name unless ENV[@var_name].nil?
419
+ expect(clz.from_env).to be_nil
420
+ Dir.mktmpdir do |dir|
421
+ key_path = File.join dir, "does-not-exist"
422
+ ENV[@var_name] = key_path
423
+ expect { clz.from_env }.to raise_error RuntimeError
424
+ end
425
+ end
426
+
427
+ it "succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
428
+ Dir.mktmpdir do |dir|
429
+ key_path = File.join dir, "my_cert_file"
430
+ FileUtils.mkdir_p File.dirname(key_path)
431
+ File.write key_path, cred_json_text
432
+ ENV[@var_name] = key_path
433
+ expect(clz.from_env).to_not be_nil
434
+ end
435
+ end
436
+
437
+ it "succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are"\
438
+ " valid" do
439
+ ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
440
+ ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
441
+ expect(clz.from_env(@scope)).to_not be_nil
442
+ end
443
+
444
+ it "sets project_id when the PROJECT_ID_VAR env var is set" do
445
+ ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
446
+ ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
447
+ ENV[PROJECT_ID_VAR] = cred_json[:project_id]
448
+ ENV[ENV_VAR] = nil
449
+ credentials = clz.from_env @scope
450
+ expect(credentials).to_not be_nil
451
+ expect(credentials.project_id).to eq(cred_json[:project_id])
452
+ end
453
+ end
454
+
455
+ describe "#from_well_known_path" do
456
+ before :example do
457
+ @home = ENV["HOME"]
458
+ @app_data = ENV["APPDATA"]
459
+ end
460
+
461
+ after :example do
462
+ ENV["HOME"] = @home unless @home == ENV["HOME"]
463
+ ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
464
+ end
465
+
466
+ it "is nil if no file exists" do
467
+ ENV["HOME"] = File.dirname __FILE__
468
+ expect(clz.from_well_known_path).to be_nil
469
+ end
470
+
471
+ it "successfully loads the file when it is present" do
472
+ Dir.mktmpdir do |dir|
473
+ key_path = File.join dir, ".config", WELL_KNOWN_PATH
474
+ key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
475
+ FileUtils.mkdir_p File.dirname(key_path)
476
+ File.write key_path, cred_json_text
477
+ ENV["HOME"] = dir
478
+ ENV["APPDATA"] = dir
479
+ expect(clz.from_well_known_path).to_not be_nil
480
+ end
481
+ end
482
+
483
+ it "successfully sets project_id when file is present" do
134
484
  Dir.mktmpdir do |dir|
135
- key_path = File.join(dir, '.config', sac::WELL_KNOWN_PATH)
136
- FileUtils.mkdir_p(File.dirname(key_path))
137
- File.write(key_path, cred_json_text)
138
- ENV['HOME'] = dir
139
- expect(sac.from_well_known_path(@scope)).to_not be_nil
485
+ key_path = File.join dir, ".config", WELL_KNOWN_PATH
486
+ key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
487
+ FileUtils.mkdir_p File.dirname(key_path)
488
+ File.write key_path, cred_json_text
489
+ ENV["HOME"] = dir
490
+ ENV["APPDATA"] = dir
491
+ credentials = clz.from_well_known_path @scope
492
+ expect(credentials.project_id).to eq(cred_json[:project_id])
493
+ expect(credentials.quota_project_id).to be_nil
140
494
  end
141
495
  end
142
496
  end