googleauth 0.1.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +7 -0
  3. data/.github/CONTRIBUTING.md +74 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  6. data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
  7. data/.github/renovate.json +6 -0
  8. data/.github/sync-repo-settings.yaml +18 -0
  9. data/.github/workflows/ci.yml +55 -0
  10. data/.github/workflows/release-please.yml +39 -0
  11. data/.gitignore +3 -0
  12. data/.kokoro/populate-secrets.sh +76 -0
  13. data/.kokoro/release.cfg +52 -0
  14. data/.kokoro/release.sh +18 -0
  15. data/.kokoro/trampoline_v2.sh +489 -0
  16. data/.repo-metadata.json +5 -0
  17. data/.rubocop.yml +17 -0
  18. data/.toys/.toys.rb +45 -0
  19. data/.toys/ci.rb +43 -0
  20. data/.toys/kokoro/.toys.rb +66 -0
  21. data/.toys/kokoro/publish-docs.rb +67 -0
  22. data/.toys/kokoro/publish-gem.rb +53 -0
  23. data/.toys/linkinator.rb +43 -0
  24. data/.trampolinerc +48 -0
  25. data/CHANGELOG.md +199 -0
  26. data/CODE_OF_CONDUCT.md +43 -0
  27. data/Gemfile +22 -1
  28. data/{COPYING → LICENSE} +0 -0
  29. data/README.md +140 -17
  30. data/googleauth.gemspec +28 -28
  31. data/integration/helper.rb +31 -0
  32. data/integration/id_tokens/key_source_test.rb +74 -0
  33. data/lib/googleauth.rb +7 -37
  34. data/lib/googleauth/application_default.rb +81 -0
  35. data/lib/googleauth/client_id.rb +104 -0
  36. data/lib/googleauth/compute_engine.rb +73 -26
  37. data/lib/googleauth/credentials.rb +561 -0
  38. data/lib/googleauth/credentials_loader.rb +207 -0
  39. data/lib/googleauth/default_credentials.rb +93 -0
  40. data/lib/googleauth/iam.rb +75 -0
  41. data/lib/googleauth/id_tokens.rb +233 -0
  42. data/lib/googleauth/id_tokens/errors.rb +71 -0
  43. data/lib/googleauth/id_tokens/key_sources.rb +396 -0
  44. data/lib/googleauth/id_tokens/verifier.rb +142 -0
  45. data/lib/googleauth/json_key_reader.rb +50 -0
  46. data/lib/googleauth/scope_util.rb +61 -0
  47. data/lib/googleauth/service_account.rb +177 -67
  48. data/lib/googleauth/signet.rb +69 -8
  49. data/lib/googleauth/stores/file_token_store.rb +65 -0
  50. data/lib/googleauth/stores/redis_token_store.rb +96 -0
  51. data/lib/googleauth/token_store.rb +69 -0
  52. data/lib/googleauth/user_authorizer.rb +285 -0
  53. data/lib/googleauth/user_refresh.rb +129 -0
  54. data/lib/googleauth/version.rb +1 -1
  55. data/lib/googleauth/web_user_authorizer.rb +295 -0
  56. data/spec/googleauth/apply_auth_examples.rb +96 -94
  57. data/spec/googleauth/client_id_spec.rb +160 -0
  58. data/spec/googleauth/compute_engine_spec.rb +125 -55
  59. data/spec/googleauth/credentials_spec.rb +600 -0
  60. data/spec/googleauth/get_application_default_spec.rb +232 -80
  61. data/spec/googleauth/iam_spec.rb +80 -0
  62. data/spec/googleauth/scope_util_spec.rb +77 -0
  63. data/spec/googleauth/service_account_spec.rb +422 -68
  64. data/spec/googleauth/signet_spec.rb +101 -25
  65. data/spec/googleauth/stores/file_token_store_spec.rb +57 -0
  66. data/spec/googleauth/stores/redis_token_store_spec.rb +50 -0
  67. data/spec/googleauth/stores/store_examples.rb +58 -0
  68. data/spec/googleauth/user_authorizer_spec.rb +343 -0
  69. data/spec/googleauth/user_refresh_spec.rb +359 -0
  70. data/spec/googleauth/web_user_authorizer_spec.rb +172 -0
  71. data/spec/spec_helper.rb +51 -10
  72. data/test/helper.rb +33 -0
  73. data/test/id_tokens/key_sources_test.rb +240 -0
  74. data/test/id_tokens/verifier_test.rb +269 -0
  75. metadata +114 -75
  76. data/.travis.yml +0 -18
  77. data/CONTRIBUTING.md +0 -32
  78. data/Rakefile +0 -15
