googleauth 0.5.1 → 0.14.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 (80) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +7 -0
  3. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +5 -4
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  6. data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
  7. data/.kokoro/build.bat +16 -0
  8. data/.kokoro/build.sh +4 -0
  9. data/.kokoro/continuous/common.cfg +24 -0
  10. data/.kokoro/continuous/linux.cfg +25 -0
  11. data/.kokoro/continuous/osx.cfg +8 -0
  12. data/.kokoro/continuous/post.cfg +30 -0
  13. data/.kokoro/continuous/windows.cfg +29 -0
  14. data/.kokoro/osx.sh +4 -0
  15. data/.kokoro/presubmit/common.cfg +24 -0
  16. data/.kokoro/presubmit/linux.cfg +24 -0
  17. data/.kokoro/presubmit/osx.cfg +8 -0
  18. data/.kokoro/presubmit/windows.cfg +29 -0
  19. data/.kokoro/release.cfg +94 -0
  20. data/.kokoro/trampoline.bat +10 -0
  21. data/.kokoro/trampoline.sh +4 -0
  22. data/.repo-metadata.json +5 -0
  23. data/.rubocop.yml +19 -1
  24. data/CHANGELOG.md +112 -19
  25. data/CODE_OF_CONDUCT.md +43 -0
  26. data/Gemfile +19 -13
  27. data/{COPYING → LICENSE} +0 -0
  28. data/README.md +58 -18
  29. data/Rakefile +126 -9
  30. data/googleauth.gemspec +28 -25
  31. data/integration/helper.rb +31 -0
  32. data/integration/id_tokens/key_source_test.rb +74 -0
  33. data/lib/googleauth.rb +7 -96
  34. data/lib/googleauth/application_default.rb +81 -0
  35. data/lib/googleauth/client_id.rb +21 -19
  36. data/lib/googleauth/compute_engine.rb +70 -43
  37. data/lib/googleauth/credentials.rb +442 -0
  38. data/lib/googleauth/credentials_loader.rb +117 -43
  39. data/lib/googleauth/default_credentials.rb +93 -0
  40. data/lib/googleauth/iam.rb +11 -11
  41. data/lib/googleauth/id_tokens.rb +233 -0
  42. data/lib/googleauth/id_tokens/errors.rb +71 -0
  43. data/lib/googleauth/id_tokens/key_sources.rb +394 -0
  44. data/lib/googleauth/id_tokens/verifier.rb +144 -0
  45. data/lib/googleauth/json_key_reader.rb +50 -0
  46. data/lib/googleauth/scope_util.rb +12 -12
  47. data/lib/googleauth/service_account.rb +74 -63
  48. data/lib/googleauth/signet.rb +55 -13
  49. data/lib/googleauth/stores/file_token_store.rb +8 -8
  50. data/lib/googleauth/stores/redis_token_store.rb +22 -22
  51. data/lib/googleauth/token_store.rb +6 -6
  52. data/lib/googleauth/user_authorizer.rb +80 -68
  53. data/lib/googleauth/user_refresh.rb +44 -35
  54. data/lib/googleauth/version.rb +1 -1
  55. data/lib/googleauth/web_user_authorizer.rb +77 -68
  56. data/rakelib/devsite_builder.rb +45 -0
  57. data/rakelib/link_checker.rb +64 -0
  58. data/rakelib/repo_metadata.rb +59 -0
  59. data/spec/googleauth/apply_auth_examples.rb +74 -50
  60. data/spec/googleauth/client_id_spec.rb +75 -55
  61. data/spec/googleauth/compute_engine_spec.rb +98 -46
  62. data/spec/googleauth/credentials_spec.rb +478 -0
  63. data/spec/googleauth/get_application_default_spec.rb +149 -111
  64. data/spec/googleauth/iam_spec.rb +25 -25
  65. data/spec/googleauth/scope_util_spec.rb +26 -24
  66. data/spec/googleauth/service_account_spec.rb +269 -144
  67. data/spec/googleauth/signet_spec.rb +101 -30
  68. data/spec/googleauth/stores/file_token_store_spec.rb +12 -13
  69. data/spec/googleauth/stores/redis_token_store_spec.rb +11 -11
  70. data/spec/googleauth/stores/store_examples.rb +16 -16
  71. data/spec/googleauth/user_authorizer_spec.rb +153 -124
  72. data/spec/googleauth/user_refresh_spec.rb +186 -121
  73. data/spec/googleauth/web_user_authorizer_spec.rb +82 -69
  74. data/spec/spec_helper.rb +21 -19
  75. data/test/helper.rb +33 -0
  76. data/test/id_tokens/key_sources_test.rb +240 -0
  77. data/test/id_tokens/verifier_test.rb +269 -0
  78. metadata +87 -34
  79. data/.rubocop_todo.yml +0 -32
  80. data/.travis.yml +0 -37
