googleauth 0.8.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +7 -0
  3. data/.github/workflows/release.yml +39 -0
  4. data/.kokoro/build.bat +9 -1
  5. data/.kokoro/continuous/linux.cfg +12 -2
  6. data/.kokoro/continuous/osx.cfg +5 -0
  7. data/.kokoro/continuous/post.cfg +30 -0
  8. data/.kokoro/continuous/windows.cfg +27 -1
  9. data/.kokoro/presubmit/linux.cfg +11 -1
  10. data/.kokoro/presubmit/osx.cfg +5 -0
  11. data/.kokoro/presubmit/windows.cfg +27 -1
  12. data/.kokoro/release.cfg +42 -1
  13. data/.kokoro/trampoline.bat +10 -0
  14. data/.repo-metadata.json +5 -0
  15. data/.rubocop.yml +8 -2
  16. data/CHANGELOG.md +94 -20
  17. data/Gemfile +7 -7
  18. data/{COPYING → LICENSE} +0 -0
  19. data/README.md +12 -15
  20. data/Rakefile +48 -5
  21. data/googleauth.gemspec +7 -3
  22. data/integration/helper.rb +31 -0
  23. data/integration/id_tokens/key_source_test.rb +74 -0
  24. data/lib/googleauth.rb +1 -0
  25. data/lib/googleauth/application_default.rb +2 -2
  26. data/lib/googleauth/compute_engine.rb +45 -20
  27. data/lib/googleauth/credentials.rb +445 -71
  28. data/lib/googleauth/credentials_loader.rb +11 -9
  29. data/lib/googleauth/iam.rb +1 -1
  30. data/lib/googleauth/id_tokens.rb +233 -0
  31. data/lib/googleauth/id_tokens/errors.rb +71 -0
  32. data/lib/googleauth/id_tokens/key_sources.rb +396 -0
  33. data/lib/googleauth/id_tokens/verifier.rb +142 -0
  34. data/lib/googleauth/json_key_reader.rb +6 -2
  35. data/lib/googleauth/scope_util.rb +1 -1
  36. data/lib/googleauth/service_account.rb +42 -23
  37. data/lib/googleauth/signet.rb +9 -6
  38. data/lib/googleauth/stores/file_token_store.rb +1 -0
  39. data/lib/googleauth/stores/redis_token_store.rb +1 -0
  40. data/lib/googleauth/user_authorizer.rb +6 -1
  41. data/lib/googleauth/user_refresh.rb +2 -2
  42. data/lib/googleauth/version.rb +1 -1
  43. data/lib/googleauth/web_user_authorizer.rb +16 -14
  44. data/rakelib/devsite_builder.rb +45 -0
  45. data/rakelib/link_checker.rb +64 -0
  46. data/rakelib/repo_metadata.rb +59 -0
  47. data/spec/googleauth/apply_auth_examples.rb +28 -5
  48. data/spec/googleauth/compute_engine_spec.rb +69 -13
  49. data/spec/googleauth/credentials_spec.rb +492 -165
  50. data/spec/googleauth/service_account_spec.rb +31 -16
  51. data/spec/googleauth/signet_spec.rb +46 -7
  52. data/spec/googleauth/user_authorizer_spec.rb +21 -1
  53. data/spec/googleauth/user_refresh_spec.rb +1 -1
  54. data/spec/googleauth/web_user_authorizer_spec.rb +6 -0
  55. data/test/helper.rb +33 -0
  56. data/test/id_tokens/key_sources_test.rb +240 -0
  57. data/test/id_tokens/verifier_test.rb +269 -0
  58. metadata +49 -13
  59. data/.kokoro/windows.sh +0 -4