@@ -0,0 +1,160 @@
1
+ # Copyright 2015, 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
+ spec_dir = File.expand_path File.join(File.dirname(__FILE__))
31
+ $LOAD_PATH.unshift spec_dir
32
+ $LOAD_PATH.uniq!
33
+
34
+ require "spec_helper"
35
+ require "fakefs/safe"
36
+ require "googleauth"
37
+
38
+ describe Google::Auth::ClientId do
39
+ shared_examples "it has a valid config" do
40
+ it "should include a valid id" do
41
+ expect(client_id.id).to eql "abc@example.com"
42
+ end
43
+
44
+ it "should include a valid secret" do
45
+ expect(client_id.secret).to eql "notasecret"
46
+ end
47
+ end
48
+
49
+ shared_examples "it can successfully load client_id" do
50
+ context "loaded from hash" do
51
+ let(:client_id) { Google::Auth::ClientId.from_hash config }
52
+
53
+ it_behaves_like "it has a valid config"
54
+ end
55
+
56
+ context "loaded from file" do
57
+ file_path = "/client_secrets.json"
58
+
59
+ let :client_id do
60
+ FakeFS do
61
+ content = MultiJson.dump config
62
+ File.write file_path, content
63
+ Google::Auth::ClientId.from_file file_path
64
+ end
65
+ end
66
+
67
+ it_behaves_like "it has a valid config"
68
+ end
69
+ end
70
+
71
+ describe "with web config" do
72
+ let :config do
73
+ {
74
+ "web" => {
75
+ "client_id" => "abc@example.com",
76
+ "client_secret" => "notasecret"
77
+ }
78
+ }
79
+ end
80
+ it_behaves_like "it can successfully load client_id"
81
+ end
82
+
83
+ describe "with installed app config" do
84
+ let :config do
85
+ {
86
+ "installed" => {
87
+ "client_id" => "abc@example.com",
88
+ "client_secret" => "notasecret"
89
+ }
90
+ }
91
+ end
92
+ it_behaves_like "it can successfully load client_id"
93
+ end
94
+
95
+ context "with missing top level property" do
96
+ let :config do
97
+ {
98
+ "notvalid" => {
99
+ "client_id" => "abc@example.com",
100
+ "client_secret" => "notasecret"
101
+ }
102
+ }
103
+ end
104
+
105
+ it "should raise error" do
106
+ expect { Google::Auth::ClientId.from_hash config }.to raise_error(
107
+ /Expected top level property/
108
+ )
109
+ end
110
+ end
111
+
112
+ context "with missing client id" do
113
+ let :config do
114
+ {
115
+ "web" => {
116
+ "client_secret" => "notasecret"
117
+ }
118
+ }
119
+ end
120
+
121
+ it "should raise error" do
122
+ expect { Google::Auth::ClientId.from_hash config }.to raise_error(
123
+ /Client id can not be nil/
124
+ )
125
+ end
126
+ end
127
+
128
+ context "with missing client secret" do
129
+ let :config do
130
+ {
131
+ "web" => {
132
+ "client_id" => "abc@example.com"
133
+ }
134
+ }
135
+ end
136
+
137
+ it "should raise error" do
138
+ expect { Google::Auth::ClientId.from_hash config }.to raise_error(
139
+ /Client secret can not be nil/
140
+ )
141
+ end
142
+ end
143
+
144
+ context "with cloud sdk credentials" do
145
+ let :config do
146
+ {
147
+ "web" => {
148
+ "client_id" => Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID,
149
+ "client_secret" => "notasecret"
150
+ }
151
+ }
152
+ end
153
+
154
+ it "should raise warning" do
155
+ expect { Google::Auth::ClientId.from_hash config }.to output(
156
+ Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
157
+ ).to_stderr
158
+ end
159
+ end
160
+ end
@@ -27,82 +27,152 @@
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 = '/computeMetadata/v1/instance/service-accounts/default/token'
40
+ MD_ACCESS_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
41
+ MD_ID_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://pubsub.googleapis.com/&format=full".freeze
41
42
  GCECredentials = Google::Auth::GCECredentials
42
43
 
43
- before(:example) do
44
+ before :example do
44
45
  @client = GCECredentials.new
46
+ @id_client = GCECredentials.new target_audience: "https://pubsub.googleapis.com/"
45
47
  end
46
48
 
