omniauth_openid_connect 0.4.0 → 0.6.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/workflows/main.yml +34 -2
- data/.rubocop.yml +1 -4
- data/CHANGELOG.md +13 -1
- data/Gemfile +6 -0
- data/README.md +31 -29
- data/Rakefile +2 -0
- data/lib/omniauth/openid_connect/version.rb +1 -1
- data/lib/omniauth/strategies/openid_connect.rb +132 -21
- data/omniauth_openid_connect.gemspec +2 -3
- data/test/lib/omniauth/strategies/openid_connect_test.rb +330 -66
- data/test/strategy_test_case.rb +47 -3
- data/test/test_helper.rb +17 -7
- metadata +24 -44
- data/.github/config/rubocop_linter_action.yml +0 -59
- data/.github/workflows/rubocop.yml +0 -22
- data/test/fixtures/id_token.txt +0 -1
- data/test/fixtures/jwks.json +0 -8
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../../../test_helper'
|
2
4
|
|
3
5
|
module OmniAuth
|
4
6
|
module Strategies
|
5
|
-
class OpenIDConnectTest < StrategyTestCase
|
7
|
+
class OpenIDConnectTest < StrategyTestCase # rubocop:disable Metrics/ClassLength
|
6
8
|
def test_client_options_defaults
|
7
9
|
assert_equal 'https', strategy.options.client_options.scheme
|
8
10
|
assert_equal 443, strategy.options.client_options.port
|
@@ -11,7 +13,7 @@ module OmniAuth
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def test_request_phase
|
14
|
-
expected_redirect =
|
16
|
+
expected_redirect = %r{^https://example\.com/authorize\?client_id=1234&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}$}
|
15
17
|
strategy.options.issuer = 'example.com'
|
16
18
|
strategy.options.client_options.host = 'example.com'
|
17
19
|
strategy.expects(:redirect).with(regexp_matches(expected_redirect))
|
@@ -19,7 +21,7 @@ module OmniAuth
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def test_logout_phase_with_discovery
|
22
|
-
expected_redirect = %r{^https
|
24
|
+
expected_redirect = %r{^https://example\.com/logout$}
|
23
25
|
strategy.options.client_options.host = 'example.com'
|
24
26
|
strategy.options.discovery = true
|
25
27
|
|
@@ -78,7 +80,7 @@ module OmniAuth
|
|
78
80
|
end
|
79
81
|
|
80
82
|
def test_request_phase_with_params
|
81
|
-
expected_redirect =
|
83
|
+
expected_redirect = %r{^https://example\.com/authorize\?claims_locales=es&client_id=1234&login_hint=john.doe%40example.com&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}&ui_locales=en$}
|
82
84
|
strategy.options.issuer = 'example.com'
|
83
85
|
strategy.options.client_options.host = 'example.com'
|
84
86
|
request.stubs(:params).returns('login_hint' => 'john.doe@example.com', 'ui_locales' => 'en', 'claims_locales' => 'es')
|
@@ -88,7 +90,7 @@ module OmniAuth
|
|
88
90
|
end
|
89
91
|
|
90
92
|
def test_request_phase_with_discovery
|
91
|
-
expected_redirect =
|
93
|
+
expected_redirect = %r{^https://example\.com/authorization\?client_id=1234&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}$}
|
92
94
|
strategy.options.client_options.host = 'example.com'
|
93
95
|
strategy.options.discovery = true
|
94
96
|
|
@@ -115,7 +117,7 @@ module OmniAuth
|
|
115
117
|
end
|
116
118
|
|
117
119
|
def test_request_phase_with_response_mode
|
118
|
-
expected_redirect =
|
120
|
+
expected_redirect = %r{^https://example\.com/authorize\?client_id=1234&nonce=\w{32}&response_mode=form_post&response_type=id_token&scope=openid&state=\w{32}$}
|
119
121
|
strategy.options.issuer = 'example.com'
|
120
122
|
strategy.options.response_mode = 'form_post'
|
121
123
|
strategy.options.response_type = 'id_token'
|
@@ -126,7 +128,7 @@ module OmniAuth
|
|
126
128
|
end
|
127
129
|
|
128
130
|
def test_request_phase_with_response_mode_symbol
|
129
|
-
expected_redirect =
|
131
|
+
expected_redirect = %r{^https://example\.com/authorize\?client_id=1234&nonce=\w{32}&response_mode=form_post&response_type=id_token&scope=openid&state=\w{32}$}
|
130
132
|
strategy.options.issuer = 'example.com'
|
131
133
|
strategy.options.response_mode = 'form_post'
|
132
134
|
strategy.options.response_type = :id_token
|
@@ -139,25 +141,26 @@ module OmniAuth
|
|
139
141
|
def test_option_acr_values
|
140
142
|
strategy.options.client_options[:host] = 'foobar.com'
|
141
143
|
|
142
|
-
|
144
|
+
refute_match(/acr_values=/, strategy.authorize_uri, 'URI must not contain acr_values')
|
143
145
|
|
144
146
|
strategy.options.acr_values = 'urn:some:acr:values:value'
|
145
|
-
|
147
|
+
assert_match(/acr_values=/, strategy.authorize_uri, 'URI must contain acr_values')
|
146
148
|
end
|
147
149
|
|
148
150
|
def test_option_custom_attributes
|
149
151
|
strategy.options.client_options[:host] = 'foobar.com'
|
150
|
-
strategy.options.extra_authorize_params = {resource: 'xyz'}
|
152
|
+
strategy.options.extra_authorize_params = { resource: 'xyz' }
|
151
153
|
|
152
154
|
assert(strategy.authorize_uri =~ /resource=xyz/, 'URI must contain custom params')
|
153
155
|
end
|
154
156
|
|
155
157
|
def test_request_phase_with_allowed_params
|
156
158
|
strategy.options.issuer = 'example.com'
|
157
|
-
strategy.options.allow_authorize_params = [
|
158
|
-
strategy.options.extra_authorize_params = {resource: 'xyz'}
|
159
|
+
strategy.options.allow_authorize_params = %i[name logo resource]
|
160
|
+
strategy.options.extra_authorize_params = { resource: 'xyz' }
|
159
161
|
strategy.options.client_options.host = 'example.com'
|
160
|
-
request.stubs(:params).returns('name' => 'example', 'logo' => 'example_logo', 'resource' => 'abc',
|
162
|
+
request.stubs(:params).returns('name' => 'example', 'logo' => 'example_logo', 'resource' => 'abc',
|
163
|
+
'not_allowed' => 'filter_me')
|
161
164
|
|
162
165
|
assert(strategy.authorize_uri =~ /resource=xyz/, 'URI must contain fixed param resource')
|
163
166
|
assert(strategy.authorize_uri =~ /name=example/, 'URI must contain dynamic param name')
|
@@ -175,16 +178,15 @@ module OmniAuth
|
|
175
178
|
assert_equal user_info.sub, strategy.uid
|
176
179
|
end
|
177
180
|
|
178
|
-
def test_callback_phase(
|
181
|
+
def test_callback_phase(_session = {}, _params = {}) # rubocop:disable Metrics/AbcSize
|
179
182
|
code = SecureRandom.hex(16)
|
180
183
|
state = SecureRandom.hex(16)
|
181
|
-
nonce = SecureRandom.hex(16)
|
182
184
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
183
185
|
request.stubs(:path).returns('')
|
184
186
|
|
185
187
|
strategy.options.issuer = 'example.com'
|
186
188
|
strategy.options.client_signing_alg = :RS256
|
187
|
-
strategy.options.client_jwk_signing_key =
|
189
|
+
strategy.options.client_jwk_signing_key = jwks.to_s
|
188
190
|
strategy.options.response_type = 'code'
|
189
191
|
|
190
192
|
strategy.unstub(:user_info)
|
@@ -193,7 +195,7 @@ module OmniAuth
|
|
193
195
|
access_token.stubs(:refresh_token)
|
194
196
|
access_token.stubs(:expires_in)
|
195
197
|
access_token.stubs(:scope)
|
196
|
-
access_token.stubs(:id_token).returns(
|
198
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
197
199
|
client.expects(:access_token!).at_least_once.returns(access_token)
|
198
200
|
access_token.expects(:userinfo!).returns(user_info)
|
199
201
|
|
@@ -208,15 +210,13 @@ module OmniAuth
|
|
208
210
|
end
|
209
211
|
|
210
212
|
def test_callback_phase_with_id_token
|
211
|
-
code = SecureRandom.hex(16)
|
212
213
|
state = SecureRandom.hex(16)
|
213
|
-
|
214
|
-
request.stubs(:params).returns('id_token' => code, 'state' => state)
|
214
|
+
request.stubs(:params).returns('id_token' => jwt.to_s, 'state' => state)
|
215
215
|
request.stubs(:path).returns('')
|
216
216
|
|
217
217
|
strategy.options.issuer = 'example.com'
|
218
218
|
strategy.options.client_signing_alg = :RS256
|
219
|
-
strategy.options.client_jwk_signing_key =
|
219
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
220
220
|
strategy.options.response_type = 'id_token'
|
221
221
|
|
222
222
|
strategy.unstub(:user_info)
|
@@ -225,7 +225,7 @@ module OmniAuth
|
|
225
225
|
access_token.stubs(:refresh_token)
|
226
226
|
access_token.stubs(:expires_in)
|
227
227
|
access_token.stubs(:scope)
|
228
|
-
access_token.stubs(:id_token).returns(
|
228
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
229
229
|
|
230
230
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
231
231
|
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
@@ -237,13 +237,174 @@ module OmniAuth
|
|
237
237
|
strategy.callback_phase
|
238
238
|
end
|
239
239
|
|
240
|
-
def
|
240
|
+
def test_callback_phase_with_id_token_and_param_provided_nonce # rubocop:disable Metrics/AbcSize
|
241
241
|
code = SecureRandom.hex(16)
|
242
242
|
state = SecureRandom.hex(16)
|
243
243
|
nonce = SecureRandom.hex(16)
|
244
|
-
|
244
|
+
request.stubs(:params).returns('code' => code, 'state' => state, 'nonce' => nonce)
|
245
|
+
request.stubs(:path).returns('')
|
245
246
|
|
246
|
-
|
247
|
+
strategy.options.issuer = 'example.com'
|
248
|
+
strategy.options.client_signing_alg = :RS256
|
249
|
+
strategy.options.client_jwk_signing_key = jwks.to_s
|
250
|
+
strategy.options.response_type = 'code'
|
251
|
+
|
252
|
+
strategy.unstub(:user_info)
|
253
|
+
access_token = stub('OpenIDConnect::AccessToken')
|
254
|
+
access_token.stubs(:access_token)
|
255
|
+
access_token.stubs(:refresh_token)
|
256
|
+
access_token.stubs(:expires_in)
|
257
|
+
access_token.stubs(:scope)
|
258
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
259
|
+
client.expects(:access_token!).at_least_once.returns(access_token)
|
260
|
+
access_token.expects(:userinfo!).returns(user_info)
|
261
|
+
|
262
|
+
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
263
|
+
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
264
|
+
id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
|
265
|
+
id_token.expects(:verify!)
|
266
|
+
|
267
|
+
strategy.expects(:decode_id_token).twice.with(access_token.id_token).returns(id_token)
|
268
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state })
|
269
|
+
strategy.callback_phase
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_callback_phase_with_id_token_no_kid
|
273
|
+
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
274
|
+
|
275
|
+
key = JSON::JWK.new(private_key)
|
276
|
+
other_key = JSON::JWK.new(other_rsa_private)
|
277
|
+
state = SecureRandom.hex(16)
|
278
|
+
request.stubs(:params).returns('id_token' => jwt.to_s, 'state' => state)
|
279
|
+
request.stubs(:path_info).returns('')
|
280
|
+
|
281
|
+
strategy.options.issuer = issuer
|
282
|
+
strategy.options.client_signing_alg = :RS256
|
283
|
+
strategy.options.client_jwk_signing_key = { 'keys' => [other_key, key] }.to_json
|
284
|
+
strategy.options.response_type = 'id_token'
|
285
|
+
|
286
|
+
strategy.unstub(:user_info)
|
287
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
288
|
+
strategy.callback_phase
|
289
|
+
end
|
290
|
+
|
291
|
+
def test_callback_phase_with_id_token_with_kid
|
292
|
+
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
293
|
+
|
294
|
+
key = JSON::JWK.new(private_key)
|
295
|
+
other_key = JSON::JWK.new(other_rsa_private)
|
296
|
+
state = SecureRandom.hex(16)
|
297
|
+
jwt_with_kid = JSON::JWT.new(payload).sign(key, :RS256)
|
298
|
+
request.stubs(:params).returns('id_token' => jwt_with_kid.to_s, 'state' => state)
|
299
|
+
request.stubs(:path_info).returns('')
|
300
|
+
|
301
|
+
strategy.options.issuer = issuer
|
302
|
+
strategy.options.client_signing_alg = :RS256
|
303
|
+
strategy.options.client_jwk_signing_key = { 'keys' => [other_key, key] }.to_json
|
304
|
+
strategy.options.response_type = 'id_token'
|
305
|
+
|
306
|
+
strategy.unstub(:user_info)
|
307
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
308
|
+
strategy.callback_phase
|
309
|
+
end
|
310
|
+
|
311
|
+
def test_callback_phase_with_id_token_with_kid_and_no_matching_kid
|
312
|
+
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
313
|
+
|
314
|
+
key = JSON::JWK.new(private_key)
|
315
|
+
other_key = JSON::JWK.new(other_rsa_private)
|
316
|
+
state = SecureRandom.hex(16)
|
317
|
+
jwt_with_kid = JSON::JWT.new(payload).sign(key, :RS256)
|
318
|
+
request.stubs(:params).returns('id_token' => jwt_with_kid.to_s, 'state' => state)
|
319
|
+
request.stubs(:path_info).returns('')
|
320
|
+
|
321
|
+
strategy.options.issuer = issuer
|
322
|
+
strategy.options.client_signing_alg = :RS256
|
323
|
+
# We use private_key here instead of the wrapped key, which contains a kid
|
324
|
+
strategy.options.client_jwk_signing_key = { 'keys' => [other_key, private_key] }.to_json
|
325
|
+
strategy.options.response_type = 'id_token'
|
326
|
+
|
327
|
+
strategy.unstub(:user_info)
|
328
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
329
|
+
|
330
|
+
assert_raises JSON::JWK::Set::KidNotFound do
|
331
|
+
strategy.callback_phase
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def test_callback_phase_with_id_token_with_hs256
|
336
|
+
state = SecureRandom.hex(16)
|
337
|
+
request.stubs(:params).returns('id_token' => jwt_with_hs256.to_s, 'state' => state)
|
338
|
+
request.stubs(:path_info).returns('')
|
339
|
+
|
340
|
+
strategy.options.issuer = issuer
|
341
|
+
strategy.options.client_options.secret = hmac_secret
|
342
|
+
strategy.options.client_signing_alg = :HS256
|
343
|
+
strategy.options.response_type = 'id_token'
|
344
|
+
|
345
|
+
strategy.unstub(:user_info)
|
346
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
347
|
+
strategy.callback_phase
|
348
|
+
end
|
349
|
+
|
350
|
+
def test_callback_phase_with_hs256_base64_jwt_secret
|
351
|
+
state = SecureRandom.hex(16)
|
352
|
+
request.stubs(:params).returns('id_token' => jwt_with_hs256.to_s, 'state' => state)
|
353
|
+
request.stubs(:path_info).returns('')
|
354
|
+
|
355
|
+
strategy.options.issuer = issuer
|
356
|
+
strategy.options.jwt_secret_base64 = Base64.encode64(hmac_secret)
|
357
|
+
strategy.options.response_type = 'id_token'
|
358
|
+
|
359
|
+
strategy.unstub(:user_info)
|
360
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
361
|
+
strategy.callback_phase
|
362
|
+
end
|
363
|
+
|
364
|
+
def test_callback_phase_with_mismatched_signing_algorithm
|
365
|
+
state = SecureRandom.hex(16)
|
366
|
+
request.stubs(:params).returns('id_token' => jwt_with_hs512.to_s, 'state' => state)
|
367
|
+
request.stubs(:path_info).returns('')
|
368
|
+
|
369
|
+
strategy.options.issuer = issuer
|
370
|
+
strategy.options.client_options.secret = hmac_secret
|
371
|
+
strategy.options.client_signing_alg = :HS256
|
372
|
+
strategy.options.response_type = 'id_token'
|
373
|
+
|
374
|
+
strategy.unstub(:user_info)
|
375
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
376
|
+
|
377
|
+
strategy.expects(:fail!).with(:invalid_jwt_algorithm, is_a(OmniAuth::Strategies::OpenIDConnect::CallbackError))
|
378
|
+
strategy.callback_phase
|
379
|
+
end
|
380
|
+
|
381
|
+
def test_callback_phase_with_id_token_no_matching_key
|
382
|
+
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
383
|
+
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
384
|
+
|
385
|
+
other_key = JSON::JWK.new(other_rsa_private)
|
386
|
+
token = JSON::JWT.new(payload).sign(rsa_private, :RS256).to_s
|
387
|
+
state = SecureRandom.hex(16)
|
388
|
+
request.stubs(:params).returns('id_token' => token, 'state' => state)
|
389
|
+
request.stubs(:path_info).returns('')
|
390
|
+
|
391
|
+
strategy.options.issuer = issuer
|
392
|
+
strategy.options.client_signing_alg = :RS256
|
393
|
+
strategy.options.client_jwk_signing_key = { 'keys' => [other_key] }.to_json
|
394
|
+
strategy.options.response_type = 'id_token'
|
395
|
+
|
396
|
+
strategy.unstub(:user_info)
|
397
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
398
|
+
|
399
|
+
assert_raises JSON::JWK::Set::KidNotFound do
|
400
|
+
strategy.callback_phase
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def test_callback_phase_with_discovery # rubocop:disable Metrics/AbcSize
|
405
|
+
state = SecureRandom.hex(16)
|
406
|
+
|
407
|
+
request.stubs(:params).returns('code' => jwt.to_s, 'state' => state)
|
247
408
|
request.stubs(:path).returns('')
|
248
409
|
|
249
410
|
strategy.options.client_options.host = 'example.com'
|
@@ -258,7 +419,7 @@ module OmniAuth
|
|
258
419
|
config.stubs(:token_endpoint).returns('https://example.com/token')
|
259
420
|
config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
|
260
421
|
config.stubs(:jwks_uri).returns('https://example.com/jwks')
|
261
|
-
config.stubs(:jwks).returns(jwks)
|
422
|
+
config.stubs(:jwks).returns(JSON::JWK::Set.new(jwks['keys']))
|
262
423
|
|
263
424
|
::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
|
264
425
|
|
@@ -273,7 +434,7 @@ module OmniAuth
|
|
273
434
|
access_token.stubs(:refresh_token)
|
274
435
|
access_token.stubs(:expires_in)
|
275
436
|
access_token.stubs(:scope)
|
276
|
-
access_token.stubs(:id_token).returns(
|
437
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
277
438
|
client.expects(:access_token!).at_least_once.returns(access_token)
|
278
439
|
access_token.expects(:userinfo!).returns(user_info)
|
279
440
|
|
@@ -281,13 +442,102 @@ module OmniAuth
|
|
281
442
|
strategy.callback_phase
|
282
443
|
end
|
283
444
|
|
445
|
+
def test_callback_phase_with_no_state_without_state_verification # rubocop:disable Metrics/AbcSize
|
446
|
+
code = SecureRandom.hex(16)
|
447
|
+
|
448
|
+
strategy.options.require_state = false
|
449
|
+
|
450
|
+
request.stubs(:params).returns('code' => code)
|
451
|
+
request.stubs(:path).returns('')
|
452
|
+
|
453
|
+
strategy.options.client_options.host = 'example.com'
|
454
|
+
strategy.options.discovery = true
|
455
|
+
|
456
|
+
issuer = stub('OpenIDConnect::Discovery::Issuer')
|
457
|
+
issuer.stubs(:issuer).returns('https://example.com/')
|
458
|
+
::OpenIDConnect::Discovery::Provider.stubs(:discover!).returns(issuer)
|
459
|
+
|
460
|
+
config = stub('OpenIDConnect::Discovery::Provder::Config')
|
461
|
+
config.stubs(:authorization_endpoint).returns('https://example.com/authorization')
|
462
|
+
config.stubs(:token_endpoint).returns('https://example.com/token')
|
463
|
+
config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
|
464
|
+
config.stubs(:jwks_uri).returns('https://example.com/jwks')
|
465
|
+
config.stubs(:jwks).returns(JSON::JWK::Set.new(jwks['keys']))
|
466
|
+
|
467
|
+
::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
|
468
|
+
|
469
|
+
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
470
|
+
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
471
|
+
id_token.stubs(:verify!).with(issuer: 'https://example.com/', client_id: @identifier, nonce: nonce).returns(true)
|
472
|
+
::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
|
473
|
+
|
474
|
+
strategy.unstub(:user_info)
|
475
|
+
access_token = stub('OpenIDConnect::AccessToken')
|
476
|
+
access_token.stubs(:access_token)
|
477
|
+
access_token.stubs(:refresh_token)
|
478
|
+
access_token.stubs(:expires_in)
|
479
|
+
access_token.stubs(:scope)
|
480
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
481
|
+
client.expects(:access_token!).at_least_once.returns(access_token)
|
482
|
+
access_token.expects(:userinfo!).returns(user_info)
|
483
|
+
|
484
|
+
strategy.call!('rack.session' => { 'omniauth.nonce' => nonce })
|
485
|
+
strategy.callback_phase
|
486
|
+
end
|
487
|
+
|
488
|
+
def test_callback_phase_with_invalid_state_without_state_verification
|
489
|
+
code = SecureRandom.hex(16)
|
490
|
+
state = SecureRandom.hex(16)
|
491
|
+
|
492
|
+
strategy.options.require_state = false
|
493
|
+
|
494
|
+
request.stubs(:params).returns('code' => code, 'state' => 'foobar')
|
495
|
+
request.stubs(:path).returns('')
|
496
|
+
|
497
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
498
|
+
strategy.expects(:fail!)
|
499
|
+
strategy.callback_phase
|
500
|
+
end
|
501
|
+
|
502
|
+
def test_callback_phase_with_jwks_uri
|
503
|
+
id_token = jwt.to_s
|
504
|
+
state = SecureRandom.hex(16)
|
505
|
+
request.stubs(:params).returns('id_token' => id_token, 'state' => state)
|
506
|
+
request.stubs(:path_info).returns('')
|
507
|
+
|
508
|
+
strategy.options.issuer = 'example.com'
|
509
|
+
strategy.options.client_options.jwks_uri = 'https://jwks.example.com'
|
510
|
+
strategy.options.response_type = 'id_token'
|
511
|
+
|
512
|
+
HTTPClient
|
513
|
+
.any_instance.stubs(:get_content)
|
514
|
+
.with(strategy.options.client_options.jwks_uri)
|
515
|
+
.returns(jwks.to_json)
|
516
|
+
|
517
|
+
strategy.unstub(:user_info)
|
518
|
+
access_token = stub('OpenIDConnect::AccessToken')
|
519
|
+
access_token.stubs(:access_token)
|
520
|
+
access_token.stubs(:refresh_token)
|
521
|
+
access_token.stubs(:expires_in)
|
522
|
+
access_token.stubs(:scope)
|
523
|
+
access_token.stubs(:id_token).returns(id_token)
|
524
|
+
|
525
|
+
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
526
|
+
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
527
|
+
id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
|
528
|
+
::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
|
529
|
+
id_token.expects(:verify!)
|
530
|
+
|
531
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
532
|
+
strategy.callback_phase
|
533
|
+
end
|
534
|
+
|
284
535
|
def test_callback_phase_with_error
|
285
536
|
state = SecureRandom.hex(16)
|
286
|
-
nonce = SecureRandom.hex(16)
|
287
537
|
request.stubs(:params).returns('error' => 'invalid_request')
|
288
538
|
request.stubs(:path).returns('')
|
289
539
|
|
290
|
-
strategy.call!({'rack.session' => {'omniauth.state' => state, 'omniauth.nonce' => nonce}})
|
540
|
+
strategy.call!({ 'rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce } })
|
291
541
|
strategy.expects(:fail!)
|
292
542
|
strategy.callback_phase
|
293
543
|
end
|
@@ -295,7 +545,6 @@ module OmniAuth
|
|
295
545
|
def test_callback_phase_with_invalid_state
|
296
546
|
code = SecureRandom.hex(16)
|
297
547
|
state = SecureRandom.hex(16)
|
298
|
-
nonce = SecureRandom.hex(16)
|
299
548
|
request.stubs(:params).returns('code' => code, 'state' => 'foobar')
|
300
549
|
request.stubs(:path).returns('')
|
301
550
|
|
@@ -306,7 +555,6 @@ module OmniAuth
|
|
306
555
|
|
307
556
|
def test_callback_phase_without_code
|
308
557
|
state = SecureRandom.hex(16)
|
309
|
-
nonce = SecureRandom.hex(16)
|
310
558
|
request.stubs(:params).returns('state' => state)
|
311
559
|
request.stubs(:path).returns('')
|
312
560
|
|
@@ -318,7 +566,6 @@ module OmniAuth
|
|
318
566
|
|
319
567
|
def test_callback_phase_without_id_token
|
320
568
|
state = SecureRandom.hex(16)
|
321
|
-
nonce = SecureRandom.hex(16)
|
322
569
|
request.stubs(:params).returns('state' => state)
|
323
570
|
request.stubs(:path).returns('')
|
324
571
|
strategy.options.response_type = 'id_token'
|
@@ -331,7 +578,6 @@ module OmniAuth
|
|
331
578
|
|
332
579
|
def test_callback_phase_without_id_token_symbol
|
333
580
|
state = SecureRandom.hex(16)
|
334
|
-
nonce = SecureRandom.hex(16)
|
335
581
|
request.stubs(:params).returns('state' => state)
|
336
582
|
request.stubs(:path).returns('')
|
337
583
|
strategy.options.response_type = :id_token
|
@@ -345,7 +591,6 @@ module OmniAuth
|
|
345
591
|
def test_callback_phase_with_timeout
|
346
592
|
code = SecureRandom.hex(16)
|
347
593
|
state = SecureRandom.hex(16)
|
348
|
-
nonce = SecureRandom.hex(16)
|
349
594
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
350
595
|
request.stubs(:path).returns('')
|
351
596
|
|
@@ -365,7 +610,6 @@ module OmniAuth
|
|
365
610
|
def test_callback_phase_with_etimeout
|
366
611
|
code = SecureRandom.hex(16)
|
367
612
|
state = SecureRandom.hex(16)
|
368
|
-
nonce = SecureRandom.hex(16)
|
369
613
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
370
614
|
request.stubs(:path).returns('')
|
371
615
|
|
@@ -385,7 +629,6 @@ module OmniAuth
|
|
385
629
|
def test_callback_phase_with_socket_error
|
386
630
|
code = SecureRandom.hex(16)
|
387
631
|
state = SecureRandom.hex(16)
|
388
|
-
nonce = SecureRandom.hex(16)
|
389
632
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
390
633
|
request.stubs(:path).returns('')
|
391
634
|
|
@@ -405,7 +648,6 @@ module OmniAuth
|
|
405
648
|
def test_callback_phase_with_rack_oauth2_client_error
|
406
649
|
code = SecureRandom.hex(16)
|
407
650
|
state = SecureRandom.hex(16)
|
408
|
-
nonce = SecureRandom.hex(16)
|
409
651
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
410
652
|
request.stubs(:path).returns('')
|
411
653
|
|
@@ -426,6 +668,7 @@ module OmniAuth
|
|
426
668
|
info = strategy.info
|
427
669
|
assert_equal user_info.name, info[:name]
|
428
670
|
assert_equal user_info.email, info[:email]
|
671
|
+
assert_equal user_info.email_verified, info[:email_verified]
|
429
672
|
assert_equal user_info.preferred_username, info[:nickname]
|
430
673
|
assert_equal user_info.given_name, info[:first_name]
|
431
674
|
assert_equal user_info.family_name, info[:last_name]
|
@@ -442,7 +685,7 @@ module OmniAuth
|
|
442
685
|
def test_credentials
|
443
686
|
strategy.options.issuer = 'example.com'
|
444
687
|
strategy.options.client_signing_alg = :RS256
|
445
|
-
strategy.options.client_jwk_signing_key =
|
688
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
446
689
|
|
447
690
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
448
691
|
id_token.stubs(:verify!).returns(true)
|
@@ -453,7 +696,7 @@ module OmniAuth
|
|
453
696
|
access_token.stubs(:refresh_token).returns(SecureRandom.hex(16))
|
454
697
|
access_token.stubs(:expires_in).returns(Time.now)
|
455
698
|
access_token.stubs(:scope).returns('openidconnect')
|
456
|
-
access_token.stubs(:id_token).returns(
|
699
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
457
700
|
|
458
701
|
client.expects(:access_token!).returns(access_token)
|
459
702
|
access_token.expects(:refresh_token).returns(access_token.refresh_token)
|
@@ -465,7 +708,7 @@ module OmniAuth
|
|
465
708
|
token: access_token.access_token,
|
466
709
|
refresh_token: access_token.refresh_token,
|
467
710
|
expires_in: access_token.expires_in,
|
468
|
-
scope: access_token.scope
|
711
|
+
scope: access_token.scope,
|
469
712
|
},
|
470
713
|
strategy.credentials
|
471
714
|
)
|
@@ -473,11 +716,10 @@ module OmniAuth
|
|
473
716
|
|
474
717
|
def test_option_send_nonce
|
475
718
|
strategy.options.client_options[:host] = 'foobar.com'
|
476
|
-
|
477
|
-
assert(strategy.authorize_uri =~ /nonce=/, 'URI must contain nonce')
|
719
|
+
assert_match(/nonce/, strategy.authorize_uri, 'URI must contain nonce')
|
478
720
|
|
479
721
|
strategy.options.send_nonce = false
|
480
|
-
|
722
|
+
refute_match(/nonce/, strategy.authorize_uri, 'URI must not contain nonce')
|
481
723
|
end
|
482
724
|
|
483
725
|
def test_failure_endpoint_redirect
|
@@ -487,9 +729,9 @@ module OmniAuth
|
|
487
729
|
|
488
730
|
result = strategy.callback_phase
|
489
731
|
|
490
|
-
assert(result.is_a?
|
732
|
+
assert(result.is_a?(Array))
|
491
733
|
assert(result[0] == 302, 'Redirect')
|
492
|
-
assert(result[1][
|
734
|
+
assert(result[1]['Location'] =~ %r{/auth/failure})
|
493
735
|
end
|
494
736
|
|
495
737
|
def test_state
|
@@ -518,7 +760,7 @@ module OmniAuth
|
|
518
760
|
def test_dynamic_state
|
519
761
|
# Stub request parameters
|
520
762
|
request.stubs(:path).returns('')
|
521
|
-
strategy.call!('rack.session' => {
|
763
|
+
strategy.call!('rack.session' => {}, QUERY_STRING: { state: 'abc', client_id: '123' })
|
522
764
|
|
523
765
|
strategy.options.state = lambda { |env|
|
524
766
|
# Get params from request, e.g. CGI.parse(env['QUERY_STRING'])
|
@@ -534,18 +776,17 @@ module OmniAuth
|
|
534
776
|
|
535
777
|
def test_option_client_auth_method
|
536
778
|
state = SecureRandom.hex(16)
|
537
|
-
nonce = SecureRandom.hex(16)
|
538
779
|
|
539
780
|
opts = strategy.options.client_options
|
540
781
|
opts[:host] = 'foobar.com'
|
541
782
|
strategy.options.issuer = 'foobar.com'
|
542
783
|
strategy.options.client_auth_method = :not_basic
|
543
784
|
strategy.options.client_signing_alg = :RS256
|
544
|
-
strategy.options.client_jwk_signing_key =
|
785
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
545
786
|
|
546
787
|
json_response = {
|
547
788
|
access_token: 'test_access_token',
|
548
|
-
id_token:
|
789
|
+
id_token: jwt.to_s,
|
549
790
|
token_type: 'Bearer',
|
550
791
|
}.to_json
|
551
792
|
success = Struct.new(:status, :body).new(200, json_response)
|
@@ -563,21 +804,19 @@ module OmniAuth
|
|
563
804
|
{}
|
564
805
|
).returns(success)
|
565
806
|
|
566
|
-
assert(strategy.send
|
807
|
+
assert(strategy.send(:access_token))
|
567
808
|
end
|
568
809
|
|
569
810
|
def test_public_key_with_jwks
|
570
811
|
strategy.options.client_signing_alg = :RS256
|
571
|
-
strategy.options.client_jwk_signing_key =
|
812
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
572
813
|
|
573
814
|
assert_equal JSON::JWK::Set, strategy.public_key.class
|
574
815
|
end
|
575
816
|
|
576
817
|
def test_public_key_with_jwk
|
577
818
|
strategy.options.client_signing_alg = :RS256
|
578
|
-
|
579
|
-
jwks = JSON.parse(jwks_str)
|
580
|
-
jwk = jwks['keys'].first
|
819
|
+
jwk = jwks[:keys].first
|
581
820
|
strategy.options.client_jwk_signing_key = jwk.to_json
|
582
821
|
|
583
822
|
assert_equal JSON::JWK, strategy.public_key.class
|
@@ -592,27 +831,17 @@ module OmniAuth
|
|
592
831
|
def test_public_key_with_hmac
|
593
832
|
strategy.options.client_options.secret = 'secret'
|
594
833
|
strategy.options.client_signing_alg = :HS256
|
595
|
-
assert_equal strategy.options.client_options.secret, strategy.
|
834
|
+
assert_equal strategy.options.client_options.secret, strategy.secret
|
596
835
|
end
|
597
836
|
|
598
837
|
def test_id_token_auth_hash
|
599
838
|
state = SecureRandom.hex(16)
|
600
|
-
nonce = SecureRandom.hex(16)
|
601
839
|
strategy.options.response_type = 'id_token'
|
602
840
|
strategy.options.issuer = 'example.com'
|
603
841
|
|
604
842
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
605
843
|
id_token.stubs(:verify!).returns(true)
|
606
|
-
id_token.stubs(:raw_attributes, :to_h).returns(
|
607
|
-
{
|
608
|
-
"iss": "http://server.example.com",
|
609
|
-
"sub": "248289761001",
|
610
|
-
"aud": "s6BhdRkqt3",
|
611
|
-
"nonce": "n-0S6_WzA2Mj",
|
612
|
-
"exp": 1311281970,
|
613
|
-
"iat": 1311280970,
|
614
|
-
}
|
615
|
-
)
|
844
|
+
id_token.stubs(:raw_attributes, :to_h).returns(payload)
|
616
845
|
|
617
846
|
request.stubs(:params).returns('state' => state, 'nounce' => nonce, 'id_token' => id_token)
|
618
847
|
request.stubs(:path).returns('')
|
@@ -630,6 +859,41 @@ module OmniAuth
|
|
630
859
|
assert auth_hash.key?('extra')
|
631
860
|
assert auth_hash['extra'].key?('raw_info')
|
632
861
|
end
|
862
|
+
|
863
|
+
def test_option_pkce
|
864
|
+
strategy.options.client_options[:host] = 'example.com'
|
865
|
+
|
866
|
+
# test pkce disabled
|
867
|
+
strategy.options.pkce = false
|
868
|
+
|
869
|
+
assert((strategy.authorize_uri !~ /code_challenge=/), 'URI must not contain code challenge param')
|
870
|
+
assert((strategy.authorize_uri !~ /code_challenge_method=/), 'URI must not contain code challenge method param')
|
871
|
+
|
872
|
+
# test pkce enabled with default opts
|
873
|
+
strategy.options.pkce = true
|
874
|
+
|
875
|
+
assert(strategy.authorize_uri =~ /code_challenge=/, 'URI must contain code challenge param')
|
876
|
+
assert(strategy.authorize_uri =~ /code_challenge_method=/, 'URI must contain code challenge method param')
|
877
|
+
|
878
|
+
# test pkce with custom verifier code
|
879
|
+
strategy.options.pkce_verifier = proc { 'dummy_verifier' }
|
880
|
+
code_challenge_value = Base64.urlsafe_encode64(
|
881
|
+
Digest::SHA2.digest(strategy.options.pkce_verifier.call),
|
882
|
+
padding: false
|
883
|
+
)
|
884
|
+
|
885
|
+
assert(strategy.authorize_uri =~ /#{Regexp.quote(code_challenge_value)}/, 'URI must contain code challenge value')
|
886
|
+
|
887
|
+
# test pkce with custom options and plain text code
|
888
|
+
strategy.options.pkce_options =
|
889
|
+
{
|
890
|
+
code_challenge: proc { |verifier| verifier },
|
891
|
+
code_challenge_method: 'plain',
|
892
|
+
}
|
893
|
+
|
894
|
+
assert(strategy.authorize_uri =~ /#{Regexp.quote(strategy.options.pkce_verifier.call)}/,
|
895
|
+
'URI must contain code challenge value')
|
896
|
+
end
|
633
897
|
end
|
634
898
|
end
|
635
899
|
end
|