googleauth 0.8.1 → 0.16.0

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