47
- def make_auth_stubs(access_token: '')
48
- Faraday::Adapter::Test::Stubs.new do |stub|
49
- stub.get(MD_URI) do |env|
50
- headers = env[:request_headers]
51
- expect(headers['Metadata-Flavor']).to eq('Google')
52
- build_json_response(
53
- 'access_token' => access_token,
54
- 'token_type' => 'Bearer',
55
- 'expires_in' => 3600)
56
- end
49
+ def make_auth_stubs opts
50
+ if opts[:access_token]
51
+ body = MultiJson.dump("access_token" => opts[:access_token],
52
+ "token_type" => "Bearer",
53
+ "expires_in" => 3600)
54
+
55
+ uri = MD_ACCESS_URI
56
+ uri += "?scopes=#{Array(opts[:scope]).join ','}" if opts[:scope]
57
+
58
+ stub_request(:get, uri)
59
+ .with(headers: { "Metadata-Flavor" => "Google" })
60
+ .to_return(body: body,
61
+ status: 200,
62
+ headers: { "Content-Type" => "application/json" })
63
+ elsif opts[:id_token]
64
+ stub_request(:get, MD_ID_URI)
65
+ .with(headers: { "Metadata-Flavor" => "Google" })
66
+ .to_return(body: opts[:id_token],
67
+ status: 200,
68
+ headers: { "Content-Type" => "text/html" })
57
69
  end
58
70
  end
59
71
 
60
- it_behaves_like 'apply/apply! are OK'
72
+ it_behaves_like "apply/apply! are OK"
61
73
 
62
- describe '#on_gce?' do
63
- it 'should be true when Metadata-Flavor is Google' do
64
- stubs = Faraday::Adapter::Test::Stubs.new do |stub|
65
- stub.get('/') do |_env|
66
- [200,
67
- { 'Metadata-Flavor' => 'Google' },
68
- '']
69
- end
74
+ context "metadata is unavailable" do
75
+ describe "#fetch_access_token" do
76
+ it "should pass scopes when requesting an access token" do
77
+ scopes = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/bigtable.data"]
78
+ stub = make_auth_stubs access_token: "1/abcdef1234567890", scope: scopes
79
+ @client = GCECredentials.new(scope: scopes)
80
+ @client.fetch_access_token!
81
+ expect(stub).to have_been_requested
70
82
  end
71
- c = Faraday.new do |b|
72
- b.adapter(:test, stubs)
83
+
84
+ it "should fail if the metadata request returns a 404" do
85
+ stub = stub_request(:get, MD_ACCESS_URI)
86
+ .to_return(status: 404,
87
+ headers: { "Metadata-Flavor" => "Google" })
88
+ expect { @client.fetch_access_token! }
89
+ .to raise_error Signet::AuthorizationError
90
+ expect(stub).to have_been_requested
73
91
  end
74
- expect(GCECredentials.on_gce?(connection: c)).to eq(true)
75
- stubs.verify_stubbed_calls
76
- end
77
92
 
78
- it 'should be false when Metadata-Flavor is not Google' do
79
- stubs = Faraday::Adapter::Test::Stubs.new do |stub|
80
- stub.get('/') do |_env|
81
- [200,
82
- { 'Metadata-Flavor' => 'NotGoogle' },
83
- '']
84
- end
93
+ it "should fail if the metadata request returns a 403" do
94
+ stub = stub_request(:get, MD_ACCESS_URI)
95
+ .to_return(status: 403,
96
+ headers: { "Metadata-Flavor" => "Google" })
97
+ expect { @client.fetch_access_token! }
98
+ .to raise_error Signet::AuthorizationError
99
+ expect(stub).to have_been_requested.times(6)
85
100
  end
86
- c = Faraday.new do |b|
87
- b.adapter(:test, stubs)
101
+
102
+ it "should fail if the metadata request returns a 500" do
103
+ stub = stub_request(:get, MD_ACCESS_URI)
104
+ .to_return(status: 500,
105
+ headers: { "Metadata-Flavor" => "Google" })
106
+ expect { @client.fetch_access_token! }
107
+ .to raise_error Signet::AuthorizationError
108
+ expect(stub).to have_been_requested.times(6)
109
+ end
110
+
111
+ it "should fail if the metadata request returns an unexpected code" do
112
+ stub = stub_request(:get, MD_ACCESS_URI)
113
+ .to_return(status: 503,
114
+ headers: { "Metadata-Flavor" => "Google" })
115
+ expect { @client.fetch_access_token! }
116
+ .to raise_error Signet::AuthorizationError
117
+ expect(stub).to have_been_requested
118
+ end
119
+
120
+ it "should fail with Signet::AuthorizationError if request times out" do
121
+ allow_any_instance_of(Faraday::Connection).to receive(:get)
122
+ .and_raise(Faraday::TimeoutError)
123
+ expect { @client.fetch_access_token! }
124
+ .to raise_error Signet::AuthorizationError
88
125
  end
89
- expect(GCECredentials.on_gce?(connection: c)).to eq(false)
90
- stubs.verify_stubbed_calls
91
- end
92
126
 
93
- it 'should be false if the response is not 200' do
94
- stubs = Faraday::Adapter::Test::Stubs.new do |stub|
95
- stub.get('/') do |_env|
96
- [404,
97
- { 'Metadata-Flavor' => 'Google' },
98
- '']
99
- end
127
+ it "should fail with Signet::AuthorizationError if request fails" do
128
+ allow_any_instance_of(Faraday::Connection).to receive(:get)
129
+ .and_raise(Faraday::ConnectionFailed, nil)
130
+ expect { @client.fetch_access_token! }
131
+ .to raise_error Signet::AuthorizationError
100
132
  end
