googleauth 0.10.0 → 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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +7 -0
- data/.kokoro/continuous/linux.cfg +2 -2
- data/.kokoro/continuous/post.cfg +30 -0
- data/.kokoro/presubmit/linux.cfg +1 -1
- data/.kokoro/release.cfg +1 -1
- data/.repo-metadata.json +5 -0
- data/.rubocop.yml +5 -4
- data/CHANGELOG.md +27 -0
- data/Gemfile +5 -2
- data/{COPYING → LICENSE} +0 -0
- data/README.md +4 -5
- data/Rakefile +45 -3
- data/googleauth.gemspec +5 -3
- data/integration/helper.rb +31 -0
- data/integration/id_tokens/key_source_test.rb +74 -0
- data/lib/googleauth.rb +1 -0
- data/lib/googleauth/application_default.rb +2 -2
- data/lib/googleauth/compute_engine.rb +36 -6
- data/lib/googleauth/credentials.rb +89 -22
- data/lib/googleauth/id_tokens.rb +233 -0
- data/lib/googleauth/id_tokens/errors.rb +71 -0
- data/lib/googleauth/id_tokens/key_sources.rb +394 -0
- data/lib/googleauth/id_tokens/verifier.rb +144 -0
- data/lib/googleauth/json_key_reader.rb +6 -2
- data/lib/googleauth/service_account.rb +16 -7
- data/lib/googleauth/signet.rb +3 -2
- data/lib/googleauth/user_authorizer.rb +6 -1
- data/lib/googleauth/user_refresh.rb +1 -1
- data/lib/googleauth/version.rb +1 -1
- data/rakelib/devsite_builder.rb +45 -0
- data/rakelib/link_checker.rb +64 -0
- data/rakelib/repo_metadata.rb +59 -0
- data/spec/googleauth/apply_auth_examples.rb +28 -5
- data/spec/googleauth/compute_engine_spec.rb +48 -13
- data/spec/googleauth/credentials_spec.rb +17 -6
- data/spec/googleauth/service_account_spec.rb +23 -16
- data/spec/googleauth/signet_spec.rb +15 -7
- data/spec/googleauth/user_authorizer_spec.rb +21 -1
- data/spec/googleauth/user_refresh_spec.rb +1 -1
- data/test/helper.rb +33 -0
- data/test/id_tokens/key_sources_test.rb +240 -0
- data/test/id_tokens/verifier_test.rb +269 -0
- metadata +46 -12
@@ -36,12 +36,13 @@ require "googleauth"
|
|
36
36
|
describe Google::Auth::Credentials, :private do
|
37
37
|
let :default_keyfile_hash do
|
38
38
|
{
|
39
|
-
"private_key_id"
|
40
|
-
"private_key"
|
41
|
-
"client_email"
|
42
|
-
"client_id"
|
43
|
-
"type"
|
44
|
-
"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
|
|
@@ -118,6 +119,7 @@ describe Google::Auth::Credentials, :private do
|
|
118
119
|
expect(creds).to be_a_kind_of(TestCredentials1)
|
119
120
|
expect(creds.client).to eq(mocked_signet)
|
120
121
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
122
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
121
123
|
end
|
122
124
|
|
123
125
|
it "subclasses can use PATH_ENV_VARS to get keyfile path" do
|
@@ -153,6 +155,7 @@ describe Google::Auth::Credentials, :private do
|
|
153
155
|
expect(creds).to be_a_kind_of(TestCredentials2)
|
154
156
|
expect(creds.client).to eq(mocked_signet)
|
155
157
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
158
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
156
159
|
end
|
157
160
|
|
158
161
|
it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
|
@@ -190,6 +193,7 @@ describe Google::Auth::Credentials, :private do
|
|
190
193
|
expect(creds).to be_a_kind_of(TestCredentials3)
|
191
194
|
expect(creds.client).to eq(mocked_signet)
|
192
195
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
196
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
193
197
|
end
|
194
198
|
|
195
199
|
it "subclasses can use DEFAULT_PATHS to get keyfile path" do
|
@@ -225,6 +229,7 @@ describe Google::Auth::Credentials, :private do
|
|
225
229
|
expect(creds).to be_a_kind_of(TestCredentials4)
|
226
230
|
expect(creds.client).to eq(mocked_signet)
|
227
231
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
232
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
228
233
|
end
|
229
234
|
|
230
235
|
it "subclasses that find no matches default to Google::Auth.get_application_default" do
|
@@ -266,6 +271,7 @@ describe Google::Auth::Credentials, :private do
|
|
266
271
|
expect(creds).to be_a_kind_of(TestCredentials5)
|
267
272
|
expect(creds.client).to eq(mocked_signet)
|
268
273
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
274
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
269
275
|
end
|
270
276
|
end
|
271
277
|
|
@@ -305,6 +311,7 @@ describe Google::Auth::Credentials, :private do
|
|
305
311
|
expect(creds).to be_a_kind_of(TestCredentials11)
|
306
312
|
expect(creds.client).to eq(mocked_signet)
|
307
313
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
314
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
308
315
|
end
|
309
316
|
|
310
317
|
it "subclasses can use PATH_ENV_VARS to get keyfile path" do
|
@@ -339,6 +346,7 @@ describe Google::Auth::Credentials, :private do
|
|
339
346
|
expect(creds).to be_a_kind_of(TestCredentials12)
|
340
347
|
expect(creds.client).to eq(mocked_signet)
|
341
348
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
349
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
342
350
|
end
|
343
351
|
|
344
352
|
it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
|
@@ -375,6 +383,7 @@ describe Google::Auth::Credentials, :private do
|
|
375
383
|
expect(creds).to be_a_kind_of(TestCredentials13)
|
376
384
|
expect(creds.client).to eq(mocked_signet)
|
377
385
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
386
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
378
387
|
end
|
379
388
|
|
380
389
|
it "subclasses can use DEFAULT_PATHS to get keyfile path" do
|
@@ -409,6 +418,7 @@ describe Google::Auth::Credentials, :private do
|
|
409
418
|
expect(creds).to be_a_kind_of(TestCredentials14)
|
410
419
|
expect(creds.client).to eq(mocked_signet)
|
411
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"])
|
412
422
|
end
|
413
423
|
|
414
424
|
it "subclasses that find no matches default to Google::Auth.get_application_default" do
|
@@ -449,6 +459,7 @@ describe Google::Auth::Credentials, :private do
|
|
449
459
|
expect(creds).to be_a_kind_of(TestCredentials15)
|
450
460
|
expect(creds.client).to eq(mocked_signet)
|
451
461
|
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
|
462
|
+
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
|
452
463
|
end
|
453
464
|
end
|
454
465
|
|
@@ -112,12 +112,13 @@ describe Google::Auth::ServiceAccountCredentials do
|
|
112
112
|
let(:client_email) { "app@developer.gserviceaccount.com" }
|
113
113
|
let :cred_json do
|
114
114
|
{
|
115
|
-
private_key_id:
|
116
|
-
private_key:
|
117
|
-
client_email:
|
118
|
-
client_id:
|
119
|
-
type:
|
120
|
-
project_id:
|
115
|
+
private_key_id: "a_private_key_id",
|
116
|
+
private_key: @key.to_pem,
|
117
|
+
client_email: client_email,
|
118
|
+
client_id: "app.apps.googleusercontent.com",
|
119
|
+
type: "service_account",
|
120
|
+
project_id: "a_project_id",
|
121
|
+
quota_project_id: "b_project_id"
|
121
122
|
}
|
122
123
|
end
|
123
124
|
|
@@ -127,24 +128,28 @@ describe Google::Auth::ServiceAccountCredentials do
|
|
127
128
|
json_key_io: StringIO.new(cred_json_text),
|
128
129
|
scope: "https://www.googleapis.com/auth/userinfo.profile"
|
129
130
|
)
|
131
|
+
@id_client = ServiceAccountCredentials.make_creds(
|
132
|
+
json_key_io: StringIO.new(cred_json_text),
|
133
|
+
target_audience: "https://pubsub.googleapis.com/"
|
134
|
+
)
|
130
135
|
end
|
131
136
|
|
132
|
-
def make_auth_stubs opts
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
+
def make_auth_stubs opts
|
138
|
+
body_fields = { "token_type" => "Bearer", "expires_in" => 3600 }
|
139
|
+
body_fields["access_token"] = opts[:access_token] if opts[:access_token]
|
140
|
+
body_fields["id_token"] = opts[:id_token] if opts[:id_token]
|
141
|
+
body = MultiJson.dump body_fields
|
137
142
|
blk = proc do |request|
|
138
143
|
params = Addressable::URI.form_unencode request.body
|
139
|
-
|
140
|
-
|
141
|
-
|
144
|
+
claim, _header = JWT.decode(params.assoc("assertion").last,
|
145
|
+
@key.public_key, true,
|
146
|
+
algorithm: "RS256")
|
147
|
+
!opts[:id_token] || claim["target_audience"] == "https://pubsub.googleapis.com/"
|
142
148
|
end
|
143
149
|
stub_request(:post, "https://www.googleapis.com/oauth2/v4/token")
|
144
150
|
.with(body: hash_including(
|
145
151
|
"grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
146
|
-
),
|
147
|
-
&blk)
|
152
|
+
), &blk)
|
148
153
|
.to_return(body: body,
|
149
154
|
status: 200,
|
150
155
|
headers: { "Content-Type" => "application/json" })
|
@@ -285,6 +290,7 @@ describe Google::Auth::ServiceAccountCredentials do
|
|
285
290
|
ENV["APPDATA"] = dir
|
286
291
|
credentials = @clz.from_well_known_path @scope
|
287
292
|
expect(credentials.project_id).to eq(cred_json[:project_id])
|
293
|
+
expect(credentials.quota_project_id).to eq(cred_json[:quota_project_id])
|
288
294
|
end
|
289
295
|
end
|
290
296
|
|
@@ -476,6 +482,7 @@ describe Google::Auth::ServiceAccountJwtHeaderCredentials do
|
|
476
482
|
ENV["APPDATA"] = dir
|
477
483
|
credentials = clz.from_well_known_path @scope
|
478
484
|
expect(credentials.project_id).to eq(cred_json[:project_id])
|
485
|
+
expect(credentials.quota_project_id).to be_nil
|
479
486
|
end
|
480
487
|
end
|
481
488
|
end
|
@@ -47,18 +47,26 @@ describe Signet::OAuth2::Client do
|
|
47
47
|
audience: "https://oauth2.googleapis.com/token",
|
48
48
|
signing_key: @key
|
49
49
|
)
|
50
|
+
@id_client = Signet::OAuth2::Client.new(
|
51
|
+
token_credential_uri: "https://oauth2.googleapis.com/token",
|
52
|
+
target_audience: "https://pubsub.googleapis.com/",
|
53
|
+
issuer: "app@example.com",
|
54
|
+
audience: "https://oauth2.googleapis.com/token",
|
55
|
+
signing_key: @key
|
56
|
+
)
|
50
57
|
end
|
51
58
|
|
52
59
|
def make_auth_stubs opts
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
60
|
+
body_fields = { "token_type" => "Bearer", "expires_in" => 3600 }
|
61
|
+
body_fields["access_token"] = opts[:access_token] if opts[:access_token]
|
62
|
+
body_fields["id_token"] = opts[:id_token] if opts[:id_token]
|
63
|
+
body = MultiJson.dump body_fields
|
57
64
|
blk = proc do |request|
|
58
65
|
params = Addressable::URI.form_unencode request.body
|
59
|
-
|
60
|
-
|
61
|
-
|
66
|
+
claim, _header = JWT.decode(params.assoc("assertion").last,
|
67
|
+
@key.public_key, true,
|
68
|
+
algorithm: "RS256")
|
69
|
+
!opts[:id_token] || claim["target_audience"] == "https://pubsub.googleapis.com/"
|
62
70
|
end
|
63
71
|
with_params = { body: hash_including(
|
64
72
|
"grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
@@ -80,7 +80,7 @@ describe Google::Auth::UserAuthorizer do
|
|
80
80
|
expect(URI(uri).query).to_not match(/client_secret/)
|
81
81
|
end
|
82
82
|
|
83
|
-
it "should include the
|
83
|
+
it "should include the redirect_uri" do
|
84
84
|
expect(URI(uri).query).to match(
|
85
85
|
%r{redirect_uri=https://www.example.com/oauth/callback}
|
86
86
|
)
|
@@ -91,6 +91,25 @@ describe Google::Auth::UserAuthorizer do
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
+
context "when generating authorization URLs and callback_uri is 'postmessage'" do
|
95
|
+
let(:callback_uri) { "postmessage" }
|
96
|
+
let :authorizer do
|
97
|
+
Google::Auth::UserAuthorizer.new(client_id,
|
98
|
+
scope,
|
99
|
+
token_store,
|
100
|
+
callback_uri)
|
101
|
+
end
|
102
|
+
let :uri do
|
103
|
+
authorizer.get_authorization_url login_hint: "user1", state: "mystate"
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should include the redirect_uri 'postmessage'" do
|
107
|
+
expect(URI(uri).query).to match(
|
108
|
+
%r{redirect_uri=postmessage}
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
94
113
|
context "when generating authorization URLs with user ID & state" do
|
95
114
|
let :uri do
|
96
115
|
authorizer.get_authorization_url login_hint: "user1", state: "mystate"
|
@@ -253,6 +272,7 @@ describe Google::Auth::UserAuthorizer do
|
|
253
272
|
user_id: "user1", code: "code"
|
254
273
|
)
|
255
274
|
expect(credentials.access_token).to eq "1/abc123"
|
275
|
+
expect(credentials.redirect_uri.to_s).to eq "https://www.example.com/oauth/callback"
|
256
276
|
end
|
257
277
|
|
258
278
|
it "should not store credentials when get only requested" do
|
data/test/helper.rb
ADDED
@@ -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
|