@@ -0,0 +1,45 @@
1
+ require "pathname"
2
+
3
+ require_relative "repo_metadata.rb"
4
+
5
+ class DevsiteBuilder
6
+ def initialize master_dir = "."
7
+ @master_dir = Pathname.new master_dir
8
+ @output_dir = "doc"
9
+ @metadata = RepoMetadata.from_source "#{master_dir}/.repo-metadata.json"
10
+ end
11
+
12
+ def build
13
+ FileUtils.remove_dir @output_dir if Dir.exist? @output_dir
14
+ markup = "--markup markdown"
15
+
16
+ Dir.chdir @master_dir do
17
+ cmds = ["-o #{@output_dir}", markup]
18
+ cmd "yard --verbose #{cmds.join ' '}"
19
+ end
20
+ @metadata.build @master_dir + @output_dir
21
+ end
22
+
23
+ def upload
24
+ Dir.chdir @output_dir do
25
+ opts = [
26
+ "--credentials=#{ENV['KOKORO_KEYSTORE_DIR']}/73713_docuploader_service_account",
27
+ "--staging-bucket=#{ENV.fetch 'STAGING_BUCKET', 'docs-staging'}",
28
+ "--metadata-file=./docs.metadata"
29
+ ]
30
+ cmd "python3 -m docuploader upload . #{opts.join ' '}"
31
+ end
32
+ end
33
+
34
+ def publish
35
+ build
36
+ upload
37
+ end
38
+
39
+ def cmd line
40
+ puts line
41
+ output = `#{line}`
42
+ puts output
43
+ output
44
+ end
45
+ end
@@ -0,0 +1,64 @@
1
+ require "open3"
2
+
3
+ class LinkChecker
4
+ def initialize
5
+ @failed = false
6
+ end
7
+
8
+ def run
9
+ job_info
10
+ git_commit = ENV.fetch "KOKORO_GITHUB_COMMIT", "master"
11
+
12
+ markdown_files = Dir.glob "**/*.md"
13
+ broken_markdown_links = check_links markdown_files,
14
+ "https://github.com/googleapis/google-auth-library-ruby/tree/#{git_commit}",
15
+ " --skip '^(?!(\\Wruby.*google|.*google.*\\Wruby|.*cloud\\.google\\.com))'"
16
+
17
+ broken_devsite_links = check_links ["googleauth"],
18
+ "https://googleapis.dev/ruby",
19
+ "/latest/ --recurse --skip https:.*github.*"
20
+
21
+ puts_broken_links broken_markdown_links
22
+ puts_broken_links broken_devsite_links
23
+ end
24
+
25
+ def check_links location_list, base, tail
26
+ broken_links = Hash.new { |h, k| h[k] = [] }
27
+ location_list.each do |location|
28
+ out, err, st = Open3.capture3 "npx linkinator #{base}/#{location}#{tail}"
29
+ puts out
30
+ unless st.to_i.zero?
31
+ @failed = true
32
+ puts err
33
+ end
34
+ checked_links = out.split "\n"
35
+ checked_links.select! { |link| link =~ /\[\d+\]/ && !link.include?("[200]") }
36
+ unless checked_links.empty?
37
+ @failed = true
38
+ broken_links[location] += checked_links
39
+ end
40
+ end
41
+ broken_links
42
+ end
43
+
44
+ def puts_broken_links link_hash
45
+ link_hash.each do |location, links|
46
+ puts "#{location} contains the following broken links:"
47
+ links.each { |link| puts " #{link}" }
48
+ puts ""
49
+ end
50
+ end
51
+
52
+ def job_info
53
+ line_length = "Using Ruby - #{RUBY_VERSION}".length + 8
54
+ puts ""
55
+ puts "#" * line_length
56
+ puts "### Using Ruby - #{RUBY_VERSION} ###"
57
+ puts "#" * line_length
58
+ puts ""
59
+ end
60
+
61
+ def exit_status
62
+ @failed ? 1 : 0
63
+ end
64
+ end
@@ -0,0 +1,59 @@
1
+ require "json"
2
+
3
+ class RepoMetadata
4
+ attr_reader :data
5
+
6
+ def initialize data
7
+ @data = data
8
+ normalize_data!
9
+ end
10
+
11
+ def allowed_fields
12
+ [
13
+ "name", "version", "language", "distribution-name",
14
+ "product-page", "github-repository", "issue-tracker"
15
+ ]
16
+ end
17
+
18
+ def build output_directory
19
+ fields = @data.to_a.map { |kv| "--#{kv[0]} #{kv[1]}" }
20
+ Dir.chdir output_directory do
21
+ cmd "python3 -m docuploader create-metadata #{fields.join ' '}"
22
+ end
23
+ end
24
+
25
+ def normalize_data!
26
+ require_relative "../lib/googleauth/version.rb"
27
+
28
+ @data.delete_if { |k, _| !allowed_fields.include?(k) }
29
+ @data["version"] = "v#{Google::Auth::VERSION}"
30
+ end
31
+
32
+ def [] key
33
+ data[key]
34
+ end
35
+
36
+ def []= key, value
37
+ @data[key] = value
38
+ end
39
+
40
+ def cmd line
41
+ puts line
42
+ output = `#{line}`
43
+ puts output
44
+ output
45
+ end
46
+
47
+ def self.from_source source
48
+ if source.is_a? RepoMetadata
49
+ data = source.data
50
+ elsif source.is_a? Hash
51
+ data = source
52
+ elsif File.file? source
53
+ data = JSON.parse File.read(source)
54
+ else
55
+ raise "Source must be a path, hash, or RepoMetadata instance"
56
+ end
57
+ RepoMetadata.new data
58
+ end
59
+ end
@@ -45,26 +45,37 @@ shared_examples "apply/apply! are OK" do
45
45
  # auth client