101
- c = Faraday.new do |b|
102
- b.adapter(:test, stubs)
133
+ end
134
+ end
135
+
136
+ describe "#on_gce?" do
137
+ it "should be true when Metadata-Flavor is Google" do
138
+ stub = stub_request(:get, "http://169.254.169.254")
139
+ .with(headers: { "Metadata-Flavor" => "Google" })
140
+ .to_return(status: 200,
141
+ headers: { "Metadata-Flavor" => "Google" })
142
+ expect(GCECredentials.on_gce?({}, true)).to eq(true)
143
+ expect(stub).to have_been_requested
144
+ end
145
+
146
+ it "should be false when Metadata-Flavor is not Google" do
147
+ stub = stub_request(:get, "http://169.254.169.254")
148
+ .with(headers: { "Metadata-Flavor" => "Google" })
149
+ .to_return(status: 200,
150
+ headers: { "Metadata-Flavor" => "NotGoogle" })
151
+ expect(GCECredentials.on_gce?({}, true)).to eq(false)
152
+ expect(stub).to have_been_requested
153
+ end
154
+
155
+ it "should be false if the response is not 200" do
156
+ stub = stub_request(:get, "http://169.254.169.254")
157
+ .with(headers: { "Metadata-Flavor" => "Google" })
158
+ .to_return(status: 404,
159
+ headers: { "Metadata-Flavor" => "NotGoogle" })
160
+ expect(GCECredentials.on_gce?({}, true)).to eq(false)
161
+ expect(stub).to have_been_requested
162
+ end
163
+
164
+ it "should honor GCE_METADATA_HOST environment variable" do
165
+ ENV["GCE_METADATA_HOST"] = "mymetadata.example.com"
166
+ begin
167
+ stub = stub_request(:get, "http://mymetadata.example.com")
168
+ .with(headers: { "Metadata-Flavor" => "Google" })
169
+ .to_return(status: 200,
170
+ headers: { "Metadata-Flavor" => "Google" })
171
+ expect(GCECredentials.on_gce?({}, true)).to eq(true)
172
+ expect(stub).to have_been_requested
173
+ ensure
174
+ ENV.delete "GCE_METADATA_HOST"
103
175
  end
104
- expect(GCECredentials.on_gce?(connection: c)).to eq(false)
105
- stubs.verify_stubbed_calls
106
176
  end
107
177
  end
108
178
  end
