googleauth 0.1.0 → 0.16.2

Sign up to get free protection for your applications and to get access to all the features.
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