googleauth 0.12.0 → 0.13.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/.rubocop.yml +3 -1
- data/CHANGELOG.md +5 -0
- data/Gemfile +4 -2
- data/Rakefile +21 -0
- data/googleauth.gemspec +1 -0
- 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/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/service_account.rb +1 -1
- data/lib/googleauth/version.rb +1 -1
- data/spec/googleauth/apply_auth_examples.rb +12 -0
- 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 +12 -3
data/lib/googleauth/version.rb
CHANGED
@@ -90,6 +90,18 @@ shared_examples "apply/apply! are OK" do
|
|
90
90
|
expect(md).to eq(want)
|
91
91
|
expect(stub).to have_been_requested
|
92
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
|
93
105
|
end
|
94
106
|
|
95
107
|
describe "updater_proc" 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
|
@@ -0,0 +1,269 @@
|
|
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
|
+
describe Google::Auth::IDTokens::Verifier do
|
32
|
+
describe "verify_oidc" do
|
33
|
+
let(:oidc_token) {
|
34
|
+
"eyJhbGciOiJSUzI1NiIsImtpZCI6IjQ5MjcxMGE3ZmNkYjE1Mzk2MGNlMDFmNzYwNTIwY" \
|
35
|
+
"TMyYzg0NTVkZmYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwOi8vZXhhbXBsZS5jb20" \
|
36
|
+
"iLCJhenAiOiI1NDIzMzkzNTc2MzgtY3IwZHNlcnIyZXZnN3N2MW1lZ2hxZXU3MDMyNzRm" \
|
37
|
+
"M2hAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbCI6IjU0MjMzOTM1N" \
|
38
|
+
"zYzOC1jcjBkc2VycjJldmc3c3YxbWVnaHFldTcwMzI3NGYzaEBkZXZlbG9wZXIuZ3Nlcn" \
|
39
|
+
"ZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1OTEzNDI" \
|
40
|
+
"3NzYsImlhdCI6MTU5MTMzOTE3NiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUu" \
|
41
|
+
"Y29tIiwic3ViIjoiMTA0MzQxNDczMTMxODI1OTU3NjAzIn0.GGDE_5HoLacyqdufdxnAC" \
|
42
|
+
"rXxYySKQYAzSQ5qfGjSUriuO3uLm2-rwSPFfLzzBeflEHdVX7XRFFszpxKajuZklF4dXd" \
|
43
|
+
"0evB1u5i3QeCJ8MSZKKx6qus_ETJv4rtuPNEuyhaRcShB7BwI8RY0IZ4_EDrhYqYInrO2" \
|
44
|
+
"wQyJGYvc41JcmoKzRoNnEVydN0Qppt9bqevq_lJg-9UjJkJ2QHjPfTgMjwhLIgNptKgtR" \
|
45
|
+
"qdoRpJmleFlbuUqyPPJfAzv3Tc6h3kw88tEcI8R3n04xmHOSMwERFFQYJdQDMd2F9SSDe" \
|
46
|
+
"rh40codO_GuPZ7bEUiKq9Lkx2LH5TuhythfsMzIwJpaEA"
|
47
|
+
}
|
48
|
+
let(:oidc_jwk_body) {
|
49
|
+
<<~JWK
|
50
|
+
{
|
51
|
+
"keys": [
|
52
|
+
{
|
53
|
+
"kid": "fb8ca5b7d8d9a5c6c6788071e866c6c40f3fc1f9",
|
54
|
+
"e": "AQAB",
|
55
|
+
"alg": "RS256",
|
56
|
+
"use": "sig",
|
57
|
+
"n": "zK8PHf_6V3G5rU-viUOL1HvAYn7q--dxMoUkt7x1rSWX6fimla-lpoYAKhFTLUELkRKy_6UDzfybz0P9eItqS2UxVWYpKYmKTQ08HgUBUde4GtO_B0SkSk8iLtGh653UBBjgXmfzdfQEz_DsaWn7BMtuAhY9hpMtJye8LQlwaS8ibQrsC0j0GZM5KXRITHwfx06_T1qqC_MOZRA6iJs-J2HNlgeyFuoQVBTY6pRqGXa-qaVsSG3iU-vqNIciFquIq-xydwxLqZNksRRer5VAsSHf0eD3g2DX-cf6paSy1aM40svO9EfSvG_07MuHafEE44RFvSZZ4ubEN9U7ALSjdw",
|
58
|
+
"kty": "RSA"
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"kty": "RSA",
|
62
|
+
"kid": "492710a7fcdb153960ce01f760520a32c8455dff",
|
63
|
+
"e": "AQAB",
|
64
|
+
"alg": "RS256",
|
65
|
+
"use": "sig",
|
66
|
+
"n": "wl6TaY_3dsuLczYH_hioeQ5JjcLKLGYb--WImN9_IKMkOj49dgs25wkjsdI9XGJYhhPJLlvfjIfXH49ZGA_XKLx7fggNaBRZcj1y-I3_77tVa9N7An5JLq3HT9XVt0PNTq0mtX009z1Hva4IWZ5IhENx2rWlZOfFAXiMUqhnDc8VY3lG7vr8_VG3cw3XRKvlZQKbb6p2YIMFsUwaDGL2tVF4SkxpxIazUYfOY5lijyVugNTslOBhlEMq_43MZlkznSrbFx8ToQ2bQX4Shj-r9pLyofbo6A7K9mgWnQXGY5rQVLPYYRzUg0ThWDzwHdgxYC5MNxKyQH4RC2LPv3U0LQ"
|
67
|
+
}
|
68
|
+
]
|
69
|
+
}
|
70
|
+
JWK
|
71
|
+
}
|
72
|
+
let(:expected_aud) { "http://example.com" }
|
73
|
+
let(:expected_azp) { "542339357638-cr0dserr2evg7sv1meghqeu703274f3h@developer.gserviceaccount.com" }
|
74
|
+
let(:unexpired_test_time) { Time.at 1591339181 }
|
75
|
+
let(:expired_test_time) { unexpired_test_time + 86400 }
|
76
|
+
|
77
|
+
after do
|
78
|
+
WebMock.reset!
|
79
|
+
Google::Auth::IDTokens.forget_sources!
|
80
|
+
end
|
81
|
+
|
82
|
+
it "verifies a good token with iss, aud, and azp checks" do
|
83
|
+
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
|
84
|
+
Time.stub :now, unexpired_test_time do
|
85
|
+
Google::Auth::IDTokens.verify_oidc oidc_token, aud: expected_aud, azp: expected_azp
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it "fails to verify a bad token" do
|
90
|
+
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
|
91
|
+
Time.stub :now, unexpired_test_time do
|
92
|
+
assert_raises Google::Auth::IDTokens::SignatureError do
|
93
|
+
Google::Auth::IDTokens.verify_oidc "#{oidc_token}x"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it "fails to verify a token with the wrong aud" do
|
99
|
+
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
|
100
|
+
Time.stub :now, unexpired_test_time do
|
101
|
+
assert_raises Google::Auth::IDTokens::AudienceMismatchError do
|
102
|
+
Google::Auth::IDTokens.verify_oidc oidc_token, aud: ["hello", "world"]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "fails to verify a token with the wrong azp" do
|
108
|
+
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
|
109
|
+
Time.stub :now, unexpired_test_time do
|
110
|
+
assert_raises Google::Auth::IDTokens::AuthorizedPartyMismatchError do
|
111
|
+
Google::Auth::IDTokens.verify_oidc oidc_token, azp: "hello"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it "fails to verify a token with the wrong issuer" do
|
117
|
+
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
|
118
|
+
Time.stub :now, unexpired_test_time do
|
119
|
+
assert_raises Google::Auth::IDTokens::IssuerMismatchError do
|
120
|
+
Google::Auth::IDTokens.verify_oidc oidc_token, iss: "hello"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it "fails to verify an expired token" do
|
126
|
+
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
|
127
|
+
Time.stub :now, expired_test_time do
|
128
|
+
assert_raises Google::Auth::IDTokens::ExpiredTokenError do
|
129
|
+
Google::Auth::IDTokens.verify_oidc oidc_token
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "verify_iap" do
|
136
|
+
let(:iap_token) {
|
137
|
+
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjBvZUxjUSJ9.eyJhdWQiOiIvcH" \
|
138
|
+
"JvamVjdHMvNjUyNTYyNzc2Nzk4L2FwcHMvY2xvdWQtc2FtcGxlcy10ZXN0cy1waHAtaWFwI" \
|
139
|
+
"iwiZW1haWwiOiJkYXp1bWFAZ29vZ2xlLmNvbSIsImV4cCI6MTU5MTMzNTcyNCwiZ29vZ2xl" \
|
140
|
+
"Ijp7ImFjY2Vzc19sZXZlbHMiOlsiYWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L2FjY2V" \
|
141
|
+
"zc0xldmVscy9yZWNlbnRTZWN1cmVDb25uZWN0RGF0YSIsImFjY2Vzc1BvbGljaWVzLzUxOD" \
|
142
|
+
"U1MTI4MDkyNC9hY2Nlc3NMZXZlbHMvdGVzdE5vT3AiLCJhY2Nlc3NQb2xpY2llcy81MTg1N" \
|
143
|
+
"TEyODA5MjQvYWNjZXNzTGV2ZWxzL2V2YXBvcmF0aW9uUWFEYXRhRnVsbHlUcnVzdGVkIiwi" \
|
144
|
+
"YWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L2FjY2Vzc0xldmVscy9jYWFfZGlzYWJsZWQ" \
|
145
|
+
"iLCJhY2Nlc3NQb2xpY2llcy81MTg1NTEyODA5MjQvYWNjZXNzTGV2ZWxzL3JlY2VudE5vbk" \
|
146
|
+
"1vYmlsZVNlY3VyZUNvbm5lY3REYXRhIiwiYWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L" \
|
147
|
+
"2FjY2Vzc0xldmVscy9jb25jb3JkIiwiYWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L2Fj" \
|
148
|
+
"Y2Vzc0xldmVscy9mdWxseVRydXN0ZWRfY2FuYXJ5RGF0YSIsImFjY2Vzc1BvbGljaWVzLzU" \
|
149
|
+
"xODU1MTI4MDkyNC9hY2Nlc3NMZXZlbHMvZnVsbHlUcnVzdGVkX3Byb2REYXRhIl19LCJoZC" \
|
150
|
+
"I6Imdvb2dsZS5jb20iLCJpYXQiOjE1OTEzMzUxMjQsImlzcyI6Imh0dHBzOi8vY2xvdWQuZ" \
|
151
|
+
"29vZ2xlLmNvbS9pYXAiLCJzdWIiOiJhY2NvdW50cy5nb29nbGUuY29tOjExMzc3OTI1ODA4" \
|
152
|
+
"MTE5ODAwNDY5NCJ9.2BlagZOoonmX35rNY-KPbONiVzFAdNXKRGkX45uGFXeHryjKgv--K6" \
|
153
|
+
"siL8syeCFXzHvgmWpJk31sEt4YLxPKvQ"
|
154
|
+
}
|
155
|
+
let(:iap_jwk_body) {
|
156
|
+
<<~JWK
|
157
|
+
{
|
158
|
+
"keys" : [
|
159
|
+
{
|
160
|
+
"alg" : "ES256",
|
161
|
+
"crv" : "P-256",
|
162
|
+
"kid" : "LYyP2g",
|
163
|
+
"kty" : "EC",
|
164
|
+
"use" : "sig",
|
165
|
+
"x" : "SlXFFkJ3JxMsXyXNrqzE3ozl_0913PmNbccLLWfeQFU",
|
166
|
+
"y" : "GLSahrZfBErmMUcHP0MGaeVnJdBwquhrhQ8eP05NfCI"
|
167
|
+
},
|
168
|
+
{
|
169
|
+
"alg" : "ES256",
|
170
|
+
"crv" : "P-256",
|
171
|
+
"kid" : "mpf0DA",
|
172
|
+
"kty" : "EC",
|
173
|
+
"use" : "sig",
|
174
|
+
"x" : "fHEdeT3a6KaC1kbwov73ZwB_SiUHEyKQwUUtMCEn0aI",
|
175
|
+
"y" : "QWOjwPhInNuPlqjxLQyhveXpWqOFcQPhZ3t-koMNbZI"
|
176
|
+
},
|
177
|
+
{
|
178
|
+
"alg" : "ES256",
|
179
|
+
"crv" : "P-256",
|
180
|
+
"kid" : "b9vTLA",
|
181
|
+
"kty" : "EC",
|
182
|
+
"use" : "sig",
|
183
|
+
"x" : "qCByTAvci-jRAD7uQSEhTdOs8iA714IbcY2L--YzynI",
|
184
|
+
"y" : "WQY0uCoQyPSozWKGQ0anmFeOH5JNXiZa9i6SNqOcm7w"
|
185
|
+
},
|
186
|
+
{
|
187
|
+
"alg" : "ES256",
|
188
|
+
"crv" : "P-256",
|
189
|
+
"kid" : "0oeLcQ",
|
190
|
+
"kty" : "EC",
|
191
|
+
"use" : "sig",
|
192
|
+
"x" : "MdhRXGEoGJLtBjQEIjnYLPkeci9rXnca2TffkI0Kac0",
|
193
|
+
"y" : "9BoREHfX7g5OK8ELpA_4RcOnFCGSjfR4SGZpBo7juEY"
|
194
|
+
},
|
195
|
+
{
|
196
|
+
"alg" : "ES256",
|
197
|
+
"crv" : "P-256",
|
198
|
+
"kid" : "g5X6ig",
|
199
|
+
"kty" : "EC",
|
200
|
+
"use" : "sig",
|
201
|
+
"x" : "115LSuaFVzVROJiGfdPN1kT14Hv3P4RIjthfslZ010s",
|
202
|
+
"y" : "-FAaRtO4yvrN4uJ89xwGWOEJcSwpLmFOtb0SDJxEAuc"
|
203
|
+
}
|
204
|
+
]
|
205
|
+
}
|
206
|
+
JWK
|
207
|
+
}
|
208
|
+
let(:expected_aud) { "/projects/652562776798/apps/cloud-samples-tests-php-iap" }
|
209
|
+
let(:unexpired_test_time) { Time.at 1591335143 }
|
210
|
+
let(:expired_test_time) { unexpired_test_time + 86400 }
|
211
|
+
|
212
|
+
after do
|
213
|
+
WebMock.reset!
|
214
|
+
Google::Auth::IDTokens.forget_sources!
|
215
|
+
end
|
216
|
+
|
217
|
+
it "verifies a good token with iss and aud checks" do
|
218
|
+
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
|
219
|
+
Time.stub :now, unexpired_test_time do
|
220
|
+
Google::Auth::IDTokens.verify_iap iap_token, aud: expected_aud
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
it "fails to verify a bad token" do
|
225
|
+
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
|
226
|
+
Time.stub :now, unexpired_test_time do
|
227
|
+
assert_raises Google::Auth::IDTokens::SignatureError do
|
228
|
+
Google::Auth::IDTokens.verify_iap "#{iap_token}x"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
it "fails to verify a token with the wrong aud" do
|
234
|
+
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
|
235
|
+
Time.stub :now, unexpired_test_time do
|
236
|
+
assert_raises Google::Auth::IDTokens::AudienceMismatchError do
|
237
|
+
Google::Auth::IDTokens.verify_iap iap_token, aud: ["hello", "world"]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
it "fails to verify a token with the wrong azp" do
|
243
|
+
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
|
244
|
+
Time.stub :now, unexpired_test_time do
|
245
|
+
assert_raises Google::Auth::IDTokens::AuthorizedPartyMismatchError do
|
246
|
+
Google::Auth::IDTokens.verify_iap iap_token, azp: "hello"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
it "fails to verify a token with the wrong issuer" do
|
252
|
+
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
|
253
|
+
Time.stub :now, unexpired_test_time do
|
254
|
+
assert_raises Google::Auth::IDTokens::IssuerMismatchError do
|
255
|
+
Google::Auth::IDTokens.verify_iap iap_token, iss: "hello"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
it "fails to verify an expired token" do
|
261
|
+
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
|
262
|
+
Time.stub :now, expired_test_time do
|
263
|
+
assert_raises Google::Auth::IDTokens::ExpiredTokenError do
|
264
|
+
Google::Auth::IDTokens.verify_iap iap_token
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|