@@ -0,0 +1,600 @@
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
+ "quota_project_id" => "b_project_id"
46
+ }
47
+ end
48
+
49
+ def mock_signet
50
+ mocked_signet = double "Signet::OAuth2::Client"
51
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
52
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
53
+ allow(mocked_signet).to receive(:client_id)
54
+ allow(Signet::OAuth2::Client).to receive(:new) do |options|
55
+ yield options if block_given?
56
+ mocked_signet
57
+ end
58
+ mocked_signet
59
+ end
60
+
61
+ it "uses a default scope" do
62
+ mock_signet do |options|
63
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
64
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
65
+ expect(options[:scope]).to eq([])
66
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
67
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
68
+ end
69
+
70
+ Google::Auth::Credentials.new default_keyfile_hash
71
+ end
72
+
73
+ it "uses a custom scope" do
74
+ mock_signet do |options|
75
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
76
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
77
+ expect(options[:scope]).to eq(["http://example.com/scope"])
78
+ expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
79
+ expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
80
+ end
81
+
82
+ Google::Auth::Credentials.new default_keyfile_hash, scope: "http://example.com/scope"
83
+ end
84
+
85
+ it "uses empty paths and env_vars by default" do
86
+ expect(Google::Auth::Credentials.paths).to eq([])
87
+ expect(Google::Auth::Credentials.env_vars).to eq([])
88
+ end
89
+
90
+ describe "using CONSTANTS" do
91
+ it "can be subclassed to pass in other env paths" do
92
+ test_path_env_val = "/unknown/path/to/file.txt".freeze
93
+ test_json_env_val = JSON.generate default_keyfile_hash
94
+
95
+ ENV["TEST_PATH"] = test_path_env_val
96
+ ENV["TEST_JSON_VARS"] = test_json_env_val
97
+
98
+ class TestCredentials1 < Google::Auth::Credentials
99
+ TOKEN_CREDENTIAL_URI = "https://example.com/token".freeze
100
+ AUDIENCE = "https://example.com/audience".freeze
101
+ SCOPE = "http://example.com/scope".freeze
102
+ PATH_ENV_VARS = ["TEST_PATH"].freeze
103
+ JSON_ENV_VARS = ["TEST_JSON_VARS"].freeze
104
+ end
105
+
106
+ allow(::File).to receive(:file?).with(test_path_env_val) { false }
107
+ allow(::File).to receive(:file?).with(test_json_env_val) { false }
108
+
109
+ mocked_signet = mock_signet
110
+
111
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
112
+ expect(options[:token_credential_uri]).to eq("https://example.com/token")
113
+ expect(options[:audience]).to eq("https://example.com/audience")
114
+ expect(options[:scope]).to eq(["http://example.com/scope"])
115
+ expect(options[:enable_self_signed_jwt]).to eq(true)
116
+ expect(options[:target_audience]).to be_nil
117
+ expect(options[:json_key_io].read).to eq(test_json_env_val)
118
+
119
+ # This should really be a Signet::OAuth2::Client object,
120
+ # but mocking is making that difficult, so return a valid hash instead.
121
+ default_keyfile_hash
122
+ end
123
+
124
+ creds = TestCredentials1.default enable_self_signed_jwt: true
125
+ expect(creds).to be_a_kind_of(TestCredentials1)
126
+ expect(creds.client).to eq(mocked_signet)
127
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
128
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
129
+ end
130
+
131
+ it "subclasses can use PATH_ENV_VARS to get keyfile path" do
132
+ class TestCredentials2 < Google::Auth::Credentials
133
+ SCOPE = "http://example.com/scope".freeze
134
+ PATH_ENV_VARS = %w[PATH_ENV_DUMMY PATH_ENV_TEST].freeze
135
+ JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
136
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
137
+ end
138
+
139
+ json_content = JSON.generate default_keyfile_hash
140
+
141
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
142
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
143
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
144
+ allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
145
+ allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
146
+ allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { json_content }
147
+
148
+ mocked_signet = mock_signet
149
+
150
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
151
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
152
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
153
+ expect(options[:scope]).to eq(["http://example.com/scope"])
154
+ expect(options[:enable_self_signed_jwt]).to be_nil
155
+ expect(options[:target_audience]).to be_nil
156
+ expect(options[:json_key_io].read).to eq(json_content)
157
+
158
+ # This should really be a Signet::OAuth2::Client object,
159
+ # but mocking is making that difficult, so return a valid hash instead.
160
+ default_keyfile_hash
161
+ end
162
+
163
+ creds = TestCredentials2.default
164
+ expect(creds).to be_a_kind_of(TestCredentials2)
165
+ expect(creds.client).to eq(mocked_signet)
166
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
167
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
168
+ end
169
+
170
+ it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
171
+ test_json_env_val = JSON.generate default_keyfile_hash
172
+
173
+ class TestCredentials3 < Google::Auth::Credentials
174
+ SCOPE = "http://example.com/scope".freeze
175
+ PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
176
+ JSON_ENV_VARS = %w[JSON_ENV_DUMMY JSON_ENV_TEST].freeze
177
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
178
+ end
179
+
180
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
181
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
182
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
183
+ allow(::File).to receive(:file?).with(test_json_env_val) { false }
184
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
185
+ allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
186
+
187
+ mocked_signet = mock_signet
188
+
189
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
190
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
191
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
192
+ expect(options[:scope]).to eq(["http://example.com/scope"])
193
+ expect(options[:enable_self_signed_jwt]).to be_nil
194
+ expect(options[:target_audience]).to be_nil
195
+ expect(options[:json_key_io].read).to eq(test_json_env_val)
196
+
197
+ # This should really be a Signet::OAuth2::Client object,
198
+ # but mocking is making that difficult, so return a valid hash instead.
199
+ default_keyfile_hash
200
+ end
201
+
202
+ creds = TestCredentials3.default
203
+ expect(creds).to be_a_kind_of(TestCredentials3)
204
+ expect(creds.client).to eq(mocked_signet)
205
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
206
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
207
+ end
208
+
209
+ it "subclasses can use DEFAULT_PATHS to get keyfile path" do
210
+ class TestCredentials4 < Google::Auth::Credentials
211
+ SCOPE = "http://example.com/scope".freeze
212
+ PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
213
+ JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
214
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
215
+ end
216
+
217
+ json_content = JSON.generate default_keyfile_hash
218
+
219
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
220
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
221
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
222
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
223
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
224
+ allow(::File).to receive(:read).with("~/default/path/to/file.txt") { json_content }
225
+
226
+ mocked_signet = mock_signet
227
+
228
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
229
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
230
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
231
+ expect(options[:scope]).to eq(["http://example.com/scope"])
232
+ expect(options[:enable_self_signed_jwt]).to be_nil
233
+ expect(options[:target_audience]).to be_nil
234
+ expect(options[:json_key_io].read).to eq(json_content)
235
+
236
+ # This should really be a Signet::OAuth2::Client object,
237
+ # but mocking is making that difficult, so return a valid hash instead.
238
+ default_keyfile_hash
239
+ end
240
+
241
+ creds = TestCredentials4.default
242
+ expect(creds).to be_a_kind_of(TestCredentials4)
243
+ expect(creds.client).to eq(mocked_signet)
244
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
245
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
246
+ end
247
+
248
+ it "subclasses that find no matches default to Google::Auth.get_application_default" do
249
+ class TestCredentials5 < Google::Auth::Credentials
250
+ SCOPE = "http://example.com/scope".freeze
251
+ PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
252
+ JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
253
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
254
+ end
255
+
256
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
257
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
258
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
259
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
260
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
261
+
262
+ mocked_signet = mock_signet
263
+
264
+ allow(Google::Auth).to receive(:get_application_default) do |scope, options|
265
+ expect(scope).to eq([TestCredentials5::SCOPE])
266
+ expect(options[:enable_self_signed_jwt]).to be_nil
267
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
268
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
269
+
270
+ # This should really be a Signet::OAuth2::Client object,
271
+ # but mocking is making that difficult, so return a valid hash instead.
272
+ default_keyfile_hash
273
+ end
274
+
275
+ creds = TestCredentials5.default
276
+ expect(creds).to be_a_kind_of(TestCredentials5)
277
+ expect(creds.client).to eq(mocked_signet)
278
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
279
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
280
+ end
281
+
282
+ it "can be subclassed to pass in other env paths" do
283
+ class TestCredentials6 < Google::Auth::Credentials
284
+ TOKEN_CREDENTIAL_URI = "https://example.com/token".freeze
285
+ AUDIENCE = "https://example.com/audience".freeze
286
+ SCOPE = "http://example.com/scope".freeze
287
+ PATH_ENV_VARS = ["TEST_PATH"].freeze
288
+ JSON_ENV_VARS = ["TEST_JSON_VARS"].freeze
289
+ DEFAULT_PATHS = ["~/default/path/to/file.txt"]
290
+ end
291
+
292
+ class TestCredentials7 < TestCredentials6
293
+ end
294
+
295
+ expect(TestCredentials7.token_credential_uri).to eq("https://example.com/token")
296
+ expect(TestCredentials7.audience).to eq("https://example.com/audience")
297
+ expect(TestCredentials7.scope).to eq(["http://example.com/scope"])
298
+ expect(TestCredentials7.env_vars).to eq(["TEST_PATH", "TEST_JSON_VARS"])
299
+ expect(TestCredentials7.paths).to eq(["~/default/path/to/file.txt"])
300
+
301
+ TestCredentials7::TOKEN_CREDENTIAL_URI = "https://example.com/token2"
302
+ expect(TestCredentials7.token_credential_uri).to eq("https://example.com/token2")
303
+ TestCredentials7::AUDIENCE = nil
304
+ expect(TestCredentials7.audience).to eq("https://example.com/audience")
305
+ end
306
+ end
307
+
308
+ describe "using class methods" do
309
+ it "can be subclassed to pass in other env paths" do
310
+ test_path_env_val = "/unknown/path/to/file.txt".freeze
311
+ test_json_env_val = JSON.generate default_keyfile_hash
312
+
313
+ ENV["TEST_PATH"] = test_path_env_val
314
+ ENV["TEST_JSON_VARS"] = test_json_env_val
315
+
316
+ class TestCredentials11 < Google::Auth::Credentials
317
+ self.token_credential_uri = "https://example.com/token"
318
+ self.audience = "https://example.com/audience"
319
+ self.scope = "http://example.com/scope"
320
+ self.env_vars = ["TEST_PATH", "TEST_JSON_VARS"]
321
+ end
322
+
323
+ allow(::File).to receive(:file?).with(test_path_env_val) { false }
324
+ allow(::File).to receive(:file?).with(test_json_env_val) { false }
325
+
326
+ mocked_signet = mock_signet
327
+
328
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
329
+ expect(options[:token_credential_uri]).to eq("https://example.com/token")
330
+ expect(options[:audience]).to eq("https://example.com/audience")
331
+ expect(options[:scope]).to eq(["http://example.com/scope"])
332
+ expect(options[:enable_self_signed_jwt]).to be_nil
333
+ expect(options[:target_audience]).to be_nil
334
+ expect(options[:json_key_io].read).to eq(test_json_env_val)
335
+
336
+ # This should really be a Signet::OAuth2::Client object,
337
+ # but mocking is making that difficult, so return a valid hash instead.
338
+ default_keyfile_hash
339
+ end
340
+
341
+ creds = TestCredentials11.default
342
+ expect(creds).to be_a_kind_of(TestCredentials11)
343
+ expect(creds.client).to eq(mocked_signet)
344
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
345
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
346
+ end
347
+
348
+ it "subclasses can use PATH_ENV_VARS to get keyfile path" do
349
+ class TestCredentials12 < Google::Auth::Credentials
350
+ self.scope = "http://example.com/scope"
351
+ self.env_vars = %w[PATH_ENV_DUMMY PATH_ENV_TEST JSON_ENV_DUMMY]
352
+ self.paths = ["~/default/path/to/file.txt"]
353
+ end
354
+
355
+ json_content = JSON.generate default_keyfile_hash
356
+
357
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
358
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
359
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
360
+ allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
361
+ allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
362
+ allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { json_content }
363
+
364
+ mocked_signet = mock_signet
365
+
366
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
367
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
368
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
369
+ expect(options[:scope]).to eq(["http://example.com/scope"])
370
+ expect(options[:enable_self_signed_jwt]).to be_nil
371
+ expect(options[:target_audience]).to be_nil
372
+ expect(options[:json_key_io].read).to eq(json_content)
373
+
374
+ # This should really be a Signet::OAuth2::Client object,
375
+ # but mocking is making that difficult, so return a valid hash instead.
376
+ default_keyfile_hash
377
+ end
378
+
379
+ creds = TestCredentials12.default
380
+ expect(creds).to be_a_kind_of(TestCredentials12)
381
+ expect(creds.client).to eq(mocked_signet)
382
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
383
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
384
+ end
385
+
386
+ it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
387
+ test_json_env_val = JSON.generate default_keyfile_hash
388
+
389
+ class TestCredentials13 < Google::Auth::Credentials
390
+ self.scope = "http://example.com/scope"
391
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY JSON_ENV_TEST]
392
+ self.paths = ["~/default/path/to/file.txt"]
393
+ end
394
+
395
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
396
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
397
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
398
+ allow(::File).to receive(:file?).with(test_json_env_val) { false }
399
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
400
+ allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
401
+
402
+ mocked_signet = mock_signet
403
+
404
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
405
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
406
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
407
+ expect(options[:scope]).to eq(["http://example.com/scope"])
408
+ expect(options[:enable_self_signed_jwt]).to be_nil
409
+ expect(options[:target_audience]).to be_nil
410
+ expect(options[:json_key_io].read).to eq(test_json_env_val)
411
+
412
+ # This should really be a Signet::OAuth2::Client object,
413
+ # but mocking is making that difficult, so return a valid hash instead.
414
+ default_keyfile_hash
415
+ end
416
+
417
+ creds = TestCredentials13.default
418
+ expect(creds).to be_a_kind_of(TestCredentials13)
419
+ expect(creds.client).to eq(mocked_signet)
420
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
421
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
422
+ end
423
+
424
+ it "subclasses can use DEFAULT_PATHS to get keyfile path" do
425
+ class TestCredentials14 < Google::Auth::Credentials
426
+ self.scope = "http://example.com/scope"
427
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
428
+ self.paths = ["~/default/path/to/file.txt"]
429
+ end
430
+
431
+ json_content = JSON.generate default_keyfile_hash
432
+
433
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
434
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
435
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
436
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
437
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
438
+ allow(::File).to receive(:read).with("~/default/path/to/file.txt") { json_content }
439
+
440
+ mocked_signet = mock_signet
441
+
442
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds) do |options|
443
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
444
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
445
+ expect(options[:scope]).to eq(["http://example.com/scope"])
446
+ expect(options[:enable_self_signed_jwt]).to be_nil
447
+ expect(options[:target_audience]).to be_nil
448
+ expect(options[:json_key_io].read).to eq(json_content)
449
+
450
+ # This should really be a Signet::OAuth2::Client object,
451
+ # but mocking is making that difficult, so return a valid hash instead.
452
+ default_keyfile_hash
453
+ end
454
+
455
+ creds = TestCredentials14.default
456
+ expect(creds).to be_a_kind_of(TestCredentials14)
457
+ expect(creds.client).to eq(mocked_signet)
458
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
459
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
460
+ end
461
+
462
+ it "subclasses that find no matches default to Google::Auth.get_application_default with self-signed jwt enabled" do
463
+ class TestCredentials15 < Google::Auth::Credentials
464
+ self.scope = "http://example.com/scope"
465
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
466
+ self.paths = ["~/default/path/to/file.txt"]
467
+ end
468
+
469
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
470
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
471
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
472
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
473
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
474
+
475
+ mocked_signet = mock_signet
476
+
477
+ allow(Google::Auth).to receive(:get_application_default) do |scope, options|
478
+ expect(scope).to eq(TestCredentials15.scope)
479
+ expect(options[:enable_self_signed_jwt]).to eq(true)
480
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
481
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
482
+
483
+ # This should really be a Signet::OAuth2::Client object,
484
+ # but mocking is making that difficult, so return a valid hash instead.
485
+ default_keyfile_hash
486
+ end
487
+
488
+ creds = TestCredentials15.default enable_self_signed_jwt: true
489
+ expect(creds).to be_a_kind_of(TestCredentials15)
490
+ expect(creds.client).to eq(mocked_signet)
491
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
492
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
493
+ end
494
+
495
+ it "subclasses that find no matches default to Google::Auth.get_application_default with self-signed jwt disabled" do
496
+ class TestCredentials16 < Google::Auth::Credentials
497
+ self.scope = "http://example.com/scope"
498
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
499
+ self.paths = ["~/default/path/to/file.txt"]
500
+ end
501
+
502
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
503
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
504
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
505
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
506
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
507
+
508
+ mocked_signet = mock_signet
509
+
510
+ allow(Google::Auth).to receive(:get_application_default) do |scope, options|
511
+ expect(scope).to eq(TestCredentials16.scope)
512
+ expect(options[:enable_self_signed_jwt]).to be_nil
513
+ expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
514
+ expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
515
+
516
+ # This should really be a Signet::OAuth2::Client object,
517
+ # but mocking is making that difficult, so return a valid hash instead.
518
+ default_keyfile_hash
519
+ end
520
+
521
+ creds = TestCredentials16.default
522
+ expect(creds).to be_a_kind_of(TestCredentials16)
523
+ expect(creds.client).to eq(mocked_signet)
524
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
525
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
526
+ end
527
+
528
+ it "subclasses that find no matches default to Google::Auth.get_application_default with custom values" do
529
+ scope2 = "http://example.com/scope2"
530
+
531
+ class TestCredentials17 < Google::Auth::Credentials
532
+ self.scope = "http://example.com/scope"
533
+ self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
534
+ self.paths = ["~/default/path/to/file.txt"]
535
+ self.token_credential_uri = "https://example.com/token2"
536
+ self.audience = "https://example.com/token3"
537
+ end
538
+
539
+ allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
540
+ allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
541
+ allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
542
+ allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
543
+ allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
544
+
545
+ mocked_signet = mock_signet
546
+
547
+ allow(Google::Auth).to receive(:get_application_default) do |scope, options|
548
+ expect(scope).to eq(scope2)
549
+ expect(options[:enable_self_signed_jwt]).to eq(false)
550
+ expect(options[:token_credential_uri]).to eq("https://example.com/token2")
551
+ expect(options[:audience]).to eq("https://example.com/token3")
552
+
553
+ # This should really be a Signet::OAuth2::Client object,
554
+ # but mocking is making that difficult, so return a valid hash instead.
555
+ default_keyfile_hash
556
+ end
557
+
558
+ creds = TestCredentials17.default scope: scope2, enable_self_signed_jwt: true
559
+ expect(creds).to be_a_kind_of(TestCredentials17)
560
+ expect(creds.client).to eq(mocked_signet)
561
+ expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
562
+ expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
563
+ end
564
+
565
+ it "subclasses delegate up the class hierarchy" do
566
+ class TestCredentials18 < Google::Auth::Credentials
567
+ self.scope = "http://example.com/scope"
568
+ self.target_audience = "https://example.com/target_audience"
569
+ self.env_vars = ["TEST_PATH", "TEST_JSON_VARS"]
570
+ self.paths = ["~/default/path/to/file.txt"]
571
+ end
572
+
573
+ class TestCredentials19 < TestCredentials18
574
+ end
575
+
576
+ expect(TestCredentials19.scope).to eq(["http://example.com/scope"])
577
+ expect(TestCredentials19.target_audience).to eq("https://example.com/target_audience")
578
+ expect(TestCredentials19.env_vars).to eq(["TEST_PATH", "TEST_JSON_VARS"])
579
+ expect(TestCredentials19.paths).to eq(["~/default/path/to/file.txt"])
580
+
581
+ TestCredentials19.token_credential_uri = "https://example.com/token2"
582
+ expect(TestCredentials19.token_credential_uri).to eq("https://example.com/token2")
583
+ TestCredentials19.token_credential_uri = nil
584
+ expect(TestCredentials19.token_credential_uri).to eq("https://oauth2.googleapis.com/token")
585
+ end
586
+ end
587
+
588
+ it "warns when cloud sdk credentials are used" do
589
+ mocked_signet = double "Signet::OAuth2::Client"
590
+ allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
591
+ allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
592
+ allow(Signet::OAuth2::Client).to receive(:new) do |_options|
593
+ mocked_signet
594
+ end
595
+ allow(mocked_signet).to receive(:client_id).and_return(Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID)
596
+ expect { Google::Auth::Credentials.new default_keyfile_hash }.to output(
597
+ Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
598
+ ).to_stderr
599
+ end
600
+ end