46
46
  describe "#fetch_access_token" do
47
47
  let(:token) { "1/abcdef1234567890" }
48
- let :stub do
48
+ let :access_stub do
49
49
  make_auth_stubs access_token: token
50
50
  end
51
+ let :id_stub do
52
+ make_auth_stubs id_token: token
53
+ end
51
54
 
52
55
  it "should set access_token to the fetched value" do
53
- stub
56
+ access_stub
54
57
  @client.fetch_access_token!
55
58
  expect(@client.access_token).to eq(token)
56
- expect(stub).to have_been_requested
59
+ expect(access_stub).to have_been_requested
60
+ end
61
+
62
+ it "should set id_token to the fetched value" do
63
+ skip unless @id_client
64
+ id_stub
65
+ @id_client.fetch_access_token!
66
+ expect(@id_client.id_token).to eq(token)
67
+ expect(id_stub).to have_been_requested
57
68
  end
58
69
 
59
70
  it "should notify refresh listeners after updating" do
60
- stub
71
+ access_stub
61
72
  expect do |b|
62
73
  @client.on_refresh(&b)
63
74
  @client.fetch_access_token!
64
75
  end.to yield_with_args(have_attributes(
65
76
  access_token: "1/abcdef1234567890"
66
77
  ))
67
- expect(stub).to have_been_requested
78
+ expect(access_stub).to have_been_requested
68
79
  end
69
80
  end
70
81
 
@@ -79,6 +90,18 @@ shared_examples "apply/apply! are OK" do
79
90
  expect(md).to eq(want)
80
91
  expect(stub).to have_been_requested
81
92
  end
93
+
94
+ it "should update the target hash with fetched ID token" do
95
+ skip unless @id_client
96
+ token = "1/abcdef1234567890"
97
+ stub = make_auth_stubs id_token: token
98
+
99
+ md = { foo: "bar" }
100
+ @id_client.apply! md
101
+ want = { :foo => "bar", auth_key => "Bearer #{token}" }
102
+ expect(md).to eq(want)
103
+ expect(stub).to have_been_requested
104
+ end
82
105
  end
83
106
 
84
107
  describe "updater_proc" do
@@ -37,31 +37,52 @@ require "googleauth/compute_engine"
37
37
  require "spec_helper"
38
38
 
39
39
  describe Google::Auth::GCECredentials do
40
- MD_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
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
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 opts = {}
48
- access_token = opts[:access_token] || ""
49
- body = MultiJson.dump("access_token" => access_token,
50
- "token_type" => "Bearer",
51
- "expires_in" => 3600)
52
- stub_request(:get, MD_URI)
53
- .with(headers: { "Metadata-Flavor" => "Google" })
54
- .to_return(body: body,
55
- status: 200,
56
- headers: { "Content-Type" => "application/json" })
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" })
69
+ end
57
70
  end
58
71
 
59
72
  it_behaves_like "apply/apply! are OK"
60
73
 
61
74
  context "metadata is unavailable" do
62
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
82
+ end
83
+
63
84
  it "should fail if the metadata request returns a 404" do
64
- stub = stub_request(:get, MD_URI)
85
+ stub = stub_request(:get, MD_ACCESS_URI)
65
86
  .to_return(status: 404,
66
87
  headers: { "Metadata-Flavor" => "Google" })
67
88
  expect { @client.fetch_access_token! }
@@ -69,8 +90,26 @@ describe Google::Auth::GCECredentials do
69
90
  expect(stub).to have_been_requested
70
91
  end
71
92
 
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)
100
+ end
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
+
72
111
  it "should fail if the metadata request returns an unexpected code" do
73
- stub = stub_request(:get, MD_URI)
112
+ stub = stub_request(:get, MD_ACCESS_URI)
74
113
  .to_return(status: 503,
75
114
  headers: { "Metadata-Flavor" => "Google" })
76
115
  expect { @client.fetch_access_token! }
@@ -97,6 +136,7 @@ describe Google::Auth::GCECredentials do
97
136
  describe "#on_gce?" do
98
137
  it "should be true when Metadata-Flavor is Google" do
