auth0 4.11.0 → 4.12.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,4 +1,4 @@
1
1
  # current version of gem
2
2
  module Auth0
3
- VERSION = '4.11.0'.freeze
3
+ VERSION = '4.12.0'.freeze
4
4
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # rubocop:disable Metrics/BlockLength
2
4
  require 'spec_helper'
3
5
  describe Auth0::Api::AuthenticationEndpoints do
@@ -20,7 +22,7 @@ describe Auth0::Api::AuthenticationEndpoints do
20
22
  grant_type: 'client_credentials',
21
23
  client_id: @instance.client_id,
22
24
  client_secret: @instance.client_secret,
23
- audience: @instance.audience,
25
+ audience: @instance.audience
24
26
  ).and_return('access_token' => 'AccessToken')
25
27
 
26
28
  expect(@instance.api_token.token).to eql 'AccessToken'
@@ -32,7 +34,7 @@ describe Auth0::Api::AuthenticationEndpoints do
32
34
  grant_type: 'client_credentials',
33
35
  client_id: @instance.client_id,
34
36
  client_secret: @instance.client_secret,
35
- audience: '__test_audience__',
37
+ audience: '__test_audience__'
36
38
  ).and_return('access_token' => 'AccessToken')
37
39
 
38
40
  expect(
@@ -47,7 +49,7 @@ describe Auth0::Api::AuthenticationEndpoints do
47
49
  allow(@instance).to receive(:post).with(
48
50
  '/oauth/token', client_id: @instance.client_id, client_secret: @instance.client_secret, grant_type: 'client_credentials'
49
51
  )
50
- .and_return('access_token' => 'AccessToken')
52
+ .and_return('access_token' => 'AccessToken')
51
53
 
52
54
  expect(@instance).to receive(:post).with(
53
55
  '/oauth/token', client_id: @instance.client_id, client_secret: @instance.client_secret, grant_type: 'client_credentials'
@@ -90,7 +92,6 @@ describe Auth0::Api::AuthenticationEndpoints do
90
92
  it { expect { @instance.obtain_user_tokens('code', '') }.to raise_error 'Must supply a valid redirect_uri' }
91
93
  end
92
94
 
93
-
94
95
  context '.exchange_auth_code_for_tokens' do
95
96
  it { is_expected.to respond_to(:exchange_auth_code_for_tokens) }
96
97
 
@@ -183,7 +184,6 @@ describe Auth0::Api::AuthenticationEndpoints do
183
184
  ).to eq 'AccessToken'
184
185
  end
185
186
 
186
-
187
187
  it 'is expected to make post request to /oauth/token with custom params' do
188
188
  allow(@instance).to receive(:post).with(
189
189
  '/oauth/token',
@@ -241,7 +241,6 @@ describe Auth0::Api::AuthenticationEndpoints do
241
241
  end
242
242
 
243
243
  it 'should make post to /oauth/token with custom params' do
244
-
245
244
  allow(@instance).to receive(:post).with(
246
245
  '/oauth/token',
247
246
  username: 'test@test.com',
@@ -356,7 +355,7 @@ describe Auth0::Api::AuthenticationEndpoints do
356
355
  '/passwordless/start',
357
356
  client_id: @instance.client_id,
358
357
  client_secret: @instance.client_secret,
359
- connection: 'email',
358
+ connection: 'email',
360
359
  email: 'test@test.com',
361
360
  send: 'code',
362
361
  authParams: {
@@ -552,7 +551,7 @@ describe Auth0::Api::AuthenticationEndpoints do
552
551
  context '.userinfo' do
553
552
  it { is_expected.to respond_to(:user_info) }
554
553
  it 'is expected to make a GET request to /userinfo' do
555
- is_expected.to receive(:get).with('/userinfo', {}, {'Authorization' => 'Bearer access-token'})
554
+ is_expected.to receive(:get).with('/userinfo', {}, { 'Authorization' => 'Bearer access-token' })
556
555
  subject.userinfo 'access-token'
557
556
  end
558
557
  end
@@ -605,7 +604,7 @@ describe Auth0::Api::AuthenticationEndpoints do
605
604
 
606
605
  it 'is expected to return a logout url with a client ID' do
607
606
  expect(@instance.logout_url(return_to, include_client: true).to_s).to eq(
608
- "https://#{@instance.domain}/v2/logout" +
607
+ "https://#{@instance.domain}/v2/logout" \
609
608
  "?returnTo=http%3A%2F%2Freturnto.com&client_id=#{@instance.client_id}"
610
609
  )
611
610
  end
@@ -655,7 +654,7 @@ describe Auth0::Api::AuthenticationEndpoints do
655
654
  end
656
655
 
657
656
  it 'is expected to get the wsfed url with wctx' do
658
- expect(@instance.wsfed_url(UP_AUTH, {wctx: 'wctx_test'}).to_s).to eq(
657
+ expect(@instance.wsfed_url(UP_AUTH, { wctx: 'wctx_test' }).to_s).to eq(
659
658
  "https://#{@instance.domain}/wsfed/#{@instance.client_id}" \
660
659
  "?whr=#{UP_AUTH}&wctx=wctx_test"
661
660
  )
@@ -674,5 +673,31 @@ describe Auth0::Api::AuthenticationEndpoints do
674
673
  )
675
674
  end
676
675
  end
676
+
677
+ # Auth0::API::AuthenticationEndpoints.validate_id_token
678
+ context '.validate_id_token' do
679
+ it { expect(@instance).to respond_to(:validate_id_token) }
680
+
681
+ it 'is expected not to raise an error with default values' do
682
+ stub_request(:get, 'https://test.auth0.com/.well-known/jwks.json').to_return(body: JWKS_RESPONSE_1.to_json)
683
+ token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LTEifQ.eyJpc3MiOiJodHRwczovL3Rlc3QuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDEyMzQ1Njc4OSIsImF1ZCI6WyJfX3Rlc3RfYXVkaWVuY2VfXyIsIl9fdGVzdF9jbGllbnRfaWRfXyJdLCJleHAiOjI1MzgzMDExNDYsImlhdCI6MTU4NzU5MjU2MSwiYXpwIjoiX190ZXN0X2NsaWVudF9pZF9fIn0.X35Hfa1C9RtuJIj7Eky2iO4elY9XqCDRy8ieFAft63vGds9vhP38x8QHbJifmLs6vDEOySKfJMWhklp3oaXm6Tk6gyUQEaliW_pXUgZt8C3Xo125R8BMCDQeVJg8Abevbg6FpHpYztWpQuI609tmpoTczx7pXMmAneg6e4LNYvvtzaFD_0M0cxtjkm4OcevCJszNBru3tdXwRynkGbMYeXgoa_FumAshRvIvh-4dtkyNWsepo5IVTvixxF3FVoFaXOOycmFXh9gxOppG4lvE78AFB9AQ9LNS-DNhcXszbPs9KHMrg2bqhSL8Razqd3m2a1MXkdLMBD5DY499MVnb5w'
684
+
685
+ expect { @instance.validate_id_token(token) }.to_not raise_exception
686
+ end
687
+
688
+ it 'is expected not to raise an error with custom values' do
689
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3N1ZXIiLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsiYXVkaWVuY2UiLCJhbm90aGVyX2F1ZGllbmNlIl0sImV4cCI6MjUzODMwMTE0NiwiaWF0IjoxNTg3NTkyNTYxLCJub25jZSI6Im5vbmNlIiwiYXpwIjoiYXVkaWVuY2UiLCJhdXRoX3RpbWUiOjE1ODc2Nzg5NjF9.u39qTvuUmbzj5jsXjATXxjxJt0u064G1IAumoi18gm0'
690
+
691
+ expect do
692
+ @instance.validate_id_token(token,
693
+ algorithm: Auth0::Algorithm::HS256.secret('secret'),
694
+ leeway: 100,
695
+ nonce: 'nonce',
696
+ max_age: 2538301146,
697
+ issuer: 'issuer',
698
+ audience: 'audience')
699
+ end.to_not raise_exception
700
+ end
701
+ end
677
702
  end
678
703
  # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,474 @@
1
+ # rubocop:disable Metrics/BlockLength
2
+ require 'spec_helper'
3
+
4
+ RSA_PUB_KEY_JWK_1 = { 'kty': "RSA", 'use': 'sig', 'n': "uGbXWiK3dQTyCbX5xdE4yCuYp0AF2d15Qq1JSXT_lx8CEcXb9RbDddl8jGDv-spi5qPa8qEHiK7FwV2KpRE983wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVsWXI9C-yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT69s7of9-I9l5lsJ9cozf1rxrXX4V1u_SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8AziMCxS-VrRPDM-zfvpIJg3JljAh3PJHDiLu902v9w-Iplu1WyoB2aPfitxEhRN0Yw", 'e': 'AQAB', 'kid': 'test-key-1' }.freeze
5
+ RSA_PUB_KEY_JWK_2 = { 'kty': "RSA", 'use': 'sig', 'n': "uGbXWiK3dQTyCbX5xdE4yCuYp0AF2d15Qq1JSXT_lx8CEcXb9RbDddl8jGDv-spi5qPa8qEHiK7FwV2KpRE983wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVsWXI9C-yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT69s7of9-I9l5lsJ9cozf1rxrXX4V1u_SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8AziMCxS-VrRPDM-zfvpIJg3JljAh3PJHDiLu902v9w-Iplu1WyoB2aPfitxEhRN0Yw", 'e': 'AQAB', 'kid': 'test-key-2' }.freeze
6
+ JWKS_RESPONSE_1 = { 'keys': [RSA_PUB_KEY_JWK_1] }.freeze
7
+ JWKS_RESPONSE_2 = { 'keys': [RSA_PUB_KEY_JWK_2] }.freeze
8
+ JWKS_URL = 'https://tokens-test.auth0.com/.well-known/jwks.json'.freeze
9
+ HMAC_SHARED_SECRET = 'secret'.freeze
10
+
11
+ LEEWAY = 60
12
+ CLOCK = 1587592561 # Apr 22 2020 21:56:01 UTC
13
+ CONTEXT = { algorithm: Auth0::Algorithm::HS256.secret(HMAC_SHARED_SECRET), leeway: LEEWAY, audience: 'tokens-test-123', issuer: 'https://tokens-test.auth0.com/', clock: CLOCK }.freeze
14
+
15
+ describe Auth0::Mixins::Validation::IdTokenValidator do
16
+ subject { @instance }
17
+
18
+ context 'instance' do
19
+ it 'is expected respond to :validate' do
20
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new({})
21
+
22
+ expect(instance).to respond_to(:validate)
23
+ end
24
+ end
25
+
26
+ context 'ID token decoding' do
27
+ expected_error = 'ID token could not be decoded'
28
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new({})
29
+
30
+ it 'is expected to raise an error with a nil id_token' do
31
+ expect { instance.validate(nil) }.to raise_exception(expected_error)
32
+ end
33
+
34
+ it 'is expected to raise an error with an empty id_token' do
35
+ expect { instance.validate('') }.to raise_exception(expected_error)
36
+ end
37
+
38
+ it 'is expected to raise an error with an invalid format' do
39
+ expect { instance.validate('a.b') }.to raise_exception(expected_error)
40
+ expect { instance.validate('a.b.') }.to raise_exception(expected_error)
41
+ expect { instance.validate('a.b.c.d') }.to raise_exception(expected_error)
42
+ end
43
+
44
+ it 'is expected to raise an error with an invalid encoding' do
45
+ expect { instance.validate('a.b.c') }.to raise_exception(expected_error)
46
+ end
47
+ end
48
+
49
+ context 'algorithm verification' do
50
+ token = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.Hn38QVtN_mWN0c-jOa-Fqq69kXpbBp0THsvE-CQ47Ps'
51
+
52
+ it 'is expected to raise an error with an unsupported algorithm' do
53
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new({ algorithm: 'ES256' })
54
+
55
+ expect { instance.validate(token) }.to raise_exception('Signature algorithm of "ES256" is not supported')
56
+ end
57
+
58
+ it 'is expected to raise an error when the algorithm does not match the alg header value' do
59
+ algorithm = Auth0::Algorithm::HS256.secret(HMAC_SHARED_SECRET)
60
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new({ algorithm: algorithm })
61
+
62
+ expect { instance.validate(token) }.to raise_exception('Signature algorithm of "ES256" is not supported. Expected the ID token to be signed with "HS256"')
63
+ end
64
+ end
65
+
66
+ context 'HS256 signature verification' do
67
+ before :each do
68
+ algorithm = Auth0::Algorithm::HS256.secret(HMAC_SHARED_SECRET)
69
+ @instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ algorithm: algorithm }))
70
+ end
71
+
72
+ it 'is expected not to raise an error with a valid signature' do
73
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.Hn38QVtN_mWN0c-jOa-Fqq69kXpbBp0THsvE-CQ47Ps'
74
+
75
+ expect { @instance.validate(token) }.not_to raise_exception
76
+ end
77
+
78
+ it 'is expected to raise an error with an invalid signature' do
79
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.invalidsignature'
80
+
81
+ expect { @instance.validate(token) }.to raise_exception('Invalid ID token signature')
82
+ end
83
+ end
84
+
85
+ context 'RS256 signature verification' do
86
+ before :each do
87
+ stub_jwks
88
+ algorithm = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
89
+ @instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ algorithm: algorithm }))
90
+ end
91
+
92
+ after :each do
93
+ Auth0::Algorithm::RS256.remove_jwks
94
+ WebMock.reset!
95
+ end
96
+
97
+ it 'is expected not to raise an error with a valid signature' do
98
+ token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LTEifQ.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.jE00ARUiAwrKEoAMwbioKYj4bUZjmg31V7McDtIPsJJ16rYcvI-e5mtSSMgCmAom6t-WA7dsSWCJUlBCW2nAMvyCZ-hj8HG9Z0RmQEiwig9Fk22avoX94zdx65TwAeDfn2uMRaX_ps3TJcn4nymUtMp8Lps_vMw15eJerKThlSO4KuLTrvDDdRaCRamAd7jxuzhiwOt0mB0TVD55b5itA02pGuyapbjQXvvLYEx8OgpyIaAkB9Ry25abgjev0bSw2kjFZckG3lv9QgvZM85m9l3Rbrc6msNPGfMDFWGyT3Tu2ObqnSEA-57hZeuCbFrOya3vUwgSlc66rfvZj2xpzg'
99
+
100
+ expect { @instance.validate(token) }.not_to raise_exception
101
+ end
102
+
103
+ it 'is expected to raise an error with an invalid signature' do
104
+ token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LTEifQ.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.invalidsignature'
105
+
106
+ expect { @instance.validate(token) }.to raise_exception('Invalid ID token signature')
107
+ end
108
+
109
+ it 'is expected to raise an error when the public key cannot be found' do
110
+ token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LTIifQ.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.r2ksgiiM8zGJ6byea_Fq_zWWEmUdOnwQLVdb5JzgdBv1GUQFp-4LNaRhcga4FIrbKgxaPeewGLTq2VqfjmNJUNfARcE3QEacQ_JEHkC6zKZIiqcu4msHl8X9HXyHxOPHMTTjYMjauPzET7UH_oLxF68DDDQqvKX9VqLsncpyC-KdTCFTLGlFSq6pxmYt6gwrF2Uo15Gzc6qe2I9-MTXCYd44VW1zQi6GhNJNKbXH6U3bf7nof0ot1PSjBXXuLgf6d3qumTStECCjIUmdBb6FiEX4SSRI4MgHWj4q0LyN28baRpYwYPhVnjzUxOP7OLjKiHs45ORBhuAWhrJnuR_uBQ'
111
+
112
+ expect { @instance.validate(token) }.to raise_exception('Could not find a public key for Key ID (kid) "test-key-2"')
113
+ end
114
+
115
+ it 'is expected to fetch the JWK set from the url if the public key cannot be found and the cache is not empty' do
116
+ token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LTIifQ.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.r2ksgiiM8zGJ6byea_Fq_zWWEmUdOnwQLVdb5JzgdBv1GUQFp-4LNaRhcga4FIrbKgxaPeewGLTq2VqfjmNJUNfARcE3QEacQ_JEHkC6zKZIiqcu4msHl8X9HXyHxOPHMTTjYMjauPzET7UH_oLxF68DDDQqvKX9VqLsncpyC-KdTCFTLGlFSq6pxmYt6gwrF2Uo15Gzc6qe2I9-MTXCYd44VW1zQi6GhNJNKbXH6U3bf7nof0ot1PSjBXXuLgf6d3qumTStECCjIUmdBb6FiEX4SSRI4MgHWj4q0LyN28baRpYwYPhVnjzUxOP7OLjKiHs45ORBhuAWhrJnuR_uBQ'
117
+ Auth0::Algorithm::RS256.jwks_url(JWKS_URL).jwks
118
+ stub_jwks(JWKS_RESPONSE_2)
119
+ @instance.validate(token)
120
+
121
+ expect(a_request(:get, JWKS_URL)).to have_been_made.twice
122
+ end
123
+ end
124
+
125
+ context 'context validation' do
126
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.Hn38QVtN_mWN0c-jOa-Fqq69kXpbBp0THsvE-CQ47Ps'
127
+
128
+ it 'is expected to raise an error with a non-integer leeway' do
129
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ leeway: '1' }))
130
+
131
+ expect { instance.validate(token) }.to raise_exception('Must supply a valid leeway')
132
+ end
133
+
134
+ it 'is expected to raise an error with a negative leeway' do
135
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ leeway: -1 }))
136
+
137
+ expect { instance.validate(token) }.to raise_exception('Must supply a valid leeway')
138
+ end
139
+
140
+ it 'is expected to raise an error with an empty nonce' do
141
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ nonce: '' }))
142
+
143
+ expect { instance.validate(token) }.to raise_exception('Must supply a valid nonce')
144
+ end
145
+
146
+ it 'is expected to raise an error with an empty issuer' do
147
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ issuer: '' }))
148
+
149
+ expect { instance.validate(token) }.to raise_exception('Must supply a valid issuer')
150
+ end
151
+
152
+ it 'is expected to raise an error with an empty audience' do
153
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ audience: '' }))
154
+
155
+ expect { instance.validate(token) }.to raise_exception('Must supply a valid audience')
156
+ end
157
+
158
+ it 'is expected to raise an error with a non-integer max_age' do
159
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ max_age: '1' }))
160
+
161
+ expect { instance.validate(token) }.to raise_exception('Must supply a valid max_age')
162
+ end
163
+
164
+ it 'is expected to raise an error with a negative max_age' do
165
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ max_age: -1 }))
166
+
167
+ expect { instance.validate(token) }.to raise_exception('Must supply a valid max_age')
168
+ end
169
+ end
170
+
171
+ context 'claims validation' do
172
+ before :all do
173
+ @instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT)
174
+ end
175
+
176
+ it 'is expected to raise an error with a missing iss' do
177
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.QL2B2tqJhlW9rc8HQ3PQKkjDufBeSvtRBtJmRPdQ5K8'
178
+
179
+ expect { @instance.validate(token) }.to raise_exception('Issuer (iss) claim must be a string present in the ID token')
180
+ end
181
+
182
+ it 'is expected to raise an error with a invalid iss' do
183
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzb21ldGhpbmctZWxzZSIsInN1YiI6ImF1dGgwfDEyMzQ1Njc4OSIsImF1ZCI6WyJ0b2tlbnMtdGVzdC0xMjMiLCJleHRlcm5hbC10ZXN0LTk5OSJdLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MSwibm9uY2UiOiJhMWIyYzNkNGU1IiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.AhMMouDlGMdxTYrY9Cn-p8svJ8ssKmsHeT6JNRVTh10'
184
+
185
+ expect { @instance.validate(token) }.to raise_exception("Issuer (iss) claim mismatch in the ID token; expected \"#{CONTEXT[:issuer]}\", found \"something-else\"")
186
+ end
187
+
188
+ it 'is expected to raise an error with a missing sub' do
189
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0._4sUXtAZYpGrO3QaYArXnk2JivCqixa7hgHhH3w4SlY'
190
+
191
+ expect { @instance.validate(token) }.to raise_exception('Subject (sub) claim must be a string present in the ID token')
192
+ end
193
+
194
+ it 'is expected to raise an error with a missing aud' do
195
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MSwibm9uY2UiOiJhMWIyYzNkNGU1IiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.TlwnBmGUKe0SElSYKxPqsG1mujkK2t1CwDJGGiWRdXA'
196
+
197
+ expect { @instance.validate(token) }.to raise_exception('Audience (aud) claim must be a string or array of strings present in the ID token')
198
+ end
199
+
200
+ it 'is expected to raise an error with an invalid string aud' do
201
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOiJleHRlcm5hbC10ZXN0LTk5OSIsImV4cCI6MTU4Nzc2NTM2MSwiaWF0IjoxNTg3NTkyNTYxLCJub25jZSI6ImExYjJjM2Q0ZTUiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1ODc2Nzg5NjF9.-Tf5CIi2bZ51UdgqxFWQNXpJJmK5GgsetcVoVrQwHIA'
202
+
203
+ expect { @instance.validate(token) }.to raise_exception("Audience (aud) claim mismatch in the ID token; expected \"#{CONTEXT[:audience]}\", found \"external-test-999\"")
204
+ end
205
+
206
+ it 'is expected to raise an error with an invalid array aud' do
207
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsiZXh0ZXJuYWwtdGVzdC05OTgiLCJleHRlcm5hbC10ZXN0LTk5OSJdLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MSwibm9uY2UiOiJhMWIyYzNkNGU1IiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.Q1GRVL5g3RLQqG5sEV_cc8WW_oiZzFIAfzRfBdxMW2s'
208
+
209
+ expect { @instance.validate(token) }.to raise_exception("Audience (aud) claim mismatch in the ID token; expected \"#{CONTEXT[:audience]}\" but was not one of \"external-test-998, external-test-999\"")
210
+ end
211
+
212
+ it 'is expected to raise an error with a missing exp' do
213
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiaWF0IjoxNTg3NTkyNTYxLCJub25jZSI6ImExYjJjM2Q0ZTUiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1ODc2Nzg5NjF9.aoLiQX3sHsf1bEbc0axbjJ9qV6hhomtEzJq-FT8OGF0'
214
+
215
+ expect { @instance.validate(token) }.to raise_exception('Expiration Time (exp) claim must be a number present in the ID token')
216
+ end
217
+
218
+ it 'is expected to raise an error with a invalid exp' do
219
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NTkyNTYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.A8Pc0vlCG5Ufez7VIoRqXTYpJehalTEgGX9cR2xJLkU'
220
+ clock = CLOCK + LEEWAY + 1
221
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ clock: clock }))
222
+
223
+ expect { instance.validate(token) }.to raise_exception("Expiration Time (exp) claim mismatch in the ID token; current time \"#{clock}\" is after expiration time \"1587592621\"")
224
+ end
225
+
226
+ it 'is expected to raise an error with a missing iat' do
227
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJub25jZSI6ImExYjJjM2Q0ZTUiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1ODc2Nzg5NjF9.Jea6UVJsAK7Hnb494f_WIQCIbaLTnnCvMenSY1Y2toc'
228
+
229
+ expect { @instance.validate(token) }.to raise_exception('Issued At (iat) claim must be a number present in the ID token')
230
+ end
231
+
232
+ it 'is expected to raise an error with a invalid iat' do
233
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc3NjUzNjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.1AeRLTaExbKnmsfNduUl3HArsau4RcNrnmYOJnkPWi0'
234
+ clock = CLOCK - LEEWAY - 1
235
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ clock: clock }))
236
+
237
+ expect { instance.validate(token) }.to raise_exception("Issued At (iat) claim mismatch in the ID token; current time \"#{clock}\" is before issued at time \"1587765301\"")
238
+ end
239
+
240
+ it 'is expected not to raise an error with a missing but not required nonce' do
241
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.-o5grnyODbBdRgzcrn7Sf9Hb6eOC0x_U2i3YjVgHN0U'
242
+
243
+ expect { @instance.validate(token) }.not_to raise_exception
244
+ end
245
+
246
+ it 'is expected to raise an error with a missing but required nonce' do
247
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.-o5grnyODbBdRgzcrn7Sf9Hb6eOC0x_U2i3YjVgHN0U'
248
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ nonce: 'a1b2c3d4e5' }))
249
+
250
+ expect { instance.validate(token) }.to raise_exception('Nonce (nonce) claim must be a string present in the ID token')
251
+ end
252
+
253
+ it 'is expected to raise an error with an invalid nonce' do
254
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiMDAwOTk5IiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.XqQPdFN4m5kmTUQQi_mAJu0LQOeUTS9lF2J_xWc_j-0'
255
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ nonce: 'a1b2c3d4e5' }))
256
+
257
+ expect { instance.validate(token) }.to raise_exception('Nonce (nonce) claim mismatch in the ID token; expected "a1b2c3d4e5", found "000999"')
258
+ end
259
+
260
+ it 'is expected to raise an error with a missing azp' do
261
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.LrgYkIbWrxMq6jvvkL1lxWL237ii1IBhtN2o_tDxFns'
262
+
263
+ expect { @instance.validate(token) }.to raise_exception('Authorized Party (azp) claim must be a string present in the ID token')
264
+ end
265
+
266
+ it 'is expected to raise an error with an invalid azp' do
267
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6ImV4dGVybmFsLXRlc3QtOTk5IiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.3DX-LY3B4UngDML-9nv11Sv89ECJpRpOLeWnkF1vAFY'
268
+
269
+ expect { @instance.validate(token) }.to raise_exception("Authorized Party (azp) claim mismatch in the ID token; expected \"tokens-test-123\", found \"external-test-999\"")
270
+ end
271
+
272
+ it 'is expected to raise an error with a missing auth_time' do
273
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyJ9.JqUotnjHbGW0FcHz1s1YsRkce9Sbpsv2AEBDMpcUhp8'
274
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ max_age: 120 }))
275
+
276
+ expect { instance.validate(token) }.to raise_exception('Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified')
277
+ end
278
+
279
+ it 'is expected to raise an error with a invalid auth_time' do
280
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzU5MjU2MX0.B7eWHJPHjhOh0ALjIQi0ro6zVsqGIeHd0gpRZsv6Hg8'
281
+ max_age = 120
282
+ auth_time = CLOCK + LEEWAY + max_age
283
+ clock = auth_time + 1
284
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ max_age: max_age, clock: clock }))
285
+
286
+ expect { instance.validate(token) }.to raise_exception("Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time \"#{clock}\" is after last auth at \"#{auth_time}\"")
287
+ end
288
+ end
289
+ end
290
+
291
+ describe Auth0::Algorithm::HS256 do
292
+ context 'class' do
293
+ it 'is expected to respond to :secret' do
294
+ expect(Auth0::Algorithm::HS256).to respond_to(:secret)
295
+ end
296
+
297
+ it 'is expected not to respond to :new' do
298
+ expect(Auth0::Algorithm::HS256).not_to respond_to(:new)
299
+ end
300
+ end
301
+
302
+ context 'instance' do
303
+ it 'is expected to respond to :secret' do
304
+ instance = Auth0::Algorithm::HS256.secret('secret')
305
+
306
+ expect(instance).to respond_to(:secret)
307
+ end
308
+
309
+ it 'is expected to return the secret' do
310
+ instance = Auth0::Algorithm::HS256.secret('secret')
311
+
312
+ expect(instance.secret).to eq('secret')
313
+ end
314
+
315
+ it 'is expected to return the algorithm name' do
316
+ instance = Auth0::Algorithm::HS256.secret('secret')
317
+
318
+ expect(instance.name).to eq('HS256')
319
+ end
320
+ end
321
+
322
+ context 'parameters' do
323
+ expected_error = 'Must supply a valid secret'
324
+
325
+ it 'is expected to raise an error with a nil secret' do
326
+ expect { Auth0::Algorithm::HS256.secret(nil) }.to raise_exception(expected_error)
327
+ end
328
+
329
+ it 'is expected to raise an error with an empty secret' do
330
+ expect { Auth0::Algorithm::HS256.secret('') }.to raise_exception(expected_error)
331
+ end
332
+ end
333
+ end
334
+
335
+ describe Auth0::Algorithm::RS256 do
336
+ before :each do
337
+ stub_jwks
338
+ end
339
+
340
+ after :each do
341
+ Auth0::Algorithm::RS256.remove_jwks
342
+ WebMock.reset!
343
+ end
344
+
345
+ context 'class' do
346
+ it 'is expected to respond to :jwks_url' do
347
+ expect(Auth0::Algorithm::RS256).to respond_to(:jwks_url)
348
+ end
349
+
350
+ it 'is expected not to respond to :new' do
351
+ expect(Auth0::Algorithm::RS256).not_to respond_to(:new)
352
+ end
353
+ end
354
+
355
+ context 'instance' do
356
+ it 'is expected to respond to :jwks' do
357
+ instance = Auth0::Algorithm::RS256.jwks_url('jwks url')
358
+
359
+ expect(instance).to respond_to(:jwks)
360
+ end
361
+
362
+ it 'is expected to respond to :fetched_jwks?' do
363
+ instance = Auth0::Algorithm::RS256.jwks_url('jwks url')
364
+
365
+ expect(instance).to respond_to(:fetched_jwks?)
366
+ end
367
+
368
+ it 'is expected to return a jwks' do
369
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
370
+
371
+ expect(instance.jwks).to have_key('keys') and contain_exactly(a_hash_including(kid: 'test-key-1'))
372
+ end
373
+
374
+ it 'is expected to return if the jwks was fetched from the url' do
375
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
376
+ instance.jwks
377
+
378
+ expect(instance.fetched_jwks?).to eq(true)
379
+ end
380
+
381
+ it 'is expected to return if the jwks was fetched from the cache' do
382
+ Auth0::Algorithm::RS256.jwks_url(JWKS_URL).jwks
383
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
384
+ instance.jwks
385
+
386
+ expect(instance.fetched_jwks?).to eq(false)
387
+ end
388
+
389
+ it 'is expected to return the algorithm name' do
390
+ instance = Auth0::Algorithm::RS256.jwks_url('jwks url')
391
+
392
+ expect(instance.name).to eq('RS256')
393
+ end
394
+ end
395
+
396
+ context 'parameters' do
397
+ it 'is expected to raise an error with a nil jwks_url' do
398
+ expect { Auth0::Algorithm::RS256.jwks_url(nil) }.to raise_exception('Must supply a valid jwks_url')
399
+ end
400
+
401
+ it 'is expected to raise an error with an empty jwks_url' do
402
+ expect { Auth0::Algorithm::RS256.jwks_url('') }.to raise_exception('Must supply a valid jwks_url')
403
+ end
404
+
405
+ it 'is expected to raise an error with a non-integer lifetime' do
406
+ expect { Auth0::Algorithm::RS256.jwks_url('jwks url', lifetime: '1') }.to raise_exception('Must supply a valid lifetime')
407
+ end
408
+
409
+ it 'is expected to raise an error with a negative lifetime' do
410
+ expect { Auth0::Algorithm::RS256.jwks_url('jwks url', lifetime: -1) }.to raise_exception('Must supply a valid lifetime')
411
+ end
412
+ end
413
+
414
+ context 'cache' do
415
+ it 'is expected to fetch the jwks from the url when the cache is empty' do
416
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
417
+ instance.jwks
418
+
419
+ expect(a_request(:get, JWKS_URL)).to have_been_made.once
420
+ end
421
+
422
+ it 'is expected to fetch the jwks from the url when the cache is expired' do
423
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL, lifetime: 0)
424
+ instance.jwks
425
+ instance.jwks
426
+
427
+ expect(a_request(:get, JWKS_URL)).to have_been_made.twice
428
+ end
429
+
430
+ it 'is not expected to fetch the jwks from the url when there is a value cached' do
431
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
432
+ instance.jwks
433
+ instance.jwks
434
+
435
+ expect(a_request(:get, JWKS_URL)).to have_been_made.once
436
+ end
437
+
438
+ it 'is expected to forcibly fetch the jwks from the url' do
439
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
440
+ instance.jwks
441
+ instance.jwks(force: true)
442
+
443
+ expect(a_request(:get, JWKS_URL)).to have_been_made.twice
444
+ end
445
+
446
+ it 'is expected to forcibly fetch the jwks from the url and cache it' do
447
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
448
+ instance.jwks(force: true)
449
+ instance.jwks
450
+
451
+ expect(a_request(:get, JWKS_URL)).to have_been_made.once
452
+ end
453
+
454
+ it 'is expected to return the last cached value if the jwks could not be fetched' do
455
+ Auth0::Algorithm::RS256.jwks_url(JWKS_URL).jwks
456
+ stub_request(:get, JWKS_URL).to_return(body: 'invalid')
457
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
458
+
459
+ expect(instance.jwks).to have_key('keys') and contain_exactly(a_hash_including(kid: 'test-key-1'))
460
+ end
461
+
462
+ it 'is expected to raise an error if the jwks could not be fetched and the cache is empty' do
463
+ stub_request(:get, JWKS_URL).to_return(body: 'invalid')
464
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
465
+
466
+ expect { instance.jwks }.to raise_exception('Could not fetch the JWK set')
467
+ end
468
+ end
469
+ end
470
+ # rubocop:enable Metrics/BlockLength
471
+
472
+ def stub_jwks(stub = JWKS_RESPONSE_1)
473
+ stub_request(:get, JWKS_URL).to_return(body: stub.to_json)
474
+ end