security_spec.rb 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. # Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe 'Desktop > Personal Setting > Security', app: :desktop_view, authenticated_as: :agent, type: :system do
  4. let(:agent) { create(:agent) }
  5. def go_to_personal_setting
  6. visit '/'
  7. find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
  8. click_on 'Profile settings'
  9. end
  10. describe 'password change' do
  11. let(:agent) { create(:agent, password: 'test') }
  12. it 'user can change password' do
  13. go_to_personal_setting
  14. click_on 'Password'
  15. fill_in 'Current password', with: 'test'
  16. fill_in 'New password', with: 'testTEST1234'
  17. fill_in 'Confirm new password', with: 'testTEST1234'
  18. click_on 'Change Password'
  19. expect(page).to have_text('Password changed successfully')
  20. end
  21. end
  22. describe 'two-factor authentication handling' do
  23. let(:agent) { create(:agent, password: 'test') }
  24. context 'with security keys method' do
  25. before do
  26. skip('Mocking of Web Authentication API is currently supported only in Chrome.') if Capybara.current_driver != :zammad_chrome
  27. Setting.set('two_factor_authentication_method_security_keys', true)
  28. Setting.set('two_factor_authentication_recovery_codes', false)
  29. Setting.set('two_factor_authentication_enforce_role_ids', [])
  30. end
  31. it 'can set up and use the method' do
  32. go_to_personal_setting
  33. click_on 'Two-factor Authentication'
  34. click_on 'Set up security keys'
  35. fill_in 'Current password', with: 'test'
  36. click_on 'Next'
  37. click_on 'Set Up'
  38. fill_in 'Name for this security key', with: Faker::Lorem.unique.word
  39. # Mock a U2F key via the Selenium virtual authenticator feature (supported only by Chrome ATM).
  40. # A virtual authenticator instance will be set up for the remainder of the browser session.
  41. # We will reuse it later during the login phase.
  42. options = Selenium::WebDriver::VirtualAuthenticatorOptions.new(protocol: :u2f, transport: :usb, resident_key: false,
  43. user_consenting: true, user_verification: true,
  44. user_verified: true)
  45. page.driver.browser.add_virtual_authenticator(options)
  46. click_on 'Next'
  47. expect(page).to have_text('Two-factor authentication method was set up successfully.')
  48. # Logout
  49. find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
  50. click_on 'Sign out'
  51. # Login
  52. fill_in 'Username / Email', with: agent.login
  53. fill_in 'Password', with: 'test'
  54. click_on 'Sign in'
  55. # Mocked key via the virtual authenticator will be accessed again right about here.
  56. expect(page).to have_current_route('/')
  57. end
  58. end
  59. context 'with authenticator app method' do
  60. before do
  61. Setting.set('two_factor_authentication_method_authenticator_app', true)
  62. Setting.set('two_factor_authentication_recovery_codes', false)
  63. end
  64. describe 'when using optional setup in personal settings' do
  65. before do
  66. Setting.set('two_factor_authentication_enforce_role_ids', [])
  67. end
  68. it 'can set up and use the method' do
  69. go_to_personal_setting
  70. click_on 'Two-factor Authentication'
  71. click_on 'Set up authenticator app'
  72. fill_in 'Current password', with: 'test'
  73. click_on 'Next'
  74. find('canvas').click
  75. secret = find('#qr-code-secret-overlay > span').text
  76. fill_in('securityCode', with: ROTP::TOTP.new(secret).now)
  77. click_on 'Set Up'
  78. expect(page).to have_text('Two-factor method has been configured successfully.')
  79. # Logout
  80. find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
  81. click_on 'Sign out'
  82. # Workaround for a non-reusable OTP inside the 30s window.
  83. # Set the last OTP timestamp in the 2FA configuration to a time window in the past.
  84. two_factor_pref = agent.two_factor_preferences.last
  85. two_factor_pref.configuration[:last_otp_at] = 30.seconds.ago.to_i
  86. two_factor_pref.save!
  87. # Login
  88. fill_in 'Username / Email', with: agent.login
  89. fill_in 'Password', with: 'test'
  90. click_on 'Sign in'
  91. # 2FA Code
  92. fill_in('Security Code', with: ROTP::TOTP.new(secret).now)
  93. click_on('Sign in')
  94. expect(page).to have_current_route('/')
  95. end
  96. end
  97. describe 'when using mandatory setup after authentication' do
  98. before do
  99. Setting.set('two_factor_authentication_enforce_role_ids', [Role.lookup(name: 'Agent').id])
  100. end
  101. it 'can set up and use the method' do
  102. visit '/'
  103. expect(page).to have_text('Set Up Two-factor Authentication')
  104. fill_in('Current password', with: 'test')
  105. click_on 'Next'
  106. expect(page).to have_text('You must protect your account with two-factor authentication.')
  107. expect(page).to have_text('Choose your preferred two-factor authentication method to set it up.')
  108. click_on 'Authenticator App'
  109. find('canvas').click
  110. secret = find('#qr-code-secret-overlay > span').text
  111. fill_in('securityCode', with: ROTP::TOTP.new(secret).now)
  112. click_on 'Set Up'
  113. expect(page).to have_text('Two-factor method has been configured successfully.')
  114. # Logout
  115. find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
  116. click_on 'Sign out'
  117. # Workaround for a non-reusable OTP inside the 30s window.
  118. # Set the last OTP timestamp in the 2FA configuration to a time window in the past.
  119. two_factor_pref = agent.two_factor_preferences.last
  120. two_factor_pref.configuration[:last_otp_at] = 30.seconds.ago.to_i
  121. two_factor_pref.save!
  122. # Login
  123. fill_in 'Username / Email', with: agent.login
  124. fill_in 'Password', with: 'test'
  125. click_on 'Sign in'
  126. # 2FA Code
  127. fill_in('Security Code', with: ROTP::TOTP.new(secret).now)
  128. click_on('Sign in')
  129. expect(page).to have_current_route('/')
  130. end
  131. end
  132. end
  133. context 'with recovery codes enabled' do
  134. before do
  135. Setting.set('two_factor_authentication_method_authenticator_app', true)
  136. Setting.set('two_factor_authentication_recovery_codes', true)
  137. Setting.set('two_factor_authentication_enforce_role_ids', [])
  138. end
  139. it 'can use a pre-generated code to login' do
  140. go_to_personal_setting
  141. click_on 'Two-factor Authentication'
  142. click_on 'Set up authenticator app'
  143. fill_in 'Current password', with: 'test'
  144. click_on 'Next'
  145. find('canvas').click
  146. secret = find('#qr-code-secret-overlay > span').text
  147. fill_in('securityCode', with: ROTP::TOTP.new(secret).now)
  148. click_on 'Set Up'
  149. expect(page).to have_text('Two-factor method has been configured successfully.')
  150. recovery_codes = find('div[data-test-id="recovery-codes"]').text.split("\n")
  151. expect(recovery_codes.length).to be(10)
  152. click_on "OK, I've saved my recovery codes"
  153. expect(page).to have_button('Regenerate Recovery Codes')
  154. # Logout
  155. find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
  156. click_on 'Sign out'
  157. # Login
  158. fill_in 'Username / Email', with: agent.login
  159. fill_in 'Password', with: 'test'
  160. click_on 'Sign in'
  161. # Switch to recovery codes.
  162. click_on 'Try another method'
  163. click_on 'Or use one of your recovery codes.'
  164. fill_in('Recovery Code', with: recovery_codes[0])
  165. click_on('Sign in')
  166. expect(page).to have_current_route('/')
  167. end
  168. end
  169. end
  170. describe 'token handling' do
  171. let(:agent) { create(:admin) }
  172. it 'user can create and use a token' do
  173. go_to_personal_setting
  174. click_on 'Token Access'
  175. click_on 'New Personal Access Token'
  176. fill_in 'Name', with: 'Test Token'
  177. # Activate some permissions for the token
  178. find('span', text: 'Configure your system.').click
  179. find('span', text: 'Manage personal settings.').click
  180. click_on 'Create'
  181. wait_for_mutation('userCurrentAccessTokenAdd')
  182. expect(Token.last.name).to eq('Test Token')
  183. expect(Token.last.permissions.map(&:name)).to eq(%w[admin user_preferences])
  184. expect(Token.last.check?).to be(true)
  185. end
  186. end
  187. end