99
138
  stub = stub_request(:get, "http://169.254.169.254")
139
+ .with(headers: { "Metadata-Flavor" => "Google" })
100
140
  .to_return(status: 200,
101
141
  headers: { "Metadata-Flavor" => "Google" })
102
142
  expect(GCECredentials.on_gce?({}, true)).to eq(true)
@@ -105,6 +145,7 @@ describe Google::Auth::GCECredentials do
105
145
 
106
146
  it "should be false when Metadata-Flavor is not Google" do
107
147
  stub = stub_request(:get, "http://169.254.169.254")
148
+ .with(headers: { "Metadata-Flavor" => "Google" })
108
149
  .to_return(status: 200,
109
150
  headers: { "Metadata-Flavor" => "NotGoogle" })
110
151
  expect(GCECredentials.on_gce?({}, true)).to eq(false)
@@ -113,10 +154,25 @@ describe Google::Auth::GCECredentials do
113
154
 
114
155
  it "should be false if the response is not 200" do
115
156
  stub = stub_request(:get, "http://169.254.169.254")
157
+ .with(headers: { "Metadata-Flavor" => "Google" })
116
158
  .to_return(status: 404,
117
159
  headers: { "Metadata-Flavor" => "NotGoogle" })
118
160
  expect(GCECredentials.on_gce?({}, true)).to eq(false)
119
161
  expect(stub).to have_been_requested
120
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"
175
+ end
176
+ end
121
177
  end
122
178
  end
@@ -36,226 +36,553 @@ require "googleauth"
36
36
  describe Google::Auth::Credentials, :private do
37
37
  let :default_keyfile_hash do
38
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"
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"
45
46
  }
46
47
  end
47
48
 
48
- it "uses a default scope" do
49
+ def mock_signet
49
50
  mocked_signet = double "Signet::OAuth2::Client"
50
51
  allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
51
52
  allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
52
53
  allow(mocked_signet).to receive(:client_id)
53
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|
54
63
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
55
64
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
56
65
  expect(options[:scope]).to eq([])
57
66
  expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
58
67
  expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
59
-
60
- mocked_signet
61
68
  end
62
69
 
63
70
  Google::Auth::Credentials.new default_keyfile_hash
64
71
  end
65
72
 
66
73
  it "uses a custom scope" do
67
- mocked_signet = double "Signet::OAuth2::Client"
68
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
69
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
70
- allow(mocked_signet).to receive(:client_id)
71
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
74
+ mock_signet do |options|
72
75
  expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
73
76
  expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
74
77
  expect(options[:scope]).to eq(["http://example.com/scope"])
75
78
  expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
76
79
  expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
77
-
78
- mocked_signet
79
80
  end
80
81
 
81
82
  Google::Auth::Credentials.new default_keyfile_hash, scope: "http://example.com/scope"
82
83
  end
83
84
 
84
- it "can be subclassed to pass in other env paths" do
85
- TEST_PATH_ENV_VAR = "TEST_PATH".freeze
86
- TEST_PATH_ENV_VAL = "/unknown/path/to/file.txt".freeze
87
- TEST_JSON_ENV_VAR = "TEST_JSON_VARS".freeze
88
-
89
- ENV[TEST_PATH_ENV_VAR] = TEST_PATH_ENV_VAL
90
- ENV[TEST_JSON_ENV_VAR] = JSON.generate default_keyfile_hash
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
91
89
 
92
- class TestCredentials < Google::Auth::Credentials
93
- SCOPE = "http://example.com/scope".freeze
94
- PATH_ENV_VARS = [TEST_PATH_ENV_VAR].freeze
95
- JSON_ENV_VARS = [TEST_JSON_ENV_VAR].freeze
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"])
96
129
  end
97
130
 
98
- allow(::File).to receive(:file?).with(TEST_PATH_ENV_VAL) { false }
99
-
100
- mocked_signet = double "Signet::OAuth2::Client"
101
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
102
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
103
- allow(mocked_signet).to receive(:client_id)
104
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
105
- expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
106
- expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
107
- expect(options[:scope]).to eq(["http://example.com/scope"])
108
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
109
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
110
-
111
- mocked_signet
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"])
112
168
  end
113
169
 
