security_spec.rb 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. # Copyright (C) 2012-2024 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. expect(page).to have_text('You must protect your account with two-factor authentication.')
  105. expect(page).to have_text('Choose your preferred two-factor authentication method to set it up.')
  106. click_on 'Authenticator App'
  107. find('canvas').click
  108. secret = find('#qr-code-secret-overlay > span').text
  109. fill_in('securityCode', with: ROTP::TOTP.new(secret).now)
  110. click_on 'Set Up'
  111. expect(page).to have_text('Two-factor method has been configured successfully.')
  112. # Logout
  113. find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
  114. click_on 'Sign out'
  115. # Workaround for a non-reusable OTP inside the 30s window.
  116. # Set the last OTP timestamp in the 2FA configuration to a time window in the past.
  117. two_factor_pref = agent.two_factor_preferences.last
  118. two_factor_pref.configuration[:last_otp_at] = 30.seconds.ago.to_i
  119. two_factor_pref.save!
  120. # Login
  121. fill_in 'Username / Email', with: agent.login
  122. fill_in 'Password', with: 'test'
  123. click_on 'Sign in'
  124. # 2FA Code
  125. fill_in('Security Code', with: ROTP::TOTP.new(secret).now)
  126. click_on('Sign in')
  127. expect(page).to have_current_route('/')
  128. end
  129. end
  130. end
  131. context 'with recovery codes enabled' do
  132. before do
  133. Setting.set('two_factor_authentication_method_authenticator_app', true)
  134. Setting.set('two_factor_authentication_recovery_codes', true)
  135. Setting.set('two_factor_authentication_enforce_role_ids', [])
  136. end
  137. it 'can use a pre-generated code to login' do
  138. go_to_personal_setting
  139. click_on 'Two-factor Authentication'
  140. click_on 'Set up authenticator app'
  141. fill_in 'Current password', with: 'test'
  142. click_on 'Next'
  143. find('canvas').click
  144. secret = find('#qr-code-secret-overlay > span').text
  145. fill_in('securityCode', with: ROTP::TOTP.new(secret).now)
  146. click_on 'Set Up'
  147. expect(page).to have_text('Two-factor method has been configured successfully.')
  148. recovery_codes = find('div[data-test-id="recovery-codes"]').text.split("\n")
  149. expect(recovery_codes.length).to be(10)
  150. click_on "OK, I've saved my recovery codes"
  151. expect(page).to have_button('Regenerate Recovery Codes')
  152. # Logout
  153. find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
  154. click_on 'Sign out'
  155. # Login
  156. fill_in 'Username / Email', with: agent.login
  157. fill_in 'Password', with: 'test'
  158. click_on 'Sign in'
  159. # Switch to recovery codes.
  160. click_on 'Try another method'
  161. click_on 'Or use one of your recovery codes.'
  162. fill_in('Recovery Code', with: recovery_codes[0])
  163. click_on('Sign in')
  164. expect(page).to have_current_route('/')
  165. end
  166. end
  167. end
  168. describe 'token handling' do
  169. let(:agent) { create(:admin) }
  170. it 'user can create and use a token' do
  171. go_to_personal_setting
  172. click_on 'Token Access'
  173. click_on 'New Personal Access Token'
  174. fill_in 'Name', with: 'Test Token'
  175. # Activate some permissions for the token
  176. find('span', text: 'Configure your system.').click
  177. find('span', text: 'Manage personal settings.').click
  178. click_on 'Create'
  179. wait_for_mutation('userCurrentAccessTokenAdd')
  180. expect(Token.last.name).to eq('Test Token')
  181. expect(Token.last.permissions.map(&:name)).to eq(%w[admin user_preferences])
  182. expect(Token.last.check?).to be(true)
  183. end
  184. end
  185. end