two_factor_spec.rb 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Auth::TwoFactor, current_user_id: 1 do
  4. let(:user) { create(:user) }
  5. let(:instance) { described_class.new(user) }
  6. before do
  7. Setting.set('two_factor_authentication_method_authenticator_app', true)
  8. end
  9. shared_examples 'responding to provided instance method' do |method|
  10. it "responds to '.#{method}'" do
  11. expect(instance).to respond_to(method)
  12. end
  13. end
  14. it_behaves_like 'responding to provided instance method', :enabled?
  15. it_behaves_like 'responding to provided instance method', :available_authentication_methods
  16. it_behaves_like 'responding to provided instance method', :enabled_authentication_methods
  17. it_behaves_like 'responding to provided instance method', :verify?
  18. it_behaves_like 'responding to provided instance method', :verify_configuration?
  19. it_behaves_like 'responding to provided instance method', :all_authentication_methods
  20. it_behaves_like 'responding to provided instance method', :authentication_method_object
  21. it_behaves_like 'responding to provided instance method', :user_authentication_methods
  22. it_behaves_like 'responding to provided instance method', :user_default_authentication_method
  23. it_behaves_like 'responding to provided instance method', :user_setup_required?
  24. it_behaves_like 'responding to provided instance method', :user_configured?
  25. shared_examples 'returning expected value' do |method, value, assertion: 'be'|
  26. context 'when checking array value', if: assertion == 'include' do
  27. it "returns expected value for '##{method}'" do
  28. expect(instance.method(method).call).to include(value)
  29. end
  30. end
  31. context 'when checking array value', if: assertion == 'be' do
  32. it "returns expected value for '##{method}'" do
  33. expect(instance.method(method).call).to be(value)
  34. end
  35. end
  36. end
  37. it_behaves_like 'returning expected value', :enabled?, true
  38. it_behaves_like 'returning expected value', :available_authentication_methods, Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp, assertion: 'include'
  39. it_behaves_like 'returning expected value', :enabled_authentication_methods, Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp, assertion: 'include'
  40. it_behaves_like 'returning expected value', :all_authentication_methods, Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp, assertion: 'include'
  41. describe '#method_object' do
  42. before { create(:user_two_factor_preference, :authenticator_app, user: user) }
  43. it 'returns expected value' do
  44. expect(instance.authentication_method_object('authenticator_app')).to be_a(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  45. end
  46. end
  47. describe '#user_methods' do
  48. before { create(:user_two_factor_preference, :authenticator_app, user: user) }
  49. it 'returns expected value' do
  50. expect(instance.user_authentication_methods).to include(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  51. end
  52. end
  53. describe '#user_default_method' do
  54. before { create(:user_two_factor_preference, :authenticator_app, user: user) }
  55. it 'returns expected value' do
  56. # 'user' variable is cached + was created before the preference was set.
  57. user.reload
  58. expect(instance.user_default_authentication_method).to be_a(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  59. end
  60. end
  61. describe '#user_setup_required' do
  62. let(:user_role) { create(:role, :agent) }
  63. let(:non_user_role) { create(:role, :agent) }
  64. let(:user) { create(:user, roles: [user_role]) }
  65. context 'when the setup is required' do
  66. before do
  67. Setting.set('two_factor_authentication_enforce_role_ids', [user_role.id])
  68. end
  69. it 'returns expected value' do
  70. expect(instance.user_setup_required?).to be(true)
  71. end
  72. end
  73. context 'when the setup is not required' do
  74. before do
  75. Setting.set('two_factor_authentication_enforce_role_ids', [non_user_role.id])
  76. end
  77. it 'returns expected value' do
  78. expect(instance.user_setup_required?).to be(false)
  79. end
  80. end
  81. end
  82. describe '#user_configured' do
  83. before do
  84. Setting.set('two_factor_authentication_method_authenticator_app', true)
  85. end
  86. shared_examples 'recovery codes present' do |configured|
  87. before do
  88. Auth::TwoFactor::RecoveryCodes.new(user).generate
  89. end
  90. it 'returns expected value' do
  91. expect(instance.user_configured?).to be(configured)
  92. end
  93. end
  94. context 'when a method is configured' do
  95. before do
  96. create(:user_two_factor_preference, :authenticator_app, user: user)
  97. # 'user' variable is cached + was created before the preference was set.
  98. user.reload
  99. end
  100. it 'returns expected value' do
  101. expect(instance.user_configured?).to be(true)
  102. end
  103. it_behaves_like 'recovery codes present', true
  104. end
  105. context 'when a method is not configured' do
  106. before { user.reload }
  107. it 'returns expected value' do
  108. expect(instance.user_configured?).to be(false)
  109. end
  110. it_behaves_like 'recovery codes present', false
  111. end
  112. end
  113. describe '#verify?' do
  114. let(:secret) { ROTP::Base32.random_base32 }
  115. let(:last_otp_at) { 1_256_953_732 } # 2009-10-31T01:48:52Z
  116. let(:two_factor_pref) do
  117. create(:user_two_factor_preference, :authenticator_app,
  118. user: user,
  119. method: method,
  120. configuration: configuration)
  121. end
  122. let(:configuration) do
  123. {
  124. last_otp_at: last_otp_at,
  125. secret: secret,
  126. }
  127. end
  128. before { two_factor_pref }
  129. context 'with authenticator app as method' do
  130. let(:method) { 'authenticator_app' }
  131. let(:code) { ROTP::TOTP.new(secret).now }
  132. shared_examples 'returning true result' do
  133. it 'returns true result' do
  134. result = instance.verify?(method, code)
  135. expect(result).to be true
  136. end
  137. it 'updates last otp at timestamp' do
  138. instance.verify?(method, code)
  139. expect(user.two_factor_preferences.find_by(method: method).configuration[:last_otp_at]).to be > last_otp_at
  140. end
  141. end
  142. shared_examples 'returning false result' do
  143. it 'returns false result' do
  144. result = instance.verify?(method, code)
  145. expect(result).to be false
  146. end
  147. end
  148. context 'with valid code provided' do
  149. let(:code) { ROTP::TOTP.new(secret).now }
  150. it_behaves_like 'returning true result'
  151. end
  152. context 'with invalid code provided' do
  153. let(:code) { 'FOOBAR' }
  154. it_behaves_like 'returning false result'
  155. end
  156. context 'with no configured secret' do
  157. let(:code) { ROTP::TOTP.new(secret).now }
  158. let(:configuration) do
  159. {
  160. foo: 'bar',
  161. }
  162. end
  163. it_behaves_like 'returning false result'
  164. end
  165. context 'with no configured method' do
  166. let(:code) { ROTP::TOTP.new(secret).now }
  167. let(:configuration) { nil }
  168. it_behaves_like 'returning false result'
  169. end
  170. context 'with used recovery code' do
  171. let(:method) { 'recovery_codes' }
  172. let(:current_codes) { Auth::TwoFactor::RecoveryCodes.new(user).generate }
  173. let(:code) { current_codes.first }
  174. let(:two_factor_pref) { nil }
  175. before do
  176. current_codes
  177. end
  178. it 'returns true result' do
  179. expect(instance.verify?(method, code)).to be true
  180. end
  181. context 'with invalid code provided' do
  182. let(:code) { 'wrong' }
  183. it 'returns false result' do
  184. expect(instance.verify?(method, code)).to be false
  185. end
  186. end
  187. end
  188. end
  189. end
  190. end