auth0 4.7.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.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +23 -0
  3. data/.env.example +2 -0
  4. data/.github/CODEOWNERS +1 -0
  5. data/.github/stale.yml +20 -0
  6. data/.gitignore +0 -1
  7. data/.rubocop.yml +2 -0
  8. data/CHANGELOG.md +83 -0
  9. data/DEPLOYMENT.md +25 -3
  10. data/Gemfile +3 -1
  11. data/Gemfile.lock +231 -0
  12. data/README.md +114 -25
  13. data/auth0.gemspec +6 -6
  14. data/codecov.yml +22 -0
  15. data/deploy_documentation.sh +1 -1
  16. data/lib/auth0.rb +1 -0
  17. data/lib/auth0/algorithm.rb +5 -0
  18. data/lib/auth0/api/authentication_endpoints.rb +38 -2
  19. data/lib/auth0/api/v2.rb +6 -0
  20. data/lib/auth0/api/v2/anomaly.rb +36 -0
  21. data/lib/auth0/api/v2/client_grants.rb +5 -1
  22. data/lib/auth0/api/v2/guardian.rb +142 -0
  23. data/lib/auth0/api/v2/jobs.rb +22 -3
  24. data/lib/auth0/api/v2/roles.rb +172 -0
  25. data/lib/auth0/api/v2/users.rb +115 -4
  26. data/lib/auth0/exception.rb +35 -7
  27. data/lib/auth0/mixins.rb +8 -3
  28. data/lib/auth0/mixins/httpproxy.rb +11 -8
  29. data/lib/auth0/mixins/permission_struct.rb +3 -0
  30. data/lib/auth0/mixins/validation.rb +340 -0
  31. data/lib/auth0/version.rb +1 -1
  32. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Anomaly/_check_if_ip_is_blocked/should_return_200_response_code.yml +65 -0
  33. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Anomaly/_remove_ip_block/should_remove_an_IP_successfully.yml +60 -0
  34. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_ClientGrants/_client_grants/should_return_at_least_1_result.yml +1 -1
  35. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_ClientGrants/_client_grants/should_return_the_first_page_of_one_result.yml +1 -1
  36. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_ClientGrants/_client_grants/should_return_the_test_client_grant.yml +1 -1
  37. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_ClientGrants/_delete_client_grant/should_delete_the_test_client_grant.yml +1 -1
  38. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_ClientGrants/_patch_client_grant/should_update_the_test_client_grant.yml +1 -1
  39. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_ClientGrants/create_test_client.yml +1 -1
  40. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_ClientGrants/create_test_client_grant.yml +1 -1
  41. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_ClientGrants/delete_test_client.yml +1 -1
  42. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_ClientGrants/delete_test_client_grant.yml +1 -1
  43. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_client/_filters/should_exclude_and_include_fields_properly.yml +1 -1
  44. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_client/_filters/should_include_the_specified_fields.yml +1 -1
  45. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_client/should_get_the_test_client.yml +1 -1
  46. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_clients/_filters/should_exclude_fields_not_specified.yml +1 -1
  47. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_clients/_filters/should_exclude_the_specified_fields.yml +1 -1
  48. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_clients/_filters/should_include_the_specified_fields.yml +1 -1
  49. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_clients/_filters/should_paginate_results.yml +1 -1
  50. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_clients/should_get_at_least_one_client.yml +1 -1
  51. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_delete_client/should_delete_the_test_client_without_an_error.yml +1 -1
  52. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_patch_client/should_update_the_client_with_the_correct_attributes.yml +1 -1
  53. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/create_test_client.yml +1 -1
  54. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_connection/_filters/should_exclude_the_fields_indicated.yml +1 -1
  55. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_connection/_filters/should_include_the_fields_indicated.yml +1 -1
  56. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_connection/should_find_the_correct_connection.yml +1 -1
  57. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_connections/_filters/should_include_previously-created_connection_when_filtered.yml +1 -1
  58. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_connections/_filters/should_should_exclude_the_fields_indicated_from_filtered_results.yml +1 -1
  59. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_connections/_filters/should_should_include_the_fields_indicated_from_filtered_results.yml +1 -1
  60. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_connections/should_include_the_previously_created_connection.yml +1 -1
  61. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_connections/should_not_be_empty.yml +1 -1
  62. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_delete_connection/should_delete_the_connection.yml +1 -1
  63. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_delete_connection_user/should_delete_the_user_created.yml +2 -2
  64. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_update_connection/should_update_the_connection.yml +1 -1
  65. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/create_test_connection.yml +1 -1
  66. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/create_test_user.yml +1 -1
  67. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_DeviceCredentials/create_test_user.yml +1 -1
  68. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_DeviceCredentials/delete_test_credential.yml +1 -1
  69. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_DeviceCredentials/delete_test_user.yml +1 -1
  70. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Emails/_configure_provider/should_configure_a_new_email_provider.yml +1 -1
  71. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Emails/_delete_provider/should_delete_the_existing_email_provider_without_an_error.yml +1 -1
  72. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Emails/_delete_provider/should_throw_an_error_trying_to_get_the_email_provider.yml +1 -1
  73. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Emails/_get_provider/_filters/should_get_the_existing_email_provider_with_specific_fields.yml +1 -1
  74. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Emails/_get_provider/_filters/should_get_the_existing_email_provider_without_specific_fields.yml +1 -1
  75. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Emails/_get_provider/should_get_the_existing_email_provider.yml +1 -1
  76. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Emails/_update_provider/should_update_the_existing_email_provider.yml +1 -1
  77. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Emails/delete_existing_provider.yml +1 -1
  78. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/_log/should_match_the_created_log_entry.yml +2 -2
  79. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/_log/should_not_be_empty.yml +2 -2
  80. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/_logs/_filters/should_exclude_fields_not_specified.yml +1 -1
  81. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/_logs/_filters/should_exclude_the_specified_fields.yml +1 -1
  82. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/_logs/_filters/should_have_one_log_entry.yml +1 -1
  83. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/_logs/_filters/should_include_the_specified_fields.yml +1 -1
  84. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/_logs/_from/should_take_one_log_entry.yml +2 -2
  85. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/create_test_user.yml +1 -1
  86. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/delete_test_disabled_rule.yml +1 -1
  87. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/delete_test_enabled_rule.yml +1 -1
  88. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Logs/delete_test_user.yml +1 -1
  89. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_add_role_permissions/should_add_a_Permission_to_the_Role_successfully.yml +69 -0
  90. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_add_role_users/should_add_a_User_to_the_Role_successfully.yml +69 -0
  91. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_delete_role/should_delete_the_Role_successfully.yml +62 -0
  92. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_get_role/should_get_the_Role_successfully.yml +67 -0
  93. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_get_role_permissions/should_get_exactly_1_Permission.yml +67 -0
  94. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_get_role_permissions/should_get_the_added_Permission_from_the_Role_successfully.yml +67 -0
  95. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_get_role_users/should_get_exactly_1_User.yml +67 -0
  96. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_get_role_users/should_get_the_added_User_from_the_Role_successfully.yml +67 -0
  97. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_get_roles/should_get_the_Role_successfully.yml +67 -0
  98. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_remove_role_permissions/should_remove_a_Permission_from_the_Role_successfully.yml +64 -0
  99. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/_update_role/should_update_the_Role_successfully.yml +69 -0
  100. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/create_test_api.yml +69 -0
  101. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/create_test_role.yml +69 -0
  102. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/create_test_user.yml +69 -0
  103. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/delete_test_api.yml +62 -0
  104. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Roles/delete_test_user.yml +62 -0
  105. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_delete_rule/should_delete_the_test_disabled_rule_without_an_error.yml +1 -1
  106. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_delete_rule/should_delete_the_test_enabled_rule_without_an_error.yml +1 -1
  107. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rule/_filters/should_exclude_the_fields_not_specified.yml +1 -1
  108. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rule/_filters/should_exclude_the_specified_fields.yml +1 -1
  109. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rule/_filters/should_include_the_specified_fields.yml +1 -1
  110. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rule/should_get_a_specific_rule.yml +1 -1
  111. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rules/_filters/should_exclude_fields_not_specified.yml +1 -1
  112. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rules/_filters/should_include_the_specified_fields.yml +1 -1
  113. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rules/_filters/should_return_at_least_1_disabled_rule.yml +1 -1
  114. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rules/_filters/should_return_at_least_1_enabled_rule.yml +1 -1
  115. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rules/_filters/should_return_paginated_results.yml +2 -2
  116. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_rules/should_return_at_least_1_rule.yml +1 -1
  117. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_update_rule/should_update_the_disabled_rule_to_be_enabled.yml +1 -1
  118. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/create_test_disabled_rule.yml +1 -1
  119. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/create_test_enabled_rule.yml +1 -1
  120. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Stats/_active_users/should_have_at_least_one_active_user.yml +1 -1
  121. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Stats/_daily_stats/should_have_at_least_one_stats_entry_for_the_timeframe.yml +1 -1
  122. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tenants/_get_tenant_settings/should_get_the_tenant_settings.yml +1 -1
  123. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tenants/_get_tenant_settings_with_specific_fields/should_exclude_a_field_not_requested.yml +1 -1
  124. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tenants/_get_tenant_settings_with_specific_fields/should_include_the_field_requested.yml +1 -1
  125. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tenants/_update_tenant_settings/should_revert_the_tenant_name.yml +1 -1
  126. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tenants/_update_tenant_settings/should_update_the_tenant_settings_with_a_new_tenant_name.yml +1 -1
  127. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tickets/_post_email_verification/should_create_an_email_verification_ticket.yml +1 -1
  128. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tickets/_post_password_change/should_create_a_password_change_ticket.yml +1 -1
  129. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tickets/create_test_user.yml +1 -1
  130. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tickets/delete_test_user.yml +1 -1
  131. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_add_user_permissions/should_add_a_Permissions_for_a_User_successfully.yml +67 -0
  132. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_add_user_roles/should_add_a_Role_to_a_User_successfully.yml +62 -0
  133. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_delete_user/{should_delete_the_user_successfully.yml → should_delete_the_User_successfully.yml} +14 -8
  134. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_delete_user/should_delete_the_secondary_User_successfully.yml +60 -0
  135. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_delete_user_provider/should_attempt_to_delete_the_MFA_provider_for_the_User.yml +60 -0
  136. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_get_enrollments/should_get_Enrollments_for_a_User_successfully.yml +65 -0
  137. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_get_user_permissions/should_get_exactly_1_Permission_for_a_User_successfully.yml +65 -0
  138. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_get_user_permissions/should_get_the_correct_Permission_for_a_User_successfully.yml +65 -0
  139. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_get_user_roles/should_get_Roles_for_a_User_successfully.yml +65 -0
  140. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_invalidate_browsers/should_invalidate_MFA_browsers_for_the_User_successfully.yml +62 -0
  141. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_link_user_account/should_link_two_Users_successfully.yml +67 -0
  142. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_patch_user/should_patch_the_User_successfully.yml +68 -0
  143. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_remove_user_permissions/should_remove_a_Permission_from_a_User_successfully.yml +62 -0
  144. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_remove_user_roles/should_remove_a_Role_from_a_User_successfully.yml +62 -0
  145. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_unlink_user_account/should_unlink_two_Users_successfully.yml +65 -0
  146. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_user/_filters/should_exclude_fields_not_indicated.yml +15 -10
  147. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_user/_filters/should_exclude_the_fields_indicated.yml +15 -13
  148. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_user/_filters/should_include_the_fields_indicated.yml +15 -13
  149. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_user/should_retrieve_the_created_user.yml +15 -16
  150. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_user_logs/should_get_Logs_for_a_User_successfully.yml +69 -0
  151. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_users/_filters/is_expected_to_find_a_user_with_a_v2_search_engine_query.yml +14 -8
  152. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_users/_filters/is_expected_to_find_a_user_with_a_v3_search_engine_query.yml +14 -8
  153. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_users/_filters/should_exclude_the_indicated_fields_when_paginated.yml +13 -7
  154. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_users/_filters/should_include_the_indicated_fields_when_paginated.yml +13 -7
  155. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_users/_filters/should_not_include_other_fields_when_paginated.yml +13 -7
  156. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_users/_filters/should_return_the_correct_number_of_results_when_paginated.yml +13 -7
  157. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_users/should_have_at_least_one_user.yml +13 -7
  158. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/create_secondary_test_user.yml +67 -0
  159. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/create_test_api.yml +67 -0
  160. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/create_test_role.yml +67 -0
  161. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/create_test_user.yml +15 -16
  162. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/delete_test_api.yml +60 -0
  163. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/delete_test_role.yml +60 -0
  164. data/spec/integration/lib/auth0/api/v2/api_anomaly_spec.rb +17 -0
  165. data/spec/integration/lib/auth0/api/v2/api_roles_spec.rb +145 -0
  166. data/spec/integration/lib/auth0/api/v2/api_users_spec.rb +150 -32
  167. data/spec/integration/lib/auth0/auth0_client_spec.rb +3 -4
  168. data/spec/lib/auth0/api/authentication_endpoints_spec.rb +37 -10
  169. data/spec/lib/auth0/api/v2/anomaly_spec.rb +26 -0
  170. data/spec/lib/auth0/api/v2/client_grants_spec.rb +17 -0
  171. data/spec/lib/auth0/api/v2/guardian_spec.rb +154 -0
  172. data/spec/lib/auth0/api/v2/jobs_spec.rb +33 -1
  173. data/spec/lib/auth0/api/v2/roles_spec.rb +362 -0
  174. data/spec/lib/auth0/api/v2/users_spec.rb +406 -66
  175. data/spec/lib/auth0/mixins/httpproxy_spec.rb +81 -2
  176. data/spec/lib/auth0/mixins/validation_spec.rb +474 -0
  177. data/spec/spec_helper.rb +11 -7
  178. data/spec/support/credentials.rb +4 -13
  179. data/spec/support/dummy_class.rb +1 -1
  180. data/spec/support/stub_response.rb +1 -1
  181. metadata +145 -23
  182. data/.travis.yml +0 -18
  183. data/build_travis.sh +0 -7
  184. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_patch_user/should_patch_email_verified_and_return_the_updated_data.yml +0 -68
  185. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_patch_user/should_patch_user_metadata_and_return_the_updated_user.yml +0 -69