@@ -27,133 +27,146 @@
27
27
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
28
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
 
30
- spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
31
- $LOAD_PATH.unshift(spec_dir)
30
+ spec_dir = File.expand_path File.join(File.dirname(__FILE__))
31
+ $LOAD_PATH.unshift spec_dir
32
32
  $LOAD_PATH.uniq!
33
33
 
34
- require 'googleauth'
35
- require 'googleauth/web_user_authorizer'
36
- require 'uri'
37
- require 'multi_json'
38
- require 'spec_helper'
39
- require 'rack'
34
+ require "googleauth"
35
+ require "googleauth/web_user_authorizer"
36
+ require "uri"
37
+ require "multi_json"
38
+ require "spec_helper"
39
+ require "rack"
40
40
 
41
41
  describe Google::Auth::WebUserAuthorizer do
42
42
  include TestHelpers
43
43
 
44
- let(:client_id) { Google::Auth::ClientId.new('testclient', 'notasecret') }
45
- let(:scope) { %w(email profile) }
44
+ let(:client_id) { Google::Auth::ClientId.new "testclient", "notasecret" }
45
+ let(:scope) { %w[email profile] }
46
46
  let(:token_store) { DummyTokenStore.new }
47
- let(:authorizer) do
48
- Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store)
47
+ let :authorizer do
48
+ Google::Auth::WebUserAuthorizer.new client_id, scope, token_store
49
49
  end
50
50
 
51
- describe '#get_authorization_url' do
52
- let(:env) do
51
+ describe "#get_authorization_url" do
52
+ let :env do
53
53
  Rack::MockRequest.env_for(
54
- 'http://example.com:8080/test',
55
- 'REMOTE_ADDR' => '10.10.10.10')
54
+ "http://example.com:8080/test",
55
+ "REMOTE_ADDR" => "10.10.10.10"
56
+ )
56
57
  end
