diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-07 15:08:27 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-07 15:08:27 +0300 |
commit | a8653790086d284cecffdc35892bb422cd6c9a7b (patch) | |
tree | 8d1f4dc69026a42a47b1026eef2566c7461a52fe /spec/features | |
parent | 444f662b8d8cbe47a8f3fa1db6ed926d64f3def3 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/features')
-rw-r--r-- | spec/features/u2f_spec.rb | 115 | ||||
-rw-r--r-- | spec/features/webauthn_spec.rb | 234 |
2 files changed, 245 insertions, 104 deletions
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index 8dbedc0a7ee..5762a54a717 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -3,22 +3,14 @@ require 'spec_helper' RSpec.describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do - def manage_two_factor_authentication - click_on 'Manage two-factor authentication' - expect(page).to have_content("Set up new U2F device") - wait_for_requests - end + include Spec::Support::Helpers::Features::TwoFactorHelpers - def register_u2f_device(u2f_device = nil, name: 'My device') - u2f_device ||= FakeU2fDevice.new(page, name) - u2f_device.respond_to_u2f_registration - click_on 'Set up new U2F device' - expect(page).to have_content('Your device was successfully set up') - fill_in "Pick a name", with: name - click_on 'Register U2F device' - u2f_device + before do + stub_feature_flags(webauthn: false) end + it_behaves_like 'hardware device for 2fa', 'U2F' + describe "registration" do let(:user) { create(:user) } @@ -27,31 +19,7 @@ RSpec.describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :j user.update_attribute(:otp_required_for_login, true) end - describe 'when 2FA via OTP is disabled' do - before do - user.update_attribute(:otp_required_for_login, false) - end - - it 'does not allow registering a new device' do - visit profile_account_path - click_on 'Enable two-factor authentication' - - expect(page).to have_button('Set up new U2F device', disabled: true) - end - end - describe 'when 2FA via OTP is enabled' do - it 'allows registering a new device with a name' do - visit profile_account_path - manage_two_factor_authentication - expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators") - - u2f_device = register_u2f_device - - expect(page).to have_content(u2f_device.name) - expect(page).to have_content('Your U2F device was registered') - end - it 'allows registering more than one device' do visit profile_account_path @@ -68,21 +36,6 @@ RSpec.describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :j expect(page).to have_content(second_device.name) expect(U2fRegistration.count).to eq(2) end - - it 'allows deleting a device' do - visit profile_account_path - manage_two_factor_authentication - expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators") - - first_u2f_device = register_u2f_device - second_u2f_device = register_u2f_device(name: 'My other device') - - accept_confirm { click_on "Delete", match: :first } - - expect(page).to have_content('Successfully deleted') - expect(page.body).not_to match(first_u2f_device.name) - expect(page).to have_content(second_u2f_device.name) - end end it 'allows the same device to be registered for multiple users' do @@ -111,9 +64,9 @@ RSpec.describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :j # Have the "u2f device" respond with bad data page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") - click_on 'Set up new U2F device' + click_on 'Set up new device' expect(page).to have_content('Your device was successfully set up') - click_on 'Register U2F device' + click_on 'Register device' expect(U2fRegistration.count).to eq(0) expect(page).to have_content("The form contains the following error") @@ -126,9 +79,9 @@ RSpec.describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :j # Failed registration page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") - click_on 'Set up new U2F device' + click_on 'Set up new device' expect(page).to have_content('Your device was successfully set up') - click_on 'Register U2F device' + click_on 'Register device' expect(page).to have_content("The form contains the following error") # Successful registration @@ -228,12 +181,12 @@ RSpec.describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :j user = gitlab_sign_in(:user) user.update_attribute(:otp_required_for_login, true) visit profile_two_factor_auth_path - expect(page).to have_content("Your U2F device needs to be set up.") + expect(page).to have_content("Your device needs to be set up.") first_device = register_u2f_device # Register second device visit profile_two_factor_auth_path - expect(page).to have_content("Your U2F device needs to be set up.") + expect(page).to have_content("Your device needs to be set up.") second_device = register_u2f_device(name: 'My other device') gitlab_sign_out @@ -249,50 +202,4 @@ RSpec.describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :j end end end - - describe 'fallback code authentication' do - let(:user) { create(:user) } - - def assert_fallback_ui(page) - expect(page).to have_button('Verify code') - expect(page).to have_css('#user_otp_attempt') - expect(page).not_to have_link('Sign in via 2FA code') - expect(page).not_to have_css('#js-authenticate-token-2fa') - end - - before do - # Register and logout - gitlab_sign_in(user) - user.update_attribute(:otp_required_for_login, true) - visit profile_account_path - end - - describe 'when no u2f device is registered' do - before do - gitlab_sign_out - gitlab_sign_in(user) - end - - it 'shows the fallback otp code UI' do - assert_fallback_ui(page) - end - end - - describe 'when a u2f device is registered' do - before do - manage_two_factor_authentication - @u2f_device = register_u2f_device - gitlab_sign_out - gitlab_sign_in(user) - end - - it 'provides a button that shows the fallback otp code UI' do - expect(page).to have_link('Sign in via 2FA code') - - click_link('Sign in via 2FA code') - - assert_fallback_ui(page) - end - end - end end diff --git a/spec/features/webauthn_spec.rb b/spec/features/webauthn_spec.rb new file mode 100644 index 00000000000..2ffb6bb3477 --- /dev/null +++ b/spec/features/webauthn_spec.rb @@ -0,0 +1,234 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Using WebAuthn Devices for Authentication', :js do + include Spec::Support::Helpers::Features::TwoFactorHelpers + let(:app_id) { "http://#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}" } + + before do + WebAuthn.configuration.origin = app_id + end + + it_behaves_like 'hardware device for 2fa', 'WebAuthn' + + describe 'registration' do + let(:user) { create(:user) } + + before do + gitlab_sign_in(user) + user.update_attribute(:otp_required_for_login, true) + end + + describe 'when 2FA via OTP is enabled' do + it 'allows registering more than one device' do + visit profile_account_path + + # First device + manage_two_factor_authentication + first_device = register_webauthn_device + expect(page).to have_content('Your WebAuthn device was registered') + + # Second device + second_device = register_webauthn_device(name: 'My other device') + expect(page).to have_content('Your WebAuthn device was registered') + + expect(page).to have_content(first_device.name) + expect(page).to have_content(second_device.name) + expect(WebauthnRegistration.count).to eq(2) + end + end + + it 'allows the same device to be registered for multiple users' do + # First user + visit profile_account_path + manage_two_factor_authentication + webauthn_device = register_webauthn_device + expect(page).to have_content('Your WebAuthn device was registered') + gitlab_sign_out + + # Second user + user = gitlab_sign_in(:user) + user.update_attribute(:otp_required_for_login, true) + visit profile_account_path + manage_two_factor_authentication + register_webauthn_device(webauthn_device, name: 'My other device') + expect(page).to have_content('Your WebAuthn device was registered') + + expect(WebauthnRegistration.count).to eq(2) + end + + context 'when there are form errors' do + let(:mock_register_js) do + <<~JS + const mockResponse = { + type: 'public-key', + id: '', + rawId: '', + response: { + clientDataJSON: '', + attestationObject: '', + }, + getClientExtensionResults: () => {}, + }; + navigator.credentials.create = function(_) {return Promise.resolve(mockResponse);} + JS + end + + it "doesn't register the device if there are errors" do + visit profile_account_path + manage_two_factor_authentication + + # Have the "webauthn device" respond with bad data + page.execute_script(mock_register_js) + click_on 'Set up new device' + expect(page).to have_content('Your device was successfully set up') + click_on 'Register device' + + expect(WebauthnRegistration.count).to eq(0) + expect(page).to have_content('The form contains the following error') + expect(page).to have_content('did not send a valid JSON response') + end + + it 'allows retrying registration' do + visit profile_account_path + manage_two_factor_authentication + + # Failed registration + page.execute_script(mock_register_js) + click_on 'Set up new device' + expect(page).to have_content('Your device was successfully set up') + click_on 'Register device' + expect(page).to have_content('The form contains the following error') + + # Successful registration + register_webauthn_device + + expect(page).to have_content('Your WebAuthn device was registered') + expect(WebauthnRegistration.count).to eq(1) + end + end + end + + describe 'authentication' do + let(:otp_required_for_login) { true } + let(:user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } + + describe 'when there is only an U2F device' do + let!(:u2f_device) do + fake_device = U2F::FakeU2F.new(app_id) # "Client" + u2f = U2F::U2F.new(app_id) # "Server" + + challenges = u2f.registration_requests.map(&:challenge) + device_response = fake_device.register_response(challenges[0]) + device_registration_params = { device_response: device_response, + name: 'My device' } + + U2fRegistration.register(user, app_id, device_registration_params, challenges) + FakeU2fDevice.new(page, 'My device', fake_device) + end + + it 'falls back to U2F' do + gitlab_sign_in(user) + + u2f_device.respond_to_u2f_authentication + + expect(page).to have_css('.sign-out-link', visible: false) + end + end + + describe 'when there is a WebAuthn device' do + let!(:webauthn_device) do + add_webauthn_device(app_id, user) + end + + describe 'when 2FA via OTP is disabled' do + let(:otp_required_for_login) { false } + + it 'allows logging in with the WebAuthn device' do + gitlab_sign_in(user) + + webauthn_device.respond_to_webauthn_authentication + + expect(page).to have_css('.sign-out-link', visible: false) + end + end + + describe 'when 2FA via OTP is enabled' do + it 'allows logging in with the WebAuthn device' do + gitlab_sign_in(user) + + webauthn_device.respond_to_webauthn_authentication + + expect(page).to have_css('.sign-out-link', visible: false) + end + end + + describe 'when a given WebAuthn device has already been registered by another user' do + describe 'but not the current user' do + let(:other_user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } + + it 'does not allow logging in with that particular device' do + # Register other user with a different WebAuthn device + other_device = add_webauthn_device(app_id, other_user) + + # Try authenticating user with the old WebAuthn device + gitlab_sign_in(user) + other_device.respond_to_webauthn_authentication + expect(page).to have_content('Authentication via WebAuthn device failed') + end + end + + describe "and also the current user" do + # TODO Uncomment once WebAuthn::FakeClient supports passing credential options + # (especially allow_credentials, as this is needed to specify which credential the + # fake client should use. Currently, the first credential is always used). + # There is an issue open for this: https://github.com/cedarcode/webauthn-ruby/issues/259 + it "allows logging in with that particular device" do + pending("support for passing credential options in FakeClient") + # Register current user with the same WebAuthn device + current_user = gitlab_sign_in(:user) + visit profile_account_path + manage_two_factor_authentication + register_webauthn_device(webauthn_device) + gitlab_sign_out + + # Try authenticating user with the same WebAuthn device + gitlab_sign_in(current_user) + webauthn_device.respond_to_webauthn_authentication + + expect(page).to have_css('.sign-out-link', visible: false) + end + end + end + + describe 'when a given WebAuthn device has not been registered' do + it 'does not allow logging in with that particular device' do + unregistered_device = FakeWebauthnDevice.new(page, 'My device') + gitlab_sign_in(user) + unregistered_device.respond_to_webauthn_authentication + + expect(page).to have_content('Authentication via WebAuthn device failed') + end + end + + describe 'when more than one device has been registered by the same user' do + it 'allows logging in with either device' do + first_device = add_webauthn_device(app_id, user) + second_device = add_webauthn_device(app_id, user) + + # Authenticate as both devices + [first_device, second_device].each do |device| + gitlab_sign_in(user) + # register_webauthn_device(device) + device.respond_to_webauthn_authentication + + expect(page).to have_css('.sign-out-link', visible: false) + + gitlab_sign_out + end + end + end + end + end +end |