114
- creds = TestCredentials.default
115
- expect(creds).to be_a_kind_of(TestCredentials)
116
- expect(creds.client).to eq(mocked_signet)
117
- expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
118
- end
119
-
120
- it "subclasses can use PATH_ENV_VARS to get keyfile path" do
121
- class TestCredentials < Google::Auth::Credentials
122
- SCOPE = "http://example.com/scope".freeze
123
- PATH_ENV_VARS = %w[PATH_ENV_DUMMY PATH_ENV_TEST].freeze
124
- JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
125
- DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
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"])
126
207
  end
127
208
 
128
- allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
129
- allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
130
- allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
131
- allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
132
- allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { JSON.generate default_keyfile_hash }
133
-
134
- mocked_signet = double "Signet::OAuth2::Client"
135
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
136
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
137
- allow(mocked_signet).to receive(:client_id)
138
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
139
- expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
140
- expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
141
- expect(options[:scope]).to eq(["http://example.com/scope"])
142
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
143
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
144
-
145
- mocked_signet
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"])
146
246
  end
147
247
 
148
- creds = TestCredentials.default
149
- expect(creds).to be_a_kind_of(TestCredentials)
150
- expect(creds.client).to eq(mocked_signet)
151
- expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
152
- end
153
-
154
- it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
155
- class TestCredentials < Google::Auth::Credentials
156
- SCOPE = "http://example.com/scope".freeze
157
- PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
158
- JSON_ENV_VARS = %w[JSON_ENV_DUMMY JSON_ENV_TEST].freeze
159
- DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
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"])
160
280
  end
161
281
 
162
- allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
163
- allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
164
- allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
165
- allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { JSON.generate default_keyfile_hash }
166
-
167
- mocked_signet = double "Signet::OAuth2::Client"
168
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
169
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
170
- allow(mocked_signet).to receive(:client_id)
171
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
172
- expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
173
- expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
174
- expect(options[:scope]).to eq(["http://example.com/scope"])
175
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
176
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
177
-
178
- mocked_signet
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")
179
305
  end
180
-
181
- creds = TestCredentials.default
182
- expect(creds).to be_a_kind_of(TestCredentials)
183
- expect(creds.client).to eq(mocked_signet)
184
- expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
185
306
  end
186
307
 
187
- it "subclasses can use DEFAULT_PATHS to get keyfile path" do
188
- class TestCredentials < Google::Auth::Credentials
189
- SCOPE = "http://example.com/scope".freeze
190
- PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
191
- JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
192
- DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
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"])
193
346
  end
194
347
 
195
- allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
196
- allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
197
- allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
198
- allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
199
- allow(::File).to receive(:read).with("~/default/path/to/file.txt") { JSON.generate default_keyfile_hash }
200
-
201
- mocked_signet = double "Signet::OAuth2::Client"
202
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
203
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
204
- allow(mocked_signet).to receive(:client_id)
205
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
206
- expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
207
- expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
208
- expect(options[:scope]).to eq(["http://example.com/scope"])
209
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
210
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
211
-
212
- mocked_signet
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"])
213
384
  end
214
385
 
215
- creds = TestCredentials.default
216
- expect(creds).to be_a_kind_of(TestCredentials)
217
- expect(creds.client).to eq(mocked_signet)
218
- expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
219
- end
220
-
221
- it "subclasses that find no matches default to Google::Auth.get_application_default" do
222
- class TestCredentials < Google::Auth::Credentials
223
- SCOPE = "http://example.com/scope".freeze
224
- PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
225
- JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
226
- DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
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"])
227
422
  end
228
423
 
229
- allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
230
- allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
231
- allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
232
- allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
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
233
461
 
234
- mocked_signet = double "Signet::OAuth2::Client"
235
- allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
236
- allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
237
- allow(mocked_signet).to receive(:client_id)
238
- allow(Google::Auth).to receive(:get_application_default) do |scope|
239
- expect(scope).to eq(TestCredentials::SCOPE)
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
240
494
 
241
- # This should really be a Signet::OAuth2::Client object,
242
- # but mocking is making that difficult, so return a valid hash instead.
243
- default_keyfile_hash
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"])
244
526
  end
245
- allow(Signet::OAuth2::Client).to receive(:new) do |options|
246
- expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
247
- expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
248
- expect(options[:scope]).to eq(["http://example.com/scope"])
249
- expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
250
- expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
251
527
 
252
- mocked_signet
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"])
253
563
  end
254
564
 
255
- creds = TestCredentials.default
256
- expect(creds).to be_a_kind_of(TestCredentials)
257
- expect(creds.client).to eq(mocked_signet)
258
- expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
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
259
586
  end
260
587
 
261
588
  it "warns when cloud sdk credentials are used" do