auth0 4.10.0 → 4.15.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.10.0'.freeze
3
+ VERSION = '4.15.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',
@@ -355,7 +354,8 @@ describe Auth0::Api::AuthenticationEndpoints do
355
354
  expect(@instance).to receive(:post).with(
356
355
  '/passwordless/start',
357
356
  client_id: @instance.client_id,
358
- connection: 'email',
357
+ client_secret: @instance.client_secret,
358
+ connection: 'email',
359
359
  email: 'test@test.com',
360
360
  send: 'code',
361
361
  authParams: {
@@ -388,6 +388,7 @@ describe Auth0::Api::AuthenticationEndpoints do
388
388
  expect(@instance).to receive(:post).with(
389
389
  '/passwordless/start',
390
390
  client_id: @instance.client_id,
391
+ client_secret: @instance.client_secret,
391
392
  connection: 'sms',
392
393
  phone_number: phone_number
393
394
  )
@@ -550,7 +551,7 @@ describe Auth0::Api::AuthenticationEndpoints do
550
551
  context '.userinfo' do
551
552
  it { is_expected.to respond_to(:user_info) }
552
553
  it 'is expected to make a GET request to /userinfo' do
553
- is_expected.to receive(:get).with('/userinfo', {}, {'Authorization' => 'Bearer access-token'})
554
+ is_expected.to receive(:get).with('/userinfo', {}, { 'Authorization' => 'Bearer access-token' })
554
555
  subject.userinfo 'access-token'
555
556
  end
556
557
  end
@@ -603,7 +604,7 @@ describe Auth0::Api::AuthenticationEndpoints do
603
604
 
604
605
  it 'is expected to return a logout url with a client ID' do
605
606
  expect(@instance.logout_url(return_to, include_client: true).to_s).to eq(
606
- "https://#{@instance.domain}/v2/logout" +
607
+ "https://#{@instance.domain}/v2/logout" \
607
608
  "?returnTo=http%3A%2F%2Freturnto.com&client_id=#{@instance.client_id}"
608
609
  )
609
610
  end
@@ -653,7 +654,7 @@ describe Auth0::Api::AuthenticationEndpoints do
653
654
  end
654
655
 
655
656
  it 'is expected to get the wsfed url with wctx' do
656
- 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(
657
658
  "https://#{@instance.domain}/wsfed/#{@instance.client_id}" \
658
659
  "?whr=#{UP_AUTH}&wctx=wctx_test"
659
660
  )
@@ -672,5 +673,31 @@ describe Auth0::Api::AuthenticationEndpoints do
672
673
  )
673
674
  end
674
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
675
702
  end
676
703
  # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ describe Auth0::Api::V2::LogStreams do
3
+ before :all do
4
+ dummy_instance = DummyClass.new
5
+ dummy_instance.extend(Auth0::Api::V2::LogStreams)
6
+ dummy_instance.extend(Auth0::Mixins::Initializer)
7
+ @instance = dummy_instance
8
+ end
9
+
10
+ context '.log_streams' do
11
+ it { expect(@instance).to respond_to(:log_streams) }
12
+ it { expect(@instance).to respond_to(:get_log_streams) }
13
+ it 'is expected to call get /api/v2/log-streams' do
14
+ expect(@instance).to receive(:get).with(
15
+ '/api/v2/log-streams'
16
+ )
17
+ expect { @instance.log_streams }.not_to raise_error
18
+ end
19
+ end
20
+
21
+ context '.log_stream' do
22
+ it { expect(@instance).to respond_to(:log_stream) }
23
+ it 'is expected to call get /api/v2/log-streams/test' do
24
+ expect(@instance).to receive(:get).with('/api/v2/log-streams/test')
25
+ expect { @instance.log_stream('test') }.not_to raise_error
26
+ end
27
+ it 'expect to raise an error when calling with empty log stream id' do
28
+ expect { @instance.log_stream(nil) }.to raise_error 'Must supply a valid log stream id'
29
+ end
30
+ end
31
+
32
+ context '.create_log_stream' do
33
+ it { expect(@instance).to respond_to(:create_log_stream) }
34
+ it 'is expected to call post /api/v2/log-streams' do
35
+ expect(@instance).to receive(:post).with(
36
+ '/api/v2/log-streams',
37
+ name: 'test',
38
+ type: 'https',
39
+ sink: {
40
+ httpEndpoint: "https://mycompany.com",
41
+ httpContentType: "string",
42
+ httpContentFormat: "JSONLINES",
43
+ httpAuthorization: "string"
44
+ }
45
+ )
46
+
47
+ @instance.create_log_stream('test', 'https',
48
+ httpEndpoint: "https://mycompany.com",
49
+ httpContentType: "string",
50
+ httpContentFormat: "JSONLINES",
51
+ httpAuthorization: "string")
52
+ end
53
+ it 'expect to raise an error when calling with empty name' do
54
+ expect { @instance.create_log_stream('', '', '') }.to raise_error 'Name must contain at least one character'
55
+ end
56
+ it 'expect to raise an error when calling with empty type' do
57
+ expect { @instance.create_log_stream('name', '', '') }.to raise_error 'Must specify a valid type'
58
+ end
59
+ it 'expect to raise an error when calling without options' do
60
+ expect { @instance.create_log_stream('name', 'https', nil) }.to raise_error 'Must supply a valid hash for options'
61
+ end
62
+ end
63
+
64
+ context '.delete_log_stream' do
65
+ it { expect(@instance).to respond_to(:delete_log_stream) }
66
+ it 'is expected to call delete /api/v2/log-streams/test' do
67
+ expect(@instance).to receive(:delete).with('/api/v2/log-streams/test')
68
+ expect { @instance.delete_log_stream('test') }.not_to raise_error
69
+ end
70
+ it 'expect to raise an error when calling with empty log stream id' do
71
+ expect { @instance.delete_log_stream(nil) }.to raise_error 'Must supply a valid log stream id'
72
+ end
73
+ end
74
+
75
+ context '.patch_log_stream' do
76
+ it { expect(@instance).to respond_to(:patch_log_stream) }
77
+ it 'is expected to send patch to /api/v2/log_streams/test' do
78
+ expect(@instance).to receive(:patch).with('/api/v2/log-streams/test', status: 'paused')
79
+ expect { @instance.patch_log_stream('test', 'paused') }.not_to raise_error
80
+ end
81
+ it { expect { @instance.patch_log_stream('', nil) }.to raise_error 'Must specify a log stream id' }
82
+ it { expect { @instance.patch_log_stream('test', nil) }.to raise_error 'Must specify a valid status' }
83
+ end
84
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+ describe Auth0::Api::V2::Prompts do
3
+ before :all do
4
+ dummy_instance = DummyClass.new
5
+ dummy_instance.extend(Auth0::Api::V2::Prompts)
6
+ @instance = dummy_instance
7
+ end
8
+
9
+ context '.prompts' do
10
+ it 'is expected to respond to a prompts method' do
11
+ expect(@instance).to respond_to(:prompts)
12
+ end
13
+
14
+ it 'is expected to respond to a get_prompts method' do
15
+ expect(@instance).to respond_to(:get_prompts)
16
+ end
17
+
18
+ it 'is expected to get /api/v2/prompts' do
19
+ expect(@instance).to receive(:get).with('/api/v2/prompts')
20
+
21
+ expect { @instance.prompts }.not_to raise_error
22
+ end
23
+ end
24
+
25
+ context '.update_prompts' do
26
+ it 'is expected to respond to an update_prompts method' do
27
+ expect(@instance).to respond_to(:update_prompts)
28
+ end
29
+
30
+ it 'is expected to respond to a patch_prompts method' do
31
+ expect(@instance).to respond_to(:patch_prompts)
32
+ end
33
+
34
+ it 'is expected to call patch /api/v2/prompts' do
35
+ expect(@instance).to receive(:patch).with('/api/v2/prompts', { universal_login_experience: 'new' })
36
+
37
+ expect { @instance.update_prompts(universal_login_experience: 'new') }.not_to raise_error
38
+ end
39
+ end
40
+
41
+ context '.custom_text' do
42
+ it 'is expected to respond to a custom_text method' do
43
+ expect(@instance).to respond_to(:custom_text)
44
+ end
45
+
46
+ it 'is expected to respond to a get_custom_text method' do
47
+ expect(@instance).to respond_to(:get_custom_text)
48
+ end
49
+
50
+ it 'is expected to get /api/v2/prompts/PROMPT_NAME/language/LANGUAGE' do
51
+ expect(@instance).to receive(:get).with('/api/v2/prompts/PROMPT_NAME/custom-text/LANGUAGE')
52
+
53
+ expect { @instance.custom_text('PROMPT_NAME', 'LANGUAGE') }.not_to raise_error
54
+ end
55
+
56
+ it 'is expected to raise an exception when the prompt is empty' do
57
+ expect { @instance.custom_text(nil, 'language') }.to raise_exception(Auth0::InvalidParameter)
58
+ end
59
+
60
+ it 'is expected to raise an exception when the language is empty' do
61
+ expect { @instance.custom_text('prompt_name', nil) }.to raise_exception(Auth0::InvalidParameter)
62
+ end
63
+ end
64
+
65
+ context '.update_custom_text' do
66
+ it 'is expected to respond to an update_custom_text method' do
67
+ expect(@instance).to respond_to(:update_custom_text)
68
+ end
69
+
70
+ it 'is expected to respond to a put_custom_text method' do
71
+ expect(@instance).to respond_to(:put_custom_text)
72
+ end
73
+
74
+ it 'is expected to call put /api/v2/prompts/PROMPT_NAME/language/LANGUAGE' do
75
+ expect(@instance).to receive(:put).with('/api/v2/prompts/PROMPT_NAME/custom-text/LANGUAGE', 'BODY')
76
+
77
+ expect { @instance.update_custom_text('PROMPT_NAME', 'LANGUAGE', 'BODY') }.not_to raise_error
78
+ end
79
+
80
+ it 'is expected to raise an exception when the prompt is empty' do
81
+ expect { @instance.update_custom_text(nil,'language', 'BODY') }.to raise_exception(Auth0::InvalidParameter)
82
+ end
83
+
84
+ it 'is expected to raise an exception when the language is empty' do
85
+ expect { @instance.update_custom_text('prompt_name', nil, 'BODY') }.to raise_exception(Auth0::InvalidParameter)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,466 @@
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 not to raise an error with a missing but not required nonce' do
233
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.-o5grnyODbBdRgzcrn7Sf9Hb6eOC0x_U2i3YjVgHN0U'
234
+
235
+ expect { @instance.validate(token) }.not_to raise_exception
236
+ end
237
+
238
+ it 'is expected to raise an error with a missing but required nonce' do
239
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.-o5grnyODbBdRgzcrn7Sf9Hb6eOC0x_U2i3YjVgHN0U'
240
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ nonce: 'a1b2c3d4e5' }))
241
+
242
+ expect { instance.validate(token) }.to raise_exception('Nonce (nonce) claim must be a string present in the ID token')
243
+ end
244
+
245
+ it 'is expected to raise an error with an invalid nonce' do
246
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiMDAwOTk5IiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.XqQPdFN4m5kmTUQQi_mAJu0LQOeUTS9lF2J_xWc_j-0'
247
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ nonce: 'a1b2c3d4e5' }))
248
+
249
+ expect { instance.validate(token) }.to raise_exception('Nonce (nonce) claim mismatch in the ID token; expected "a1b2c3d4e5", found "000999"')
250
+ end
251
+
252
+ it 'is expected to raise an error with a missing azp' do
253
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.LrgYkIbWrxMq6jvvkL1lxWL237ii1IBhtN2o_tDxFns'
254
+
255
+ expect { @instance.validate(token) }.to raise_exception('Authorized Party (azp) claim must be a string present in the ID token')
256
+ end
257
+
258
+ it 'is expected to raise an error with an invalid azp' do
259
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6ImV4dGVybmFsLXRlc3QtOTk5IiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.3DX-LY3B4UngDML-9nv11Sv89ECJpRpOLeWnkF1vAFY'
260
+
261
+ expect { @instance.validate(token) }.to raise_exception("Authorized Party (azp) claim mismatch in the ID token; expected \"tokens-test-123\", found \"external-test-999\"")
262
+ end
263
+
264
+ it 'is expected to raise an error with a missing auth_time' do
265
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyJ9.JqUotnjHbGW0FcHz1s1YsRkce9Sbpsv2AEBDMpcUhp8'
266
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ max_age: 120 }))
267
+
268
+ 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')
269
+ end
270
+
271
+ it 'is expected to raise an error with a invalid auth_time' do
272
+ token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzU5MjU2MX0.B7eWHJPHjhOh0ALjIQi0ro6zVsqGIeHd0gpRZsv6Hg8'
273
+ max_age = 120
274
+ auth_time = CLOCK + LEEWAY + max_age
275
+ clock = auth_time + 1
276
+ instance = Auth0::Mixins::Validation::IdTokenValidator.new(CONTEXT.merge({ max_age: max_age, clock: clock }))
277
+
278
+ 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}\"")
279
+ end
280
+ end
281
+ end
282
+
283
+ describe Auth0::Algorithm::HS256 do
284
+ context 'class' do
285
+ it 'is expected to respond to :secret' do
286
+ expect(Auth0::Algorithm::HS256).to respond_to(:secret)
287
+ end
288
+
289
+ it 'is expected not to respond to :new' do
290
+ expect(Auth0::Algorithm::HS256).not_to respond_to(:new)
291
+ end
292
+ end
293
+
294
+ context 'instance' do
295
+ it 'is expected to respond to :secret' do
296
+ instance = Auth0::Algorithm::HS256.secret('secret')
297
+
298
+ expect(instance).to respond_to(:secret)
299
+ end
300
+
301
+ it 'is expected to return the secret' do
302
+ instance = Auth0::Algorithm::HS256.secret('secret')
303
+
304
+ expect(instance.secret).to eq('secret')
305
+ end
306
+
307
+ it 'is expected to return the algorithm name' do
308
+ instance = Auth0::Algorithm::HS256.secret('secret')
309
+
310
+ expect(instance.name).to eq('HS256')
311
+ end
312
+ end
313
+
314
+ context 'parameters' do
315
+ expected_error = 'Must supply a valid secret'
316
+
317
+ it 'is expected to raise an error with a nil secret' do
318
+ expect { Auth0::Algorithm::HS256.secret(nil) }.to raise_exception(expected_error)
319
+ end
320
+
321
+ it 'is expected to raise an error with an empty secret' do
322
+ expect { Auth0::Algorithm::HS256.secret('') }.to raise_exception(expected_error)
323
+ end
324
+ end
325
+ end
326
+
327
+ describe Auth0::Algorithm::RS256 do
328
+ before :each do
329
+ stub_jwks
330
+ end
331
+
332
+ after :each do
333
+ Auth0::Algorithm::RS256.remove_jwks
334
+ WebMock.reset!
335
+ end
336
+
337
+ context 'class' do
338
+ it 'is expected to respond to :jwks_url' do
339
+ expect(Auth0::Algorithm::RS256).to respond_to(:jwks_url)
340
+ end
341
+
342
+ it 'is expected not to respond to :new' do
343
+ expect(Auth0::Algorithm::RS256).not_to respond_to(:new)
344
+ end
345
+ end
346
+
347
+ context 'instance' do
348
+ it 'is expected to respond to :jwks' do
349
+ instance = Auth0::Algorithm::RS256.jwks_url('jwks url')
350
+
351
+ expect(instance).to respond_to(:jwks)
352
+ end
353
+
354
+ it 'is expected to respond to :fetched_jwks?' do
355
+ instance = Auth0::Algorithm::RS256.jwks_url('jwks url')
356
+
357
+ expect(instance).to respond_to(:fetched_jwks?)
358
+ end
359
+
360
+ it 'is expected to return a jwks' do
361
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
362
+
363
+ expect(instance.jwks).to have_key('keys') and contain_exactly(a_hash_including(kid: 'test-key-1'))
364
+ end
365
+
366
+ it 'is expected to return if the jwks was fetched from the url' do
367
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
368
+ instance.jwks
369
+
370
+ expect(instance.fetched_jwks?).to eq(true)
371
+ end
372
+
373
+ it 'is expected to return if the jwks was fetched from the cache' do
374
+ Auth0::Algorithm::RS256.jwks_url(JWKS_URL).jwks
375
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
376
+ instance.jwks
377
+
378
+ expect(instance.fetched_jwks?).to eq(false)
379
+ end
380
+
381
+ it 'is expected to return the algorithm name' do
382
+ instance = Auth0::Algorithm::RS256.jwks_url('jwks url')
383
+
384
+ expect(instance.name).to eq('RS256')
385
+ end
386
+ end
387
+
388
+ context 'parameters' do
389
+ it 'is expected to raise an error with a nil jwks_url' do
390
+ expect { Auth0::Algorithm::RS256.jwks_url(nil) }.to raise_exception('Must supply a valid jwks_url')
391
+ end
392
+
393
+ it 'is expected to raise an error with an empty jwks_url' do
394
+ expect { Auth0::Algorithm::RS256.jwks_url('') }.to raise_exception('Must supply a valid jwks_url')
395
+ end
396
+
397
+ it 'is expected to raise an error with a non-integer lifetime' do
398
+ expect { Auth0::Algorithm::RS256.jwks_url('jwks url', lifetime: '1') }.to raise_exception('Must supply a valid lifetime')
399
+ end
400
+
401
+ it 'is expected to raise an error with a negative lifetime' do
402
+ expect { Auth0::Algorithm::RS256.jwks_url('jwks url', lifetime: -1) }.to raise_exception('Must supply a valid lifetime')
403
+ end
404
+ end
405
+
406
+ context 'cache' do
407
+ it 'is expected to fetch the jwks from the url when the cache is empty' do
408
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
409
+ instance.jwks
410
+
411
+ expect(a_request(:get, JWKS_URL)).to have_been_made.once
412
+ end
413
+
414
+ it 'is expected to fetch the jwks from the url when the cache is expired' do
415
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL, lifetime: 0)
416
+ instance.jwks
417
+ instance.jwks
418
+
419
+ expect(a_request(:get, JWKS_URL)).to have_been_made.twice
420
+ end
421
+
422
+ it 'is not expected to fetch the jwks from the url when there is a value cached' do
423
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
424
+ instance.jwks
425
+ instance.jwks
426
+
427
+ expect(a_request(:get, JWKS_URL)).to have_been_made.once
428
+ end
429
+
430
+ it 'is expected to forcibly fetch the jwks from the url' do
431
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
432
+ instance.jwks
433
+ instance.jwks(force: true)
434
+
435
+ expect(a_request(:get, JWKS_URL)).to have_been_made.twice
436
+ end
437
+
438
+ it 'is expected to forcibly fetch the jwks from the url and cache it' do
439
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
440
+ instance.jwks(force: true)
441
+ instance.jwks
442
+
443
+ expect(a_request(:get, JWKS_URL)).to have_been_made.once
444
+ end
445
+
446
+ it 'is expected to return the last cached value if the jwks could not be fetched' do
447
+ Auth0::Algorithm::RS256.jwks_url(JWKS_URL).jwks
448
+ stub_request(:get, JWKS_URL).to_return(body: 'invalid')
449
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
450
+
451
+ expect(instance.jwks).to have_key('keys') and contain_exactly(a_hash_including(kid: 'test-key-1'))
452
+ end
453
+
454
+ it 'is expected to raise an error if the jwks could not be fetched and the cache is empty' do
455
+ stub_request(:get, JWKS_URL).to_return(body: 'invalid')
456
+ instance = Auth0::Algorithm::RS256.jwks_url(JWKS_URL)
457
+
458
+ expect { instance.jwks }.to raise_exception('Could not fetch the JWK set')
459
+ end
460
+ end
461
+ end
462
+ # rubocop:enable Metrics/BlockLength
463
+
464
+ def stub_jwks(stub = JWKS_RESPONSE_1)
465
+ stub_request(:get, JWKS_URL).to_return(body: stub.to_json)
466
+ end