57
- let(:request) { Rack::Request.new(env) }
58
- it 'should include current url in state' do
59
- url = authorizer.get_authorization_url(request: request)
58
+ let(:request) { Rack::Request.new env }
59
+ it "should include current url in state" do
60
+ url = authorizer.get_authorization_url request: request
60
61
  expect(url).to match(
61
- %r{%22current_uri%22:%22http://example.com:8080/test%22})
62
+ %r{%22current_uri%22:%22http://example.com:8080/test%22}
63
+ )
62
64
  end
63
65
 
64
- it 'should include request forgery token in state' do
65
- expect(SecureRandom).to receive(:base64).and_return('aGVsbG8=')
66
- url = authorizer.get_authorization_url(request: request)
66
+ it "should allow adding custom state key-value pairs" do
67
+ url = authorizer.get_authorization_url request: request, state: { james: "bond", kind: 1 }
68
+ expect(url).to match(%r{%22james%22:%22bond%22})
69
+ expect(url).to match(%r{%22kind%22:1})
70
+ end
71
+
72
+ it "should include request forgery token in state" do
73
+ expect(SecureRandom).to receive(:base64).and_return("aGVsbG8=")
74
+ url = authorizer.get_authorization_url request: request
67
75
  expect(url).to match(/%22session_id%22:%22aGVsbG8=%22/)
68
76
  end
69
77
 
70
- it 'should include request forgery token in session' do
71
- expect(SecureRandom).to receive(:base64).and_return('aGVsbG8=')
72
- authorizer.get_authorization_url(request: request)
73
- expect(request.session['g-xsrf-token']).to eq 'aGVsbG8='
78
+ it "should include request forgery token in session" do
79
+ expect(SecureRandom).to receive(:base64).and_return("aGVsbG8=")
80
+ authorizer.get_authorization_url request: request
81
+ expect(request.session["g-xsrf-token"]).to eq "aGVsbG8="
74
82
  end
75
83
 
76
- it 'should resolve callback against base URL' do
77
- url = authorizer.get_authorization_url(request: request)
84
+ it "should resolve callback against base URL" do
85
+ url = authorizer.get_authorization_url request: request
78
86
  expect(url).to match(
79
- %r{redirect_uri=http://example.com:8080/oauth2callback})
87
+ %r{redirect_uri=http://example.com:8080/oauth2callback}
88
+ )
80
89
  end
81
90
 
82
- it 'should allow overriding the current URL' do
91
+ it "should allow overriding the current URL" do
83
92
  url = authorizer.get_authorization_url(
84
- request: request,
85
- redirect_to: '/foo')
93
+ request: request,
94
+ redirect_to: "/foo"
95
+ )
86
96
  expect(url).to match %r{%22current_uri%22:%22/foo%22}
87
97
  end
88
98
 
89
- it 'should pass through login hint' do
99
+ it "should pass through login hint" do
90
100
  url = authorizer.get_authorization_url(
91
- request: request,
92
- login_hint: 'user@example.com')
101
+ request: request,
102
+ login_hint: "user@example.com"
103
+ )
93
104
  expect(url).to match(/login_hint=user@example.com/)
94
105
  end
95
106
  end
96
107
 
97
- shared_examples 'handles callback' do
98
- let(:token_json) do
99
- MultiJson.dump('access_token' => '1/abc123',
100
- 'token_type' => 'Bearer',
101
- 'expires_in' => 3600)
108
+ shared_examples "handles callback" do
109
+ let :token_json do
110
+ MultiJson.dump("access_token" => "1/abc123",
111
+ "token_type" => "Bearer",
112
+ "expires_in" => 3600)
102
113
  end
103
114
 
104
- before(:example) do
105
- stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token')
106
- .to_return(body: token_json,
107
- status: 200,
108
- headers: { 'Content-Type' => 'application/json' })
115
+ before :example do
116
+ stub_request(:post, "https://oauth2.googleapis.com/token")
117
+ .to_return(body: token_json,
118
+ status: 200,
119
+ headers: { "Content-Type" => "application/json" })
109
120
  end
110
121
 
111
- let(:env) do
122
+ let :env do
112
123
  Rack::MockRequest.env_for(
113
- 'http://example.com:8080/oauth2callback?code=authcode&'\
114
- 'state=%7B%22current_uri%22%3A%22%2Ffoo%22%2C%22'\
115
- 'session_id%22%3A%22abc%22%7D',
116
- 'REMOTE_ADDR' => '10.10.10.10')
124
+ "http://example.com:8080/oauth2callback?code=authcode&"\
125
+ "state=%7B%22current_uri%22%3A%22%2Ffoo%22%2C%22"\
126
+ "session_id%22%3A%22abc%22%7D",
127
+ "REMOTE_ADDR" => "10.10.10.10"
128
+ )
117
129
  end
118
- let(:request) { Rack::Request.new(env) }
130
+ let(:request) { Rack::Request.new env }
119
131
 
120
- before(:example) do
121
- request.session['g-xsrf-token'] = 'abc'
132
+ before :example do
133
+ request.session["g-xsrf-token"] = "abc"
122
134
  end
123
135
 
124
- it 'should return credentials when valid code present' do
136
+ it "should return credentials when valid code present" do
125
137
  expect(credentials).to be_instance_of(
126
- Google::Auth::UserRefreshCredentials)
138
+ Google::Auth::UserRefreshCredentials
139
+ )
127
140
  end
