two_factor_spec.rb 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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. describe '#all_authentication_methods' do
  10. it 'returns all methods, including disabled and not setup for user' do
  11. expect(instance.all_authentication_methods.map { |elem| elem.class.name })
  12. .to eq([
  13. Auth::TwoFactor::AuthenticationMethod::SecurityKeys.name,
  14. Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp.name,
  15. ])
  16. end
  17. it 'returns instance for current user' do
  18. expect(instance.all_authentication_methods.first).to have_attributes(user: user)
  19. end
  20. end
  21. describe '.authentication_method_classes' do
  22. it 'returns sorted methods' do
  23. expect(described_class.authentication_method_classes)
  24. .to eq([
  25. Auth::TwoFactor::AuthenticationMethod::SecurityKeys,
  26. Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp,
  27. ])
  28. end
  29. end
  30. describe '#enabled_authentication_methods' do
  31. it 'returns only enabled method' do
  32. expect(instance.enabled_authentication_methods)
  33. .to contain_exactly(be_a(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp))
  34. end
  35. end
  36. describe '#available_authentication_methods' do
  37. it 'returns available methods' do
  38. expect(instance.available_authentication_methods)
  39. .to contain_exactly(be_a(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp))
  40. end
  41. context 'when enabled method is not available' do
  42. before do
  43. allow_any_instance_of(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  44. .to receive(:available?)
  45. .and_return(false)
  46. end
  47. it 'returns available methods' do
  48. expect(instance.available_authentication_methods).to be_empty
  49. end
  50. end
  51. end
  52. describe '#enabled?' do
  53. it 'returns true' do
  54. expect(instance).to be_enabled
  55. end
  56. context 'without enabled methods' do
  57. before do
  58. Setting.set('two_factor_authentication_method_authenticator_app', false)
  59. end
  60. it 'returns false' do
  61. expect(instance).not_to be_enabled
  62. end
  63. end
  64. end
  65. describe '#verify_configuration?' do
  66. it 'returns false if invalid method name given' do
  67. expect(instance)
  68. .not_to be_verify_configuration('nonexistantmethod', {}, {})
  69. end
  70. it 'returns false if invalid payload or configuration given' do
  71. expect(instance)
  72. .not_to be_verify_configuration('authenticator_app', {}, {})
  73. end
  74. context 'when payload and configuration are valid' do
  75. before do
  76. allow_any_instance_of(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  77. .to receive(:verify)
  78. .and_return({ config: :yes, verified: true })
  79. end
  80. it 'returns true' do
  81. expect(instance)
  82. .to be_verify_configuration('authenticator_app', {}, {})
  83. end
  84. it 'creates uer configuration' do
  85. expect_any_instance_of(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  86. .to receive(:create_user_config)
  87. .with({ config: :yes })
  88. instance.verify_configuration?('authenticator_app', {}, {})
  89. end
  90. end
  91. end
  92. describe '#authentication_method_object' do
  93. before { create(:user_two_factor_preference, :authenticator_app, user: user) }
  94. it 'returns expected value' do
  95. expect(instance.authentication_method_object('authenticator_app')).to be_a(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  96. end
  97. end
  98. describe '#user_authentication_methods' do
  99. before { create(:user_two_factor_preference, :authenticator_app, user: user) }
  100. it 'returns expected value' do
  101. expect(instance.user_authentication_methods).to include(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  102. end
  103. end
  104. describe '#user_default_method' do
  105. before { create(:user_two_factor_preference, :authenticator_app, user: user) }
  106. it 'returns expected value' do
  107. # 'user' variable is cached + was created before the preference was set.
  108. user.reload
  109. expect(instance.user_default_authentication_method).to be_a(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  110. end
  111. context 'when two methods exist' do
  112. let(:another_method) { create(:user_two_factor_preference, :security_keys, user: user) }
  113. before do
  114. Setting.set('two_factor_authentication_method_security_keys', true)
  115. another_method
  116. user.reload
  117. user.preferences[:two_factor_authentication][:default] = 'security_keys'
  118. user.save!
  119. end
  120. it 'returns selected method' do
  121. user.reload
  122. expect(instance.user_default_authentication_method)
  123. .to be_a(Auth::TwoFactor::AuthenticationMethod::SecurityKeys)
  124. end
  125. context 'when default method disabled' do
  126. before do
  127. Setting.set('two_factor_authentication_method_security_keys', false)
  128. end
  129. it 'returns another method' do
  130. user.reload
  131. expect(instance.user_default_authentication_method)
  132. .to be_a(Auth::TwoFactor::AuthenticationMethod::AuthenticatorApp)
  133. end
  134. end
  135. end
  136. end
  137. describe '#user_setup_required' do
  138. let(:user_role) { create(:role, :agent) }
  139. let(:non_user_role) { create(:role, :agent) }
  140. let(:user) { create(:user, roles: [user_role]) }
  141. context 'when the setup is required' do
  142. before do
  143. Setting.set('two_factor_authentication_enforce_role_ids', [user_role.id])
  144. end
  145. it 'returns expected value' do
  146. expect(instance.user_setup_required?).to be(true)
  147. end
  148. end
  149. context 'when the setup is not required' do
  150. before do
  151. Setting.set('two_factor_authentication_enforce_role_ids', [non_user_role.id])
  152. end
  153. it 'returns expected value' do
  154. expect(instance.user_setup_required?).to be(false)
  155. end
  156. end
  157. end
  158. describe '#user_configured' do
  159. before do
  160. Setting.set('two_factor_authentication_method_authenticator_app', true)
  161. end
  162. shared_examples 'recovery codes present' do |configured|
  163. before do
  164. Auth::TwoFactor::RecoveryCodes.new(user).generate
  165. end
  166. it 'returns expected value' do
  167. expect(instance.user_configured?).to be(configured)
  168. end
  169. end
  170. context 'when a method is configured' do
  171. before do
  172. create(:user_two_factor_preference, :authenticator_app, user: user)
  173. # 'user' variable is cached + was created before the preference was set.
  174. user.reload
  175. end
  176. it 'returns expected value' do
  177. expect(instance.user_configured?).to be(true)
  178. end
  179. it_behaves_like 'recovery codes present', true
  180. end
  181. context 'when a method is not configured' do
  182. before { user.reload }
  183. it 'returns expected value' do
  184. expect(instance.user_configured?).to be(false)
  185. end
  186. it_behaves_like 'recovery codes present', false
  187. end
  188. end
  189. describe '#verify?' do
  190. let(:secret) { ROTP::Base32.random_base32 }
  191. let(:last_otp_at) { 1_256_953_732 } # 2009-10-31T01:48:52Z
  192. let(:two_factor_pref) do
  193. create(:user_two_factor_preference, :authenticator_app,
  194. user: user,
  195. method: method,
  196. configuration: configuration)
  197. end
  198. let(:configuration) do
  199. {
  200. last_otp_at: last_otp_at,
  201. secret: secret,
  202. }
  203. end
  204. before { two_factor_pref }
  205. context 'with authenticator app as method' do
  206. let(:method) { 'authenticator_app' }
  207. let(:code) { ROTP::TOTP.new(secret).now }
  208. shared_examples 'returning true result' do
  209. it 'returns true result' do
  210. result = instance.verify?(method, code)
  211. expect(result).to be true
  212. end
  213. it 'updates last otp at timestamp' do
  214. instance.verify?(method, code)
  215. expect(user.two_factor_preferences.find_by(method: method).configuration[:last_otp_at]).to be > last_otp_at
  216. end
  217. end
  218. shared_examples 'returning false result' do
  219. it 'returns false result' do
  220. result = instance.verify?(method, code)
  221. expect(result).to be false
  222. end
  223. end
  224. context 'with valid code provided' do
  225. let(:code) { ROTP::TOTP.new(secret).now }
  226. it_behaves_like 'returning true result'
  227. end
  228. context 'with invalid code provided' do
  229. let(:code) { 'FOOBAR' }
  230. it_behaves_like 'returning false result'
  231. end
  232. context 'with no configured secret' do
  233. let(:code) { ROTP::TOTP.new(secret).now }
  234. let(:configuration) do
  235. {
  236. foo: 'bar',
  237. }
  238. end
  239. it_behaves_like 'returning false result'
  240. end
  241. context 'with no configured method' do
  242. let(:code) { ROTP::TOTP.new(secret).now }
  243. let(:configuration) { nil }
  244. it_behaves_like 'returning false result'
  245. end
  246. context 'with used recovery code' do
  247. let(:method) { 'recovery_codes' }
  248. let(:current_codes) { Auth::TwoFactor::RecoveryCodes.new(user).generate }
  249. let(:code) { current_codes.first }
  250. let(:two_factor_pref) { nil }
  251. before do
  252. current_codes
  253. end
  254. it 'returns true result' do
  255. expect(instance.verify?(method, code)).to be true
  256. end
  257. context 'with invalid code provided' do
  258. let(:code) { 'wrong' }
  259. it 'returns false result' do
  260. expect(instance.verify?(method, code)).to be false
  261. end
  262. end
  263. end
  264. end
  265. end
  266. end