123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- require 'rails_helper'
- RSpec.describe 'Desktop > Personal Setting > Security', app: :desktop_view, authenticated_as: :agent, type: :system do
- let(:agent) { create(:agent) }
- def go_to_personal_setting
- visit '/'
- find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
- click_on 'Profile settings'
- end
- describe 'password change' do
- let(:agent) { create(:agent, password: 'test') }
- it 'user can change password' do
- go_to_personal_setting
- click_on 'Password'
- fill_in 'Current password', with: 'test'
- fill_in 'New password', with: 'testTEST1234'
- fill_in 'Confirm new password', with: 'testTEST1234'
- click_on 'Change Password'
- expect(page).to have_text('Password changed successfully')
- end
- end
- describe 'two-factor authentication handling' do
- let(:agent) { create(:agent, password: 'test') }
- context 'with security keys method' do
- before do
- skip('Mocking of Web Authentication API is currently supported only in Chrome.') if Capybara.current_driver != :zammad_chrome
- Setting.set('two_factor_authentication_method_security_keys', true)
- Setting.set('two_factor_authentication_recovery_codes', false)
- Setting.set('two_factor_authentication_enforce_role_ids', [])
- end
- it 'can set up and use the method' do
- go_to_personal_setting
- click_on 'Two-factor Authentication'
- click_on 'Set up security keys'
- fill_in 'Current password', with: 'test'
- click_on 'Next'
- click_on 'Set Up'
- fill_in 'Name for this security key', with: Faker::Lorem.unique.word
- # Mock a U2F key via the Selenium virtual authenticator feature (supported only by Chrome ATM).
- # A virtual authenticator instance will be set up for the remainder of the browser session.
- # We will reuse it later during the login phase.
- options = Selenium::WebDriver::VirtualAuthenticatorOptions.new(protocol: :u2f, transport: :usb, resident_key: false,
- user_consenting: true, user_verification: true,
- user_verified: true)
- page.driver.browser.add_virtual_authenticator(options)
- click_on 'Next'
- expect(page).to have_text('Two-factor authentication method was set up successfully.')
- # Logout
- find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
- click_on 'Sign out'
- # Login
- fill_in 'Username / Email', with: agent.login
- fill_in 'Password', with: 'test'
- click_on 'Sign in'
- # Mocked key via the virtual authenticator will be accessed again right about here.
- expect(page).to have_current_route('/')
- end
- end
- context 'with authenticator app method' do
- before do
- Setting.set('two_factor_authentication_method_authenticator_app', true)
- Setting.set('two_factor_authentication_recovery_codes', false)
- end
- describe 'when using optional setup in personal settings' do
- before do
- Setting.set('two_factor_authentication_enforce_role_ids', [])
- end
- it 'can set up and use the method' do
- go_to_personal_setting
- click_on 'Two-factor Authentication'
- click_on 'Set up authenticator app'
- fill_in 'Current password', with: 'test'
- click_on 'Next'
- find('canvas').click
- secret = find('#qr-code-secret-overlay > span').text
- fill_in('securityCode', with: ROTP::TOTP.new(secret).now)
- click_on 'Set Up'
- expect(page).to have_text('Two-factor method has been configured successfully.')
- # Logout
- find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
- click_on 'Sign out'
- # Workaround for a non-reusable OTP inside the 30s window.
- # Set the last OTP timestamp in the 2FA configuration to a time window in the past.
- two_factor_pref = agent.two_factor_preferences.last
- two_factor_pref.configuration[:last_otp_at] = 30.seconds.ago.to_i
- two_factor_pref.save!
- # Login
- fill_in 'Username / Email', with: agent.login
- fill_in 'Password', with: 'test'
- click_on 'Sign in'
- # 2FA Code
- fill_in('Security Code', with: ROTP::TOTP.new(secret).now)
- click_on('Sign in')
- expect(page).to have_current_route('/')
- end
- end
- describe 'when using mandatory setup after authentication' do
- before do
- Setting.set('two_factor_authentication_enforce_role_ids', [Role.lookup(name: 'Agent').id])
- end
- it 'can set up and use the method' do
- visit '/'
- expect(page).to have_text('Set Up Two-factor Authentication')
- expect(page).to have_text('You must protect your account with two-factor authentication.')
- expect(page).to have_text('Choose your preferred two-factor authentication method to set it up.')
- click_on 'Authenticator App'
- find('canvas').click
- secret = find('#qr-code-secret-overlay > span').text
- fill_in('securityCode', with: ROTP::TOTP.new(secret).now)
- click_on 'Set Up'
- expect(page).to have_text('Two-factor method has been configured successfully.')
- # Logout
- find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
- click_on 'Sign out'
- # Workaround for a non-reusable OTP inside the 30s window.
- # Set the last OTP timestamp in the 2FA configuration to a time window in the past.
- two_factor_pref = agent.two_factor_preferences.last
- two_factor_pref.configuration[:last_otp_at] = 30.seconds.ago.to_i
- two_factor_pref.save!
- # Login
- fill_in 'Username / Email', with: agent.login
- fill_in 'Password', with: 'test'
- click_on 'Sign in'
- # 2FA Code
- fill_in('Security Code', with: ROTP::TOTP.new(secret).now)
- click_on('Sign in')
- expect(page).to have_current_route('/')
- end
- end
- end
- context 'with recovery codes enabled' do
- before do
- Setting.set('two_factor_authentication_method_authenticator_app', true)
- Setting.set('two_factor_authentication_recovery_codes', true)
- Setting.set('two_factor_authentication_enforce_role_ids', [])
- end
- it 'can use a pre-generated code to login' do
- go_to_personal_setting
- click_on 'Two-factor Authentication'
- click_on 'Set up authenticator app'
- fill_in 'Current password', with: 'test'
- click_on 'Next'
- find('canvas').click
- secret = find('#qr-code-secret-overlay > span').text
- fill_in('securityCode', with: ROTP::TOTP.new(secret).now)
- click_on 'Set Up'
- expect(page).to have_text('Two-factor method has been configured successfully.')
- recovery_codes = find('div[data-test-id="recovery-codes"]').text.split("\n")
- expect(recovery_codes.length).to be(10)
- click_on "OK, I've saved my recovery codes"
- expect(page).to have_button('Regenerate Recovery Codes')
- # Logout
- find("[aria-label=\"Avatar (#{agent.fullname})\"]").click
- click_on 'Sign out'
- # Login
- fill_in 'Username / Email', with: agent.login
- fill_in 'Password', with: 'test'
- click_on 'Sign in'
- # Switch to recovery codes.
- click_on 'Try another method'
- click_on 'Or use one of your recovery codes.'
- fill_in('Recovery Code', with: recovery_codes[0])
- click_on('Sign in')
- expect(page).to have_current_route('/')
- end
- end
- end
- describe 'token handling' do
- let(:agent) { create(:admin) }
- it 'user can create and use a token' do
- go_to_personal_setting
- click_on 'Token Access'
- click_on 'New Personal Access Token'
- fill_in 'Name', with: 'Test Token'
- # Activate some permissions for the token
- find('span', text: 'Configure your system.').click
- find('span', text: 'Manage personal settings.').click
- click_on 'Create'
- wait_for_mutation('userCurrentAccessTokenAdd')
- expect(Token.last.name).to eq('Test Token')
- expect(Token.last.permissions.map(&:name)).to eq(%w[admin user_preferences])
- expect(Token.last.check?).to be(true)
- end
- end
- end
|