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