diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-29 12:53:15 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-29 12:53:43 +0000 |
commit | 8a2a8c40a84b97bd1df668b3458cf61cadce1c2a (patch) | |
tree | 838787352e579632098ddc791afe20b5ed856c12 /spec | |
parent | 86842c660b55c74269649851bb694e40367e8bef (diff) | |
download | gitlab-ce-8a2a8c40a84b97bd1df668b3458cf61cadce1c2a.tar.gz |
Add latest changes from gitlab-org/security/gitlab@14-3-stable-ee
Diffstat (limited to 'spec')
5 files changed, 327 insertions, 5 deletions
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb index 073180cbafd..a0e2cf671af 100644 --- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb +++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb @@ -35,6 +35,27 @@ RSpec.describe Profiles::TwoFactorAuthsController do end end + shared_examples 'user must enter a valid current password' do + let(:current_password) { '123' } + + it 'requires the current password', :aggregate_failures do + go + + expect(response).to redirect_to(profile_two_factor_auth_path) + expect(flash[:alert]).to eq(_('You must provide a valid current password')) + end + + context 'when the user is on the last sign in attempt' do + it do + user.update!(failed_attempts: User.maximum_attempts.pred) + + go + + expect(user.reload).to be_access_locked + end + end + end + describe 'GET show' do let_it_be_with_reload(:user) { create(:user) } @@ -69,9 +90,10 @@ RSpec.describe Profiles::TwoFactorAuthsController do let_it_be_with_reload(:user) { create(:user) } let(:pin) { 'pin-code' } + let(:current_password) { user.password } def go - post :create, params: { pin_code: pin } + post :create, params: { pin_code: pin, current_password: current_password } end context 'with valid pin' do @@ -136,21 +158,25 @@ RSpec.describe Profiles::TwoFactorAuthsController do end end + it_behaves_like 'user must enter a valid current password' + it_behaves_like 'user must first verify their primary email address' end describe 'POST codes' do let_it_be_with_reload(:user) { create(:user, :two_factor) } + let(:current_password) { user.password } + it 'presents plaintext codes for the user to save' do expect(user).to receive(:generate_otp_backup_codes!).and_return(%w(a b c)) - post :codes + post :codes, params: { current_password: current_password } expect(assigns[:codes]).to match_array %w(a b c) end it 'persists the generated codes' do - post :codes + post :codes, params: { current_password: current_password } user.reload expect(user.otp_backup_codes).not_to be_empty @@ -159,12 +185,18 @@ RSpec.describe Profiles::TwoFactorAuthsController do it 'dismisses the `TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK` callout' do expect(controller.helpers).to receive(:dismiss_two_factor_auth_recovery_settings_check) - post :codes + post :codes, params: { current_password: current_password } + end + + it_behaves_like 'user must enter a valid current password' do + let(:go) { post :codes, params: { current_password: current_password } } end end describe 'DELETE destroy' do - subject { delete :destroy } + subject { delete :destroy, params: { current_password: current_password } } + + let(:current_password) { user.password } context 'for a user that has 2FA enabled' do let_it_be_with_reload(:user) { create(:user, :two_factor) } @@ -187,6 +219,10 @@ RSpec.describe Profiles::TwoFactorAuthsController do expect(flash[:notice]) .to eq _('Two-factor authentication has been disabled successfully!') end + + it_behaves_like 'user must enter a valid current password' do + let(:go) { delete :destroy, params: { current_password: current_password } } + end end context 'for a user that does not have 2FA enabled' do diff --git a/spec/features/profiles/two_factor_auths_spec.rb b/spec/features/profiles/two_factor_auths_spec.rb new file mode 100644 index 00000000000..e1feca5031a --- /dev/null +++ b/spec/features/profiles/two_factor_auths_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Two factor auths' do + context 'when signed in' do + before do + allow(Gitlab).to receive(:com?) { true } + end + + context 'when user has two-factor authentication disabled' do + let(:user) { create(:user ) } + + before do + sign_in(user) + end + + it 'requires the current password to set up two factor authentication', :js do + visit profile_two_factor_auth_path + + register_2fa(user.reload.current_otp, '123') + + expect(page).to have_content('You must provide a valid current password') + + register_2fa(user.reload.current_otp, user.password) + + expect(page).to have_content('Please copy, download, or print your recovery codes before proceeding.') + + click_button 'Copy codes' + click_link 'Proceed' + + expect(page).to have_content('Status: Enabled') + end + end + + context 'when user has two-factor authentication enabled' do + let(:user) { create(:user, :two_factor) } + + before do + sign_in(user) + end + + it 'requires the current_password to disable two-factor authentication', :js do + visit profile_two_factor_auth_path + + fill_in 'current_password', with: '123' + + click_button 'Disable two-factor authentication' + + page.accept_alert + + expect(page).to have_content('You must provide a valid current password') + + fill_in 'current_password', with: user.password + + click_button 'Disable two-factor authentication' + + page.accept_alert + + expect(page).to have_content('Two-factor authentication has been disabled successfully!') + expect(page).to have_content('Enable two-factor authentication') + end + + it 'requires the current_password to regernate recovery codes', :js do + visit profile_two_factor_auth_path + + fill_in 'current_password', with: '123' + + click_button 'Regenerate recovery codes' + + expect(page).to have_content('You must provide a valid current password') + + fill_in 'current_password', with: user.password + + click_button 'Regenerate recovery codes' + + expect(page).to have_content('Please copy, download, or print your recovery codes before proceeding.') + end + end + + def register_2fa(pin, password) + fill_in 'pin_code', with: pin + fill_in 'current_password', with: password + + click_button 'Register with two-factor app' + end + end +end diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index afd750d02eb..79c4057a8b9 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -807,6 +807,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_shared_state do expect(current_path).to eq(profile_two_factor_auth_path) fill_in 'pin_code', with: user.reload.current_otp + fill_in 'current_password', with: user.password click_button 'Register with two-factor app' click_button 'Copy codes' diff --git a/spec/frontend/authentication/two_factor_auth/components/__snapshots__/manage_two_factor_form_spec.js.snap b/spec/frontend/authentication/two_factor_auth/components/__snapshots__/manage_two_factor_form_spec.js.snap new file mode 100644 index 00000000000..3fe0e570a54 --- /dev/null +++ b/spec/frontend/authentication/two_factor_auth/components/__snapshots__/manage_two_factor_form_spec.js.snap @@ -0,0 +1,99 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManageTwoFactorForm Disable button renders the component correctly 1`] = ` +VueWrapper { + "_emitted": Object {}, + "_emittedByOrder": Array [], + "isFunctionalComponent": undefined, +} +`; + +exports[`ManageTwoFactorForm Disable button renders the component correctly 2`] = ` +<form + action="#" + class="gl-display-inline-block" + method="post" +> + <input + data-testid="test-2fa-method-field" + name="_method" + type="hidden" + /> + + <input + name="authenticity_token" + type="hidden" + /> + + <div + class="form-group gl-form-group" + id="__BVID__15" + role="group" + > + <label + class="d-block col-form-label" + for="current-password" + id="__BVID__15__BV_label_" + > + Current password + </label> + <div + class="bv-no-focus-ring" + > + <input + aria-required="true" + class="gl-form-input form-control" + data-qa-selector="current_password_field" + id="current-password" + name="current_password" + required="required" + type="password" + /> + <!----> + <!----> + <!----> + </div> + </div> + + <button + class="btn btn-danger gl-mr-3 gl-display-inline-block btn-danger btn-md gl-button" + data-confirm="Are you sure? This will invalidate your registered applications and U2F devices." + data-form-action="2fa_auth_path" + data-form-method="2fa_auth_method" + data-testid="test-2fa-disable-button" + type="submit" + > + <!----> + + <!----> + + <span + class="gl-button-text" + > + + Disable two-factor authentication + + </span> + </button> + + <button + class="btn gl-display-inline-block btn-default btn-md gl-button" + data-form-action="2fa_codes_path" + data-form-method="2fa_codes_method" + data-testid="test-2fa-regenerate-codes-button" + type="submit" + > + <!----> + + <!----> + + <span + class="gl-button-text" + > + + Regenerate recovery codes + + </span> + </button> +</form> +`; diff --git a/spec/frontend/authentication/two_factor_auth/components/manage_two_factor_form_spec.js b/spec/frontend/authentication/two_factor_auth/components/manage_two_factor_form_spec.js new file mode 100644 index 00000000000..384579c6876 --- /dev/null +++ b/spec/frontend/authentication/two_factor_auth/components/manage_two_factor_form_spec.js @@ -0,0 +1,98 @@ +import { within } from '@testing-library/dom'; +import { mount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import ManageTwoFactorForm, { + i18n, +} from '~/authentication/two_factor_auth/components/manage_two_factor_form.vue'; + +describe('ManageTwoFactorForm', () => { + let wrapper; + + const createComponent = (options = {}) => { + wrapper = extendedWrapper( + mount(ManageTwoFactorForm, { + provide: { + webauthnEnabled: options?.webauthnEnabled || false, + profileTwoFactorAuthPath: '2fa_auth_path', + profileTwoFactorAuthMethod: '2fa_auth_method', + codesProfileTwoFactorAuthPath: '2fa_codes_path', + codesProfileTwoFactorAuthMethod: '2fa_codes_method', + }, + }), + ); + }; + + const queryByText = (text, options) => within(wrapper.element).queryByText(text, options); + const queryByLabelText = (text, options) => + within(wrapper.element).queryByLabelText(text, options); + + beforeEach(() => { + createComponent(); + }); + + describe('Current password field', () => { + it('renders the current password field', () => { + expect(queryByLabelText(i18n.currentPassword).tagName).toEqual('INPUT'); + }); + }); + + describe('Disable button', () => { + it('renders the component correctly', () => { + expect(wrapper).toMatchSnapshot(); + expect(wrapper.element).toMatchSnapshot(); + }); + + it('has the right confirm text', () => { + expect(wrapper.findByTestId('test-2fa-disable-button').element.dataset.confirm).toEqual( + i18n.confirm, + ); + }); + + describe('when webauthnEnabled', () => { + beforeEach(() => { + createComponent({ + webauthnEnabled: true, + }); + }); + + it('has the right confirm text', () => { + expect(wrapper.findByTestId('test-2fa-disable-button').element.dataset.confirm).toEqual( + i18n.confirmWebAuthn, + ); + }); + }); + + it('modifies the form action and method when submitted through the button', async () => { + const form = wrapper.find('form'); + const disableButton = wrapper.findByTestId('test-2fa-disable-button').element; + const methodInput = wrapper.findByTestId('test-2fa-method-field').element; + + form.trigger('submit', { submitter: disableButton }); + + await wrapper.vm.$nextTick(); + + expect(form.element.getAttribute('action')).toEqual('2fa_auth_path'); + expect(methodInput.getAttribute('value')).toEqual('2fa_auth_method'); + }); + }); + + describe('Regenerate recovery codes button', () => { + it('renders the button', () => { + expect(queryByText(i18n.regenerateRecoveryCodes)).toEqual(expect.any(HTMLElement)); + }); + + it('modifies the form action and method when submitted through the button', async () => { + const form = wrapper.find('form'); + const regenerateCodesButton = wrapper.findByTestId('test-2fa-regenerate-codes-button') + .element; + const methodInput = wrapper.findByTestId('test-2fa-method-field').element; + + form.trigger('submit', { submitter: regenerateCodesButton }); + + await wrapper.vm.$nextTick(); + + expect(form.element.getAttribute('action')).toEqual('2fa_codes_path'); + expect(methodInput.getAttribute('value')).toEqual('2fa_codes_method'); + }); + }); +}); |