googleauth 0.5.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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,97 @@
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 'faraday'
36
- require 'googleauth/compute_engine'
37
- require 'spec_helper'
34
+ require "apply_auth_examples"
35
+ require "faraday"
36
+ require "googleauth/compute_engine"
37
+ require "spec_helper"
38
38
 
39
39
  describe Google::Auth::GCECredentials do
40
- MD_URI = 'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token'
40
+ MD_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
41
41
  GCECredentials = Google::Auth::GCECredentials
42
42
 
43
- before(:example) do
43
+ before :example do
44
44
  @client = GCECredentials.new
45
45
  end
46
46
 
47
- def make_auth_stubs(opts = {})
48
- access_token = opts[:access_token] || ''
49
- body = MultiJson.dump('access_token' => access_token,
50
- 'token_type' => 'Bearer',
51
- 'expires_in' => 3600)
47
+ def make_auth_stubs opts = {}
48
+ access_token = opts[:access_token] || ""
49
+ body = MultiJson.dump("access_token" => access_token,
50
+ "token_type" => "Bearer",
51
+ "expires_in" => 3600)
52
52
  stub_request(:get, MD_URI)
53
- .with(headers: { 'Metadata-Flavor' => 'Google' })
54
- .to_return(body: body,
55
- status: 200,
56
- headers: { 'Content-Type' => 'application/json' })
53
+ .with(headers: { "Metadata-Flavor" => "Google" })
54
+ .to_return(body: body,
55
+ status: 200,
56
+ headers: { "Content-Type" => "application/json" })
57
57
  end
58
58
 
59
- it_behaves_like 'apply/apply! are OK'
59
+ it_behaves_like "apply/apply! are OK"
60
60
 
61
- context 'metadata is unavailable' do
62
- describe '#fetch_access_token' do
63
- it 'should fail if the metadata request returns a 404' do
61
+ context "metadata is unavailable" do
62
+ describe "#fetch_access_token" do
63
+ it "should fail if the metadata request returns a 404" do
64
64
  stub = stub_request(:get, MD_URI)
65
- .to_return(status: 404,
66
- headers: { 'Metadata-Flavor' => 'Google' })
67
- blk = proc { @client.fetch_access_token! }
68
- expect(&blk).to raise_error Signet::AuthorizationError
65
+ .to_return(status: 404,
66
+ headers: { "Metadata-Flavor" => "Google" })
67
+ expect { @client.fetch_access_token! }
68
+ .to raise_error Signet::AuthorizationError
69
69
  expect(stub).to have_been_requested
70
70
  end
71
71
 
72
- it 'should fail if the metadata request returns an unexpected code' do
72
+ it "should fail if the metadata request returns an unexpected code" do
73
73
  stub = stub_request(:get, MD_URI)
74
- .to_return(status: 503,
75
- headers: { 'Metadata-Flavor' => 'Google' })
76
- blk = proc { @client.fetch_access_token! }
77
- expect(&blk).to raise_error Signet::AuthorizationError
74
+ .to_return(status: 503,
75
+ headers: { "Metadata-Flavor" => "Google" })
76
+ expect { @client.fetch_access_token! }
77
+ .to raise_error Signet::AuthorizationError
78
78
  expect(stub).to have_been_requested
79
79
  end
80
+
81
+ it "should fail with Signet::AuthorizationError if request times out" do
82
+ allow_any_instance_of(Faraday::Connection).to receive(:get)
83
+ .and_raise(Faraday::TimeoutError)
84
+ expect { @client.fetch_access_token! }
85
+ .to raise_error Signet::AuthorizationError
86
+ end
87
+
88
+ it "should fail with Signet::AuthorizationError if request fails" do
89
+ allow_any_instance_of(Faraday::Connection).to receive(:get)
90
+ .and_raise(Faraday::ConnectionFailed, nil)
91
+ expect { @client.fetch_access_token! }
92
+ .to raise_error Signet::AuthorizationError
93
+ end
80
94
  end
81
95
  end
82
96
 
83
- describe '#on_gce?' do
84
- it 'should be true when Metadata-Flavor is Google' do
85
- stub = stub_request(:get, 'http://169.254.169.254')
86
- .to_return(status: 200,
87
- headers: { 'Metadata-Flavor' => 'Google' })
97
+ describe "#on_gce?" do
98
+ it "should be true when Metadata-Flavor is Google" do
99
+ stub = stub_request(:get, "http://169.254.169.254")
100
+ .with(headers: { "Metadata-Flavor" => "Google" })
101
+ .to_return(status: 200,
102
+ headers: { "Metadata-Flavor" => "Google" })
88
103
  expect(GCECredentials.on_gce?({}, true)).to eq(true)
