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.
@@ -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 = /^https:\/\/example\.com\/authorize\?client_id=1234&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}$/
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:\/\/example\.com\/logout$}
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 = /^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$/
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 = /^https:\/\/example\.com\/authorization\?client_id=1234&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}$/
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 = /^https:\/\/example\.com\/authorize\?client_id=1234&nonce=\w{32}&response_mode=form_post&response_type=id_token&scope=openid&state=\w{32}$/
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 = /^https:\/\/example\.com\/authorize\?client_id=1234&nonce=\w{32}&response_mode=form_post&response_type=id_token&scope=openid&state=\w{32}$/
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
- assert(!(strategy.authorize_uri =~ /acr_values=/), 'URI must not contain acr_values')
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
- assert(strategy.authorize_uri =~ /acr_values=/, 'URI must contain acr_values')
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 = [:name, :logo, :resource]
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', 'not_allowed' => 'filter_me')
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(session = {}, params = {})
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 = File.read('test/fixtures/jwks.json')
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(File.read('test/fixtures/id_token.txt'))
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
- nonce = SecureRandom.hex(16)
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 = File.read('test/fixtures/jwks.json')
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(File.read('test/fixtures/id_token.txt'))
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 test_callback_phase_with_discovery
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
- jwks = JSON::JWK::Set.new(JSON.parse(File.read('test/fixtures/jwks.json'))['keys'])
244
+ request.stubs(:params).returns('code' => code, 'state' => state, 'nonce' => nonce)
245
+ request.stubs(:path).returns('')
245
246
 
246
- request.stubs(:params).returns('code' => code, 'state' => state)
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(File.read('test/fixtures/id_token.txt'))
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 = File.read('test/fixtures/jwks.json')
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(File.read('test/fixtures/id_token.txt'))
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
- assert(!(strategy.authorize_uri =~ /nonce=/), 'URI must not contain nonce')
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? Array)
732
+ assert(result.is_a?(Array))
491
733
  assert(result[0] == 302, 'Redirect')
492
- assert(result[1]["Location"] =~ /\/auth\/failure/)
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' => { }, QUERY_STRING: { state: 'abc', client_id: '123' } )
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 = File.read('test/fixtures/jwks.json')
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: File.read('test/fixtures/id_token.txt'),
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 :access_token)
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 = File.read('./test/fixtures/jwks.json')
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
- jwks_str = File.read('./test/fixtures/jwks.json')
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.public_key
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