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