89
104
  expect(stub).to have_been_requested
90
105
  end
91
106
 
92
- it 'should be false when Metadata-Flavor is not Google' do
93
- stub = stub_request(:get, 'http://169.254.169.254')
94
- .to_return(status: 200,
95
- headers: { 'Metadata-Flavor' => 'NotGoogle' })
107
+ it "should be false when Metadata-Flavor is not Google" do
108
+ stub = stub_request(:get, "http://169.254.169.254")
109
+ .with(headers: { "Metadata-Flavor" => "Google" })
110
+ .to_return(status: 200,
111
+ headers: { "Metadata-Flavor" => "NotGoogle" })
96
112
  expect(GCECredentials.on_gce?({}, true)).to eq(false)
97
113
  expect(stub).to have_been_requested
98
114
  end
99
115
 
100
- it 'should be false if the response is not 200' do
101
- stub = stub_request(:get, 'http://169.254.169.254')
102
- .to_return(status: 404,
103
- headers: { 'Metadata-Flavor' => 'NotGoogle' })
116
+ it "should be false if the response is not 200" do
117
+ stub = stub_request(:get, "http://169.254.169.254")
118
+ .with(headers: { "Metadata-Flavor" => "Google" })
119
+ .to_return(status: 404,
120
+ headers: { "Metadata-Flavor" => "NotGoogle" })
104
121
  expect(GCECredentials.on_gce?({}, true)).to eq(false)
105
122
  expect(stub).to have_been_requested
106
123
  end
