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,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