128
141
 
129
- it 'should return next URL to redirect to' do
130
- expect(next_url).to eq '/foo'
142
+ it "should return next URL to redirect to" do
143
+ expect(next_url).to eq "/foo"
131
144
  end
132
145
 
133
- it 'should fail if xrsf token in session and does not match request' do
134
- request.session['g-xsrf-token'] = '123'
146
+ it "should fail if xrsf token in session and does not match request" do
147
+ request.session["g-xsrf-token"] = "123"
135
148
  expect { credentials }.to raise_error(Signet::AuthorizationError)
136
149
  end
137
150
  end
138
151
 
139
- describe '#handle_auth_callback' do
140
- let(:result) { authorizer.handle_auth_callback('user1', request) }
152
+ describe "#handle_auth_callback" do
153
+ let(:result) { authorizer.handle_auth_callback "user1", request }
141
154
  let(:credentials) { result[0] }
142
155
  let(:next_url) { result[1] }
143
156
 
144
- it_behaves_like 'handles callback'
157
+ it_behaves_like "handles callback"
145
158
  end
146
159
 
147
- describe '#handle_auth_callback_deferred and #get_credentials' do
148
- let(:next_url) do
149
- Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request)
160
+ describe "#handle_auth_callback_deferred and #get_credentials" do
161
+ let :next_url do
162
+ Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred request
150
163
  end
151
164
 
152
- let(:credentials) do
165
+ let :credentials do
153
166
  next_url
154
- authorizer.get_credentials('user1', request)
167
+ authorizer.get_credentials "user1", request
155
168
  end
156
169
 
157
- it_behaves_like 'handles callback'
170
+ it_behaves_like "handles callback"
158
171
  end
159
172
  end
@@ -27,17 +27,17 @@
27
27
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
28
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
 
30
- spec_dir = File.expand_path(File.dirname(__FILE__))
31
- root_dir = File.expand_path(File.join(spec_dir, '..'))
32
- lib_dir = File.expand_path(File.join(root_dir, 'lib'))
30
+ spec_dir = __dir__
31
+ root_dir = File.expand_path File.join(spec_dir, "..")
32
+ lib_dir = File.expand_path File.join(root_dir, "lib")
33
33
 
34
- $LOAD_PATH.unshift(spec_dir)
35
- $LOAD_PATH.unshift(lib_dir)
34
+ $LOAD_PATH.unshift spec_dir
35
+ $LOAD_PATH.unshift lib_dir
36
36
  $LOAD_PATH.uniq!
37
37
 
38
38
  # set up coverage
39
- require 'simplecov'
40
- require 'coveralls'
39
+ require "simplecov"
40
+ require "coveralls"
41
41
 