@@ -0,0 +1,467 @@
1
+ # Copyright 2017, Google Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are
6
+ # met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above
11
+ # copyright notice, this list of conditions and the following disclaimer
12
+ # in the documentation and/or other materials provided with the
13
+ # distribution.
14
+ # * Neither the name of Google Inc. nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require "googleauth"
31
+
32
+
33
+ # This test is testing the private class Google::Auth::Credentials. We want to
34
+ # make sure that the passed in scope propogates to the Signet object. This means
35
+ # testing the private API, which is generally frowned on.
36
+ describe Google::Auth::Credentials, :private do
37
+ let :default_keyfile_hash do
38
+ {
39
+ "private_key_id" => "testabc1234567890xyz",
40
+ "private_key" => "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAOyi0Hy1l4Ym2m2o71Q0TF4O9E81isZEsX0bb+Bqz1SXEaSxLiXM\nUZE8wu0eEXivXuZg6QVCW/5l+f2+9UPrdNUCAwEAAQJAJkqubA/Chj3RSL92guy3\nktzeodarLyw8gF8pOmpuRGSiEo/OLTeRUMKKD1/kX4f9sxf3qDhB4e7dulXR1co/\nIQIhAPx8kMW4XTTL6lJYd2K5GrH8uBMp8qL5ya3/XHrBgw3dAiEA7+3Iw3ULTn2I\n1J34WlJ2D5fbzMzB4FAHUNEV7Ys3f1kCIQDtUahCMChrl7+H5t9QS+xrn77lRGhs\nB50pjvy95WXpgQIhAI2joW6JzTfz8fAapb+kiJ/h9Vcs1ZN3iyoRlNFb61JZAiA8\nNy5NyNrMVwtB/lfJf1dAK/p/Bwd8LZLtgM6PapRfgw==\n-----END RSA PRIVATE KEY-----\n",
41
+ "client_email" => "credz-testabc1234567890xyz@developer.gserviceaccount.com",
42
+ "client_id" => "credz-testabc1234567890xyz.apps.googleusercontent.com",
43
+ "type" => "service_account",
44
+ "project_id" => "a_project_id"
45
+ }
46
+ end
47
+
48
+ it "uses a default scope" do
49
+ mocked_signet = double "Signet::OAuth2::Client"
50
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
51
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
52
+ allow(mocked_signet).to receive(:client_id)
53
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
54
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
55
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
56
+ expect(options[:scope]).to eq([])
57
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
58
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
59
+
60
+ mocked_signet
61
+ end
62
+
63
+ Google::Auth::Credentials.new default_keyfile_hash
64
+ end
65
+
66
+ it "uses a custom scope" do
67
+ mocked_signet = double "Signet::OAuth2::Client"
68
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
69
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
70
+ allow(mocked_signet).to receive(:client_id)
71
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
72
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
73
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
74
+ expect(options[:scope]).to eq(["http://example.com/scope"])
75
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
76
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
77
+
78
+ mocked_signet
79
+ end
80
+
81
+ Google::Auth::Credentials.new default_keyfile_hash, scope: "http://example.com/scope"
82
+ end
83
+
84
+ describe "using CONSTANTS" do
85
+ it "can be subclassed to pass in other env paths" do
86
+ test_path_env_val = "/unknown/path/to/file.txt".freeze
87
+ test_json_env_val = JSON.generate default_keyfile_hash
88
+
89
+ ENV["TEST_PATH"] = test_path_env_val
90
+ ENV["TEST_JSON_VARS"] = test_json_env_val
91
+
92
+ class TestCredentials1 < Google::Auth::Credentials
93
+ TOKEN_CREDENTIAL_URI = "https://example.com/token".freeze
94
+ AUDIENCE = "https://example.com/audience".freeze
95
+ SCOPE = "http://example.com/scope".freeze
96
+ PATH_ENV_VARS = ["TEST_PATH"].freeze
97
+ JSON_ENV_VARS = ["TEST_JSON_VARS"].freeze
98
+ end
99
+
100
+ allow(::File).to receive(:file?).with(test_path_env_val) { false }
101
+ allow(::File).to receive(:file?).with(test_json_env_val) { false }
102
+
103
+ mocked_signet = double "Signet::OAuth2::Client"
104
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
105
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
106
+ allow(mocked_signet).to receive(:client_id)
107
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
108
+ expect(options[:token_credential_uri]).to eq("https://example.com/token")
109
+ expect(options[:audience]).to eq("https://example.com/audience")
110
+ expect(options[:scope]).to eq(["http://example.com/scope"])
111
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
112
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
113
+
114
+ mocked_signet
115
+ end
116
+
117
+ creds = TestCredentials1.default
118
+ expect(creds).to be_a_kind_of(TestCredentials1)
119
+ expect(creds.client).to eq(mocked_signet)
120
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
121
+ end
122
+
123
+ it "subclasses can use PATH_ENV_VARS to get keyfile path" do
124
+ class TestCredentials2 < Google::Auth::Credentials
125
+ SCOPE = "http://example.com/scope".freeze
126
+ PATH_ENV_VARS = %w[PATH_ENV_DUMMY PATH_ENV_TEST].freeze
127
+ JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
128
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
129
+ end
130
+
131
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
132
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
133
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
134
+ allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
135
+ allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
136
+ allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { JSON.generate default_keyfile_hash }
137
+
138
+ mocked_signet = double "Signet::OAuth2::Client"
139
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
140
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
141
+ allow(mocked_signet).to receive(:client_id)
142
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
143
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
144
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
145
+ expect(options[:scope]).to eq(["http://example.com/scope"])
146
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
147
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
148
+
149
+ mocked_signet
150
+ end
151
+
152
+ creds = TestCredentials2.default
153
+ expect(creds).to be_a_kind_of(TestCredentials2)
154
+ expect(creds.client).to eq(mocked_signet)
155
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
156
+ end
157
+
158
+ it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
159
+ test_json_env_val = JSON.generate default_keyfile_hash
160
+
161
+ class TestCredentials3 < Google::Auth::Credentials
162
+ SCOPE = "http://example.com/scope".freeze
163
+ PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
164
+ JSON_ENV_VARS = %w[JSON_ENV_DUMMY JSON_ENV_TEST].freeze
165
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
166
+ end
167
+
168
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
169
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
170
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
171
+ allow(::File).to receive(:file?).with(test_json_env_val) { false }
172
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
173
+ allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
174
+
175
+ mocked_signet = double "Signet::OAuth2::Client"
176
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
177
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
178
+ allow(mocked_signet).to receive(:client_id)
179
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
180
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
181
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
182
+ expect(options[:scope]).to eq(["http://example.com/scope"])
183
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
184
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
185
+
186
+ mocked_signet
187
+ end
188
+
189
+ creds = TestCredentials3.default
190
+ expect(creds).to be_a_kind_of(TestCredentials3)
191
+ expect(creds.client).to eq(mocked_signet)
192
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
193
+ end
194
+
195
+ it "subclasses can use DEFAULT_PATHS to get keyfile path" do
196
+ class TestCredentials4 < Google::Auth::Credentials
197
+ SCOPE = "http://example.com/scope".freeze
198
+ PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
199
+ JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
200
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
201
+ end
202
+
203
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
204
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
205
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
206
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
207
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
208
+ allow(::File).to receive(:read).with("~/default/path/to/file.txt") { JSON.generate default_keyfile_hash }
209
+
210
+ mocked_signet = double "Signet::OAuth2::Client"
211
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
212
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
213
+ allow(mocked_signet).to receive(:client_id)
214
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
215
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
216
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
217
+ expect(options[:scope]).to eq(["http://example.com/scope"])
218
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
219
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
220
+
221
+ mocked_signet
222
+ end
223
+
224
+ creds = TestCredentials4.default
225
+ expect(creds).to be_a_kind_of(TestCredentials4)
226
+ expect(creds.client).to eq(mocked_signet)
227
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
228
+ end
229
+
230
+ it "subclasses that find no matches default to Google::Auth.get_application_default" do
231
+ class TestCredentials5 < Google::Auth::Credentials
232
+ SCOPE = "http://example.com/scope".freeze
233
+ PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
234
+ JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
235
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
236
+ end
237
+
238
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
239
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
240
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
241
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
242
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
243
+
244
+ mocked_signet = double "Signet::OAuth2::Client"
245
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
246
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
247
+ allow(mocked_signet).to receive(:client_id)
248
+ allow(Google::Auth).to receive(:get_application_default) do |scope|
249
+ expect(scope).to eq([TestCredentials5::SCOPE])
250
+
251
+ # This should really be a Signet::OAuth2::Client object,
252
+ # but mocking is making that difficult, so return a valid hash instead.
253
+ default_keyfile_hash
254
+ end
255
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
256
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
257
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
258
+ expect(options[:scope]).to eq(["http://example.com/scope"])
259
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
260
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
261
+
262
+ mocked_signet
263
+ end
264
+
265
+ creds = TestCredentials5.default
266
+ expect(creds).to be_a_kind_of(TestCredentials5)
267
+ expect(creds.client).to eq(mocked_signet)
268
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
269
+ end
270
+ end
271
+
272
+ describe "using class methods" do
273
+ it "can be subclassed to pass in other env paths" do
274
+ test_path_env_val = "/unknown/path/to/file.txt".freeze
275
+ test_json_env_val = JSON.generate default_keyfile_hash
276
+
277
+ ENV["TEST_PATH"] = test_path_env_val
278
+ ENV["TEST_JSON_VARS"] = test_json_env_val
279
+
280
+ class TestCredentials11 < Google::Auth::Credentials
281
+ self.token_credential_uri = "https://example.com/token"
282
+ self.audience = "https://example.com/audience"
283
+ self.scope = "http://example.com/scope"
284
+ self.env_vars = ["TEST_PATH", "TEST_JSON_VARS"]
285
+ end
286
+
287
+ allow(::File).to receive(:file?).with(test_path_env_val) { false }
288
+ allow(::File).to receive(:file?).with(test_json_env_val) { false }
289
+
290
+ mocked_signet = double "Signet::OAuth2::Client"
291
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
292
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
293
+ allow(mocked_signet).to receive(:client_id)
294
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
295
+ expect(options[:token_credential_uri]).to eq("https://example.com/token")
296
+ expect(options[:audience]).to eq("https://example.com/audience")
297
+ expect(options[:scope]).to eq(["http://example.com/scope"])
298
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
299
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
300
+
301
+ mocked_signet
302
+ end
303
+
304
+ creds = TestCredentials11.default
305
+ expect(creds).to be_a_kind_of(TestCredentials11)
306
+ expect(creds.client).to eq(mocked_signet)
307
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
308
+ end
309
+
310
+ it "subclasses can use PATH_ENV_VARS to get keyfile path" do
311
+ class TestCredentials12 < Google::Auth::Credentials
312
+ self.scope = "http://example.com/scope"
313
+ self.env_vars = %w[PATH_ENV_DUMMY PATH_ENV_TEST JSON_ENV_DUMMY]
314
+ self.paths = ["~/default/path/to/file.txt"]
315
+ end
316
+
317
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
318
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
319
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
320
+ allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
321
+ allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
322
+ allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { JSON.generate default_keyfile_hash }
323
+
324
+ mocked_signet = double "Signet::OAuth2::Client"
325
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
326
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
327
+ allow(mocked_signet).to receive(:client_id)
328
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
329
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
330
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
331
+ expect(options[:scope]).to eq(["http://example.com/scope"])
332
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
333
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
334
+
335
+ mocked_signet
336
+ end
337
+
338
+ creds = TestCredentials12.default
339
+ expect(creds).to be_a_kind_of(TestCredentials12)
340
+ expect(creds.client).to eq(mocked_signet)
341
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
342
+ end
343
+
344
+ it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
345
+ test_json_env_val = JSON.generate default_keyfile_hash
346
+
347
+ class TestCredentials13 < Google::Auth::Credentials
348
+ self.scope = "http://example.com/scope"
349
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY JSON_ENV_TEST]
350
+ self.paths = ["~/default/path/to/file.txt"]
351
+ end
352
+
353
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
354
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
355
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
356
+ allow(::File).to receive(:file?).with(test_json_env_val) { false }
357
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
358
+ allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
359
+
360
+ mocked_signet = double "Signet::OAuth2::Client"
361
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
362
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
363
+ allow(mocked_signet).to receive(:client_id)
364
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
365
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
366
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
367
+ expect(options[:scope]).to eq(["http://example.com/scope"])
368
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
369
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
370
+
371
+ mocked_signet
372
+ end
373
+
374
+ creds = TestCredentials13.default
375
+ expect(creds).to be_a_kind_of(TestCredentials13)
376
+ expect(creds.client).to eq(mocked_signet)
377
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
378
+ end
379
+
380
+ it "subclasses can use DEFAULT_PATHS to get keyfile path" do
381
+ class TestCredentials14 < Google::Auth::Credentials
382
+ self.scope = "http://example.com/scope"
383
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
384
+ self.paths = ["~/default/path/to/file.txt"]
385
+ end
386
+
387
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
388
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
389
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
390
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
391
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
392
+ allow(::File).to receive(:read).with("~/default/path/to/file.txt") { JSON.generate default_keyfile_hash }
393
+
394
+ mocked_signet = double "Signet::OAuth2::Client"
395
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
396
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
397
+ allow(mocked_signet).to receive(:client_id)
398
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
399
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
400
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
401
+ expect(options[:scope]).to eq(["http://example.com/scope"])
402
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
403
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
404
+
405
+ mocked_signet
406
+ end
407
+
408
+ creds = TestCredentials14.default
409
+ expect(creds).to be_a_kind_of(TestCredentials14)
410
+ expect(creds.client).to eq(mocked_signet)
411
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
412
+ end
413
+
414
+ it "subclasses that find no matches default to Google::Auth.get_application_default" do
415
+ class TestCredentials15 < Google::Auth::Credentials
416
+ self.scope = "http://example.com/scope"
417
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
418
+ self.paths = ["~/default/path/to/file.txt"]
419
+ end
420
+
421
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
422
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
423
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
424
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
425
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
426
+
427
+ mocked_signet = double "Signet::OAuth2::Client"
428
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
429
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
430
+ allow(mocked_signet).to receive(:client_id)
431
+ allow(Google::Auth).to receive(:get_application_default) do |scope|
432
+ expect(scope).to eq(TestCredentials15.scope)
433
+
434
+ # This should really be a Signet::OAuth2::Client object,
435
+ # but mocking is making that difficult, so return a valid hash instead.
436
+ default_keyfile_hash
437
+ end
438
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
439
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
440
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
441
+ expect(options[:scope]).to eq(["http://example.com/scope"])
442
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
443
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
444
+
445
+ mocked_signet
446
+ end
447
+
448
+ creds = TestCredentials15.default
449
+ expect(creds).to be_a_kind_of(TestCredentials15)
450
+ expect(creds.client).to eq(mocked_signet)
451
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
452
+ end
453
+ end
454
+
455
+ it "warns when cloud sdk credentials are used" do
456
+ mocked_signet = double "Signet::OAuth2::Client"
457
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
458
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
459
+ allow(Signet::OAuth2::Client).to receive(:new) do |_options|
460
+ mocked_signet
461
+ end
462
+ allow(mocked_signet).to receive(:client_id).and_return(Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID)
463
+ expect { Google::Auth::Credentials.new default_keyfile_hash }.to output(
464
+ Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
465
+ ).to_stderr
466
+ end
467
+ end