@@ -66,8 +66,19 @@ describe Auth0::Mixins::HTTPProxy do
66
66
  expect { @instance.send(http_method, '/test') }.to raise_error(Auth0::Unsupported)
67
67
  end
68
68
 
69
+ it "should raise Auth0::RequestTimeout on send http #{http_method} method
70
+ to path defined through HTTP when RestClient::RequestTimeout received" do
71
+ allow(RestClient::Request).to receive(:execute).with(method: http_method,
72
+ url: '/test',
73
+ timeout: nil,
74
+ headers: { params: {} },
75
+ payload: nil)
76
+ .and_raise(RestClient::Exceptions::OpenTimeout.new)
77
+ expect { @instance.send(http_method, '/test') }.to raise_error(Auth0::RequestTimeout)
78
+ end
79
+
69
80
  it "should raise Auth0::BadRequest on send http #{http_method} method
70
- to path defined through HTTP when 400 or other unknown status received" do
81
+ to path defined through HTTP when 400 status received" do
71
82
  @exception.response = StubResponse.new({}, false, 400)
72
83
  allow(RestClient::Request).to receive(:execute).with(method: http_method,
73
84
  url: '/test',
@@ -89,6 +100,35 @@ describe Auth0::Mixins::HTTPProxy do
89
100
  .and_raise(@exception)
90
101
  expect { @instance.send(http_method, '/test') }.to raise_error(Auth0::AccessDenied)
91
102
  end
103
+
104
+ it "should raise Auth0::RateLimitEncountered on send http #{http_method} method
105
+ to path defined through HTTP when 429 recieved" do
106
+ headers = {
107
+ 'X-RateLimit-Limit' => 10,
108
+ 'X-RateLimit-Remaining' => 0,
109
+ 'X-RateLimit-Reset' => 1560564149
110
+ }
111
+ @exception.response = StubResponse.new({}, false, 429, headers)
112
+ allow(RestClient::Request).to receive(:execute).with(method: http_method,
113
+ url: '/test',
114
+ timeout: nil,
115
+ headers: { params: {} },
116
+ payload: nil)
117
+ .and_raise(@exception)
118
+ expect { @instance.send(http_method, '/test') }.to raise_error { |error|
119
+ expect(error).to be_a(Auth0::RateLimitEncountered)
120
+ expect(error).to have_attributes(
121
+ error_data: {
122
+ headers: headers,
123
+ code: 429
124
+ },
125
+ headers: headers,
126
+ http_code: 429,
127
+ reset: Time.at(1560564149)
128
+ )
129
+ }
130
+ end
131
+
92
132
  it "should raise Auth0::ServerError on send http #{http_method} method
93
133
  to path defined through HTTP when 500 received" do
94
134
  @exception.response = StubResponse.new({}, false, 500)
@@ -148,6 +188,34 @@ describe Auth0::Mixins::HTTPProxy do
148
188
  .and_raise(@exception)
149
189
  expect { @instance.send(http_method, '/test') }.to raise_error(Auth0::Unauthorized)
150
190
  end
191
+
192
+ it "should raise Auth0::RateLimitEncountered on send http #{http_method} method
193
+ to path defined through HTTP when 429 status received" do
194
+ headers = {
195
+ 'X-RateLimit-Limit' => 10,
196
+ 'X-RateLimit-Remaining' => 0,
197
+ 'X-RateLimit-Reset' => 1560564149
198
+ }
199
+ @exception.response = StubResponse.new({}, false, 429,headers)
200
+ allow(RestClient::Request).to receive(:execute).with(method: http_method,
201
+ url: '/test',
202
+ timeout: nil,
203
+ headers: nil,
204
+ payload: '{}')
205
+ .and_raise(@exception)
206
+ expect { @instance.send(http_method, '/test') }.to raise_error { |error|
207
+ expect(error).to be_a(Auth0::RateLimitEncountered)
208
+ expect(error).to have_attributes(
209
+ error_data: {
210
+ headers: headers,
211
+ code: 429
212
+ },
213
+ headers: headers,
214
+ http_code: 429,
215
+ reset: Time.at(1560564149)
216
+ )
217
+ }
218
+ end
151
219
 
152
220
  it "should raise Auth0::NotFound on send http #{http_method} method
153
221
  to path defined through HTTP when 404 status received" do
@@ -173,8 +241,19 @@ describe Auth0::Mixins::HTTPProxy do
173
241
  expect { @instance.send(http_method, '/test') }.to raise_error(Auth0::Unsupported)
174
242
  end
175
243
 
244
+ it "should raise Auth0::RequestTimeout on send http #{http_method} method
245
+ to path defined through HTTP when RestClient::RequestTimeout received" do
246
+ allow(RestClient::Request).to receive(:execute).with(method: http_method,
247
+ url: '/test',
248
+ timeout: nil,
249
+ headers: nil,
250
+ payload: '{}')
251
+ .and_raise(RestClient::Exceptions::OpenTimeout.new)
252
+ expect { @instance.send(http_method, '/test') }.to raise_error(Auth0::RequestTimeout)
253
+ end
254
+
176
255
  it "should raise Auth0::BadRequest on send http #{http_method} method
177
- to path defined through HTTP when 400 or other unknown status received" do
256
+ to path defined through HTTP when 400 status received" do
178
257
  @exception.response = StubResponse.new({}, false, 400)
179
258
  allow(RestClient::Request).to receive(:execute).with(method: http_method,
180
259
  url: '/test',
@@ -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