42
42
  SimpleCov.formatters = [
43
43
  Coveralls::SimpleCov::Formatter,
@@ -45,18 +45,18 @@ SimpleCov.formatters = [
45
45
  ]
46
46
  SimpleCov.start
47
47
 
48
- require 'faraday'
49
- require 'rspec'
50
- require 'logging'
51
- require 'rspec/logging_helper'
52
- require 'webmock/rspec'
53
- require 'multi_json'
48
+ require "faraday"
49
+ require "rspec"
50
+ require "logging"
51
+ require "rspec/logging_helper"
52
+ require "webmock/rspec"
53
+ require "multi_json"
54
54
 
55
55
  # Preload adapter to work around Rubinius error with FakeFS
56
- MultiJson.use(:json_gem)
56
+ MultiJson.use :json_gem
57
57
 
58
58
  # Allow Faraday to support test stubs
59
- Faraday::Adapter.load_middleware(:test)
59
+ Faraday::Adapter.load_middleware :test
60
60
 
61
61
  # Configure RSpec to capture log messages for each test. The output from the
62
62
  # logs will be stored in the @log_output variable. It is a StringIO instance.
@@ -64,6 +64,8 @@ RSpec.configure do |config|
64
64
  include RSpec::LoggingHelper
65
65
  config.capture_log_messages
66
66
  config.include WebMock::API
67
+ config.filter_run focus: true
68
+ config.run_all_when_everything_filtered = true
67
69
  end
68
70
 
69
71
  module TestHelpers
@@ -76,15 +78,15 @@ class DummyTokenStore
76
78
  @tokens = {}
77
79
  end
78
80
 
79
- def load(id)
81
+ def load id
80
82
  @tokens[id]
81
83
  end
82
84
 
83
- def store(id, token)
85
+ def store id, token
84
86
  @tokens[id] = token
85
87
  end
86
88
 
87
- def delete(id)
88
- @tokens.delete(id)
89
+ def delete id
90
+ @tokens.delete id
89
91
  end
90
92
  end
@@ -0,0 +1,33 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are
5
+ # met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright
8
+ # notice, this list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above
10
+ # copyright notice, this list of conditions and the following disclaimer
11
+ # in the documentation and/or other materials provided with the
12
+ # distribution.
13
+ # * Neither the name of Google Inc. nor the names of its
14
+ # contributors may be used to endorse or promote products derived from
15
+ # this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ require "minitest/autorun"
30
+ require "minitest/focus"
31
+ require "webmock/minitest"
32
+
33
+ require "googleauth"
@@ -0,0 +1,240 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are
5
+ # met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright
8
+ # notice, this list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above
10
+ # copyright notice, this list of conditions and the following disclaimer
11
+ # in the documentation and/or other materials provided with the
12
+ # distribution.
13
+ # * Neither the name of Google Inc. nor the names of its
14
+ # contributors may be used to endorse or promote products derived from
15
+ # this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ require "helper"
30
+
31
+ require "openssl"
32
+
33
+ describe Google::Auth::IDTokens do
34
+ describe "StaticKeySource" do
35
+ let(:key1) { Google::Auth::IDTokens::KeyInfo.new id: "1234", key: :key1, algorithm: "RS256" }
36
+ let(:key2) { Google::Auth::IDTokens::KeyInfo.new id: "5678", key: :key2, algorithm: "ES256" }
37
+ let(:keys) { [key1, key2] }
38
+ let(:source) { Google::Auth::IDTokens::StaticKeySource.new keys }
39
+
40
+ it "returns a static set of keys" do
41
+ assert_equal keys, source.current_keys
42
+ end
43
+
44
+ it "does not change on refresh" do
45
+ assert_equal keys, source.refresh_keys
46
+ end
47
+ end
48
+
49
+ describe "HttpKeySource" do
50
+ let(:certs_uri) { "https://example.com/my-certs" }
51
+ let(:certs_body) { "{}" }
52
+
53
+ it "raises an error when failing to parse json from the site" do
54
+ source = Google::Auth::IDTokens::HttpKeySource.new certs_uri
55
+ stub = stub_request(:get, certs_uri).to_return(body: "whoops")
56
+ error = assert_raises Google::Auth::IDTokens::KeySourceError do
57
+ source.refresh_keys
58
+ end
59
+ assert_equal "Unable to parse JSON", error.message
60
+ assert_requested stub
61
+ end
62
+
63
+ it "downloads data but gets no keys" do
64
+ source = Google::Auth::IDTokens::HttpKeySource.new certs_uri
65
+ stub = stub_request(:get, certs_uri).to_return(body: certs_body)
66
+ keys = source.refresh_keys
67
+ assert_empty keys
68
+ assert_requested stub
69
+ end
70
+ end
71
+
72
+ describe "X509CertHttpKeySource" do
73
+ let(:certs_uri) { "https://example.com/my-certs" }
74
+ let(:key1) { OpenSSL::PKey::RSA.new 2048 }
75
+ let(:key2) { OpenSSL::PKey::RSA.new 2048 }
76
+ let(:cert1) { generate_cert key1 }
77
+ let(:cert2) { generate_cert key2 }
78
+ let(:id1) { "1234" }
79
+ let(:id2) { "5678" }
80
+ let(:certs_body) { JSON.dump({ id1 => cert1.to_pem, id2 => cert2.to_pem }) }
81
+
82
+ after do
83
+ WebMock.reset!
84
+ end
85
+
86
+ def generate_cert key
87
+ cert = OpenSSL::X509::Certificate.new
88
+ cert.subject = cert.issuer = OpenSSL::X509::Name.parse "/C=BE/O=Test/OU=Test/CN=Test"
89
+ cert.not_before = Time.now
90
+ cert.not_after = Time.now + 365 * 24 * 60 * 60
91
+ cert.public_key = key.public_key
92
+ cert.serial = 0x0
93
+ cert.version = 2
94
+ cert.sign key, OpenSSL::Digest::SHA1.new
95
+ cert
96
+ end
97
+
98
+ it "raises an error when failing to reach the site" do
99
+ source = Google::Auth::IDTokens::X509CertHttpKeySource.new certs_uri
100
+ stub = stub_request(:get, certs_uri).to_return(body: "whoops", status: 404)
101
+ error = assert_raises Google::Auth::IDTokens::KeySourceError do
102
+ source.refresh_keys
103
+ end
104
+ assert_equal "Unable to retrieve data from #{certs_uri}", error.message
105
+ assert_requested stub
106
+ end
107
+
108
+ it "raises an error when failing to parse json from the site" do
109
+ source = Google::Auth::IDTokens::X509CertHttpKeySource.new certs_uri
110
+ stub = stub_request(:get, certs_uri).to_return(body: "whoops")
111
+ error = assert_raises Google::Auth::IDTokens::KeySourceError do
112
+ source.refresh_keys
113
+ end
114
+ assert_equal "Unable to parse JSON", error.message
115
+ assert_requested stub
116
+ end
117
+
118
+ it "raises an error when failing to parse x509 from the site" do
119
+ source = Google::Auth::IDTokens::X509CertHttpKeySource.new certs_uri
120
+ stub = stub_request(:get, certs_uri).to_return(body: '{"hi": "whoops"}')
121
+ error = assert_raises Google::Auth::IDTokens::KeySourceError do
122
+ source.refresh_keys
123
+ end
124
+ assert_equal "Unable to parse X509 certificates", error.message
125
+ assert_requested stub
126
+ end
127
+
128
+ it "gets the right certificates" do
129
+ source = Google::Auth::IDTokens::X509CertHttpKeySource.new certs_uri
130
+ stub = stub_request(:get, certs_uri).to_return(body: certs_body)
131
+ keys = source.refresh_keys
132
+ assert_equal id1, keys[0].id
133
+ assert_equal id2, keys[1].id
134
+ assert_equal key1.public_key.to_pem, keys[0].key.to_pem
135
+ assert_equal key2.public_key.to_pem, keys[1].key.to_pem
136
+ assert_equal "RS256", keys[0].algorithm
137
+ assert_equal "RS256", keys[1].algorithm
138
+ assert_requested stub
139
+ end
140
+ end
141
+
142
+ describe "JwkHttpKeySource" do
143
+ let(:jwk_uri) { "https://example.com/my-jwk" }
144
+ let(:id1) { "fb8ca5b7d8d9a5c6c6788071e866c6c40f3fc1f9" }
145
+ let(:id2) { "LYyP2g" }
146
+ let(:jwk1) {
147
+ {
148
+ alg: "RS256",
149
+ e: "AQAB",
150
+ kid: id1,
151
+ kty: "RSA",
152
+ n: "zK8PHf_6V3G5rU-viUOL1HvAYn7q--dxMoUkt7x1rSWX6fimla-lpoYAKhFTLU" \
153
+ "ELkRKy_6UDzfybz0P9eItqS2UxVWYpKYmKTQ08HgUBUde4GtO_B0SkSk8iLtGh" \
154
+ "653UBBjgXmfzdfQEz_DsaWn7BMtuAhY9hpMtJye8LQlwaS8ibQrsC0j0GZM5KX" \
155
+ "RITHwfx06_T1qqC_MOZRA6iJs-J2HNlgeyFuoQVBTY6pRqGXa-qaVsSG3iU-vq" \
156
+ "NIciFquIq-xydwxLqZNksRRer5VAsSHf0eD3g2DX-cf6paSy1aM40svO9EfSvG" \
157
+ "_07MuHafEE44RFvSZZ4ubEN9U7ALSjdw",
158
+ use: "sig"
159
+ }
160
+ }
161
+ let(:jwk2) {
162
+ {
163
+ alg: "ES256",
164
+ crv: "P-256",
165
+ kid: id2,
166
+ kty: "EC",
167
+ use: "sig",
168
+ x: "SlXFFkJ3JxMsXyXNrqzE3ozl_0913PmNbccLLWfeQFU",
169
+ y: "GLSahrZfBErmMUcHP0MGaeVnJdBwquhrhQ8eP05NfCI"
170
+ }
171
+ }
172
+ let(:bad_type_jwk) {
173
+ {
174
+ alg: "RS256",
175
+ kid: "hello",
176
+ kty: "blah",
177
+ use: "sig"
178
+ }
179
+ }
180
+ let(:jwk_body) { JSON.dump({ keys: [jwk1, jwk2] }) }
181
+ let(:bad_type_body) { JSON.dump({ keys: [bad_type_jwk] }) }
182
+
183
+ after do
184
+ WebMock.reset!
185
+ end
186
+
187
+ it "raises an error when failing to reach the site" do
188
+ source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
189
+ stub = stub_request(:get, jwk_uri).to_return(body: "whoops", status: 404)
190
+ error = assert_raises Google::Auth::IDTokens::KeySourceError do
191
+ source.refresh_keys
192
+ end
193
+ assert_equal "Unable to retrieve data from #{jwk_uri}", error.message
194
+ assert_requested stub
195
+ end
196
+
197
+ it "raises an error when failing to parse json from the site" do
198
+ source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
199
+ stub = stub_request(:get, jwk_uri).to_return(body: "whoops")
200
+ error = assert_raises Google::Auth::IDTokens::KeySourceError do
201
+ source.refresh_keys
202
+ end
203
+ assert_equal "Unable to parse JSON", error.message
204
+ assert_requested stub
205
+ end
206
+
207
+ it "raises an error when the json structure is malformed" do
208
+ source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
209
+ stub = stub_request(:get, jwk_uri).to_return(body: '{"hi": "whoops"}')
210
+ error = assert_raises Google::Auth::IDTokens::KeySourceError do
211
+ source.refresh_keys
212
+ end
213
+ assert_equal "No keys found in jwk set", error.message
214
+ assert_requested stub
215
+ end
216
+
217
+ it "raises an error when an unrecognized key type is encountered" do
218
+ source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
219
+ stub = stub_request(:get, jwk_uri).to_return(body: bad_type_body)
220
+ error = assert_raises Google::Auth::IDTokens::KeySourceError do
221
+ source.refresh_keys
222
+ end
223
+ assert_equal "Cannot use key type blah", error.message
224
+ assert_requested stub
225
+ end
226
+
227
+ it "gets the right keys" do
228
+ source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
229
+ stub = stub_request(:get, jwk_uri).to_return(body: jwk_body)
230
+ keys = source.refresh_keys
231
+ assert_equal id1, keys[0].id
232
+ assert_equal id2, keys[1].id
233
+ assert_kind_of OpenSSL::PKey::RSA, keys[0].key
234
+ assert_kind_of OpenSSL::PKey::EC, keys[1].key
235
+ assert_equal "RS256", keys[0].algorithm
236
+ assert_equal "ES256", keys[1].algorithm
237
+ assert_requested stub
238
+ end
239
+ end
240
+ end