2019-07-31 22:56:46 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2015-09-11 14:41:01 +05:30
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
RSpec.describe Profiles::TwoFactorAuthsController do
|
2015-09-11 14:41:01 +05:30
|
|
|
before do
|
|
|
|
# `user` should be defined within the action-specific describe blocks
|
|
|
|
sign_in(user)
|
|
|
|
|
|
|
|
allow(subject).to receive(:current_user).and_return(user)
|
|
|
|
end
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
shared_examples 'user must first verify their primary email address' do
|
|
|
|
before do
|
|
|
|
allow(user).to receive(:primary_email_verified?).and_return(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'redirects to profile_emails_path' do
|
|
|
|
go
|
|
|
|
|
|
|
|
expect(response).to redirect_to(profile_emails_path)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'displays a notice' do
|
|
|
|
go
|
|
|
|
|
|
|
|
expect(flash[:notice])
|
|
|
|
.to eq _('You need to verify your primary email first before enabling Two-Factor Authentication.')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
shared_examples 'user must enter a valid current password' do
|
|
|
|
let(:current_password) { '123' }
|
2022-11-25 23:54:43 +05:30
|
|
|
let(:error_message) { { message: _('You must provide a valid current password') } }
|
2021-09-30 23:02:18 +05:30
|
|
|
|
|
|
|
it 'requires the current password', :aggregate_failures do
|
|
|
|
go
|
|
|
|
|
2022-11-25 23:54:43 +05:30
|
|
|
expect(assigns[:error]).to eq(error_message)
|
|
|
|
expect(response).to render_template(:show)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'assigns qr_code' do
|
|
|
|
code = double('qr code')
|
|
|
|
expect(subject).to receive(:build_qr_code).and_return(code)
|
|
|
|
|
|
|
|
go
|
|
|
|
expect(assigns[:qr_code]).to eq(code)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'assigns account_string' do
|
|
|
|
go
|
|
|
|
expect(assigns[:account_string]).to eq("#{Gitlab.config.gitlab.host}:#{user.email}")
|
2021-09-30 23:02:18 +05:30
|
|
|
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
|
2021-11-11 11:23:49 +05:30
|
|
|
|
|
|
|
context 'when user authenticates with an external service' do
|
|
|
|
before do
|
|
|
|
allow(user).to receive(:password_automatically_set?).and_return(true)
|
2021-12-07 22:27:20 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not require the current password', :aggregate_failures do
|
|
|
|
go
|
|
|
|
|
2022-11-25 23:54:43 +05:30
|
|
|
expect(assigns[:error]).not_to eq(error_message)
|
2021-12-07 22:27:20 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when password authentication is disabled' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(password_authentication_enabled_for_web: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not require the current password', :aggregate_failures do
|
|
|
|
go
|
|
|
|
|
2022-11-25 23:54:43 +05:30
|
|
|
expect(assigns[:error]).not_to eq(error_message)
|
2021-12-07 22:27:20 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the user is an LDAP user' do
|
|
|
|
before do
|
|
|
|
allow(user).to receive(:ldap_user?).and_return(true)
|
2021-11-11 11:23:49 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not require the current password', :aggregate_failures do
|
|
|
|
go
|
|
|
|
|
2022-11-25 23:54:43 +05:30
|
|
|
expect(assigns[:error]).not_to eq(error_message)
|
2021-11-11 11:23:49 +05:30
|
|
|
end
|
|
|
|
end
|
2021-09-30 23:02:18 +05:30
|
|
|
end
|
|
|
|
|
2016-06-16 23:09:34 +05:30
|
|
|
describe 'GET show' do
|
2021-11-11 11:23:49 +05:30
|
|
|
let_it_be_with_reload(:user) { create(:user) }
|
2015-09-11 14:41:01 +05:30
|
|
|
|
|
|
|
it 'generates otp_secret for user' do
|
2020-09-03 11:15:55 +05:30
|
|
|
expect(User).to receive(:generate_otp_secret).with(32).and_call_original.once
|
2015-09-11 14:41:01 +05:30
|
|
|
|
2016-06-16 23:09:34 +05:30
|
|
|
get :show
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'assigns qr_code' do
|
|
|
|
code = double('qr code')
|
|
|
|
expect(subject).to receive(:build_qr_code).and_return(code)
|
|
|
|
|
2016-06-16 23:09:34 +05:30
|
|
|
get :show
|
2022-06-21 17:19:12 +05:30
|
|
|
expect(assigns[:qr_code]).to eq(code)
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
2020-09-03 11:15:55 +05:30
|
|
|
|
2022-06-21 17:19:12 +05:30
|
|
|
it 'generates a single otp_secret with multiple page loads', :freeze_time do
|
|
|
|
expect(User).to receive(:generate_otp_secret).with(32).and_call_original.once
|
|
|
|
|
|
|
|
user.update!(otp_secret: nil, otp_secret_expires_at: nil)
|
2020-09-03 11:15:55 +05:30
|
|
|
|
|
|
|
2.times do
|
|
|
|
get :show
|
|
|
|
end
|
|
|
|
end
|
2021-11-11 11:23:49 +05:30
|
|
|
|
2022-06-21 17:19:12 +05:30
|
|
|
it 'generates a new otp_secret once the ttl has expired' do
|
|
|
|
expect(User).to receive(:generate_otp_secret).with(32).and_call_original.once
|
|
|
|
|
|
|
|
user.update!(otp_secret: "FT7KAVNU63YZH7PBRVPVL7CPSAENXY25", otp_secret_expires_at: 2.minutes.from_now)
|
|
|
|
|
|
|
|
travel_to(10.minutes.from_now) do
|
|
|
|
get :show
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
it_behaves_like 'user must first verify their primary email address' do
|
|
|
|
let(:go) { get :show }
|
|
|
|
end
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe 'POST create' do
|
2021-11-11 11:23:49 +05:30
|
|
|
let_it_be_with_reload(:user) { create(:user) }
|
|
|
|
|
|
|
|
let(:pin) { 'pin-code' }
|
2021-09-30 23:02:18 +05:30
|
|
|
let(:current_password) { user.password }
|
2015-09-11 14:41:01 +05:30
|
|
|
|
|
|
|
def go
|
2021-09-30 23:02:18 +05:30
|
|
|
post :create, params: { pin_code: pin, current_password: current_password }
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'with valid pin' do
|
|
|
|
before do
|
2015-09-25 12:07:36 +05:30
|
|
|
expect(user).to receive(:validate_and_consume_otp!).with(pin).and_return(true)
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
2016-06-16 23:09:34 +05:30
|
|
|
it 'enables 2fa for the user' do
|
2015-09-11 14:41:01 +05:30
|
|
|
go
|
|
|
|
|
|
|
|
user.reload
|
|
|
|
expect(user).to be_two_factor_enabled
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'presents plaintext codes for the user to save' do
|
|
|
|
expect(user).to receive(:generate_otp_backup_codes!).and_return(%w(a b c))
|
|
|
|
|
|
|
|
go
|
|
|
|
|
|
|
|
expect(assigns[:codes]).to match_array %w(a b c)
|
|
|
|
end
|
|
|
|
|
2020-09-03 11:15:55 +05:30
|
|
|
it 'calls to delete other sessions' do
|
|
|
|
expect(ActiveSession).to receive(:destroy_all_but_current)
|
|
|
|
|
|
|
|
go
|
|
|
|
end
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
it 'dismisses the `TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK` callout' do
|
|
|
|
expect(controller.helpers).to receive(:dismiss_two_factor_auth_recovery_settings_check)
|
2021-10-27 15:23:28 +05:30
|
|
|
|
|
|
|
go
|
|
|
|
end
|
|
|
|
|
2015-09-11 14:41:01 +05:30
|
|
|
it 'renders create' do
|
|
|
|
go
|
|
|
|
expect(response).to render_template(:create)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with invalid pin' do
|
|
|
|
before do
|
2015-09-25 12:07:36 +05:30
|
|
|
expect(user).to receive(:validate_and_consume_otp!).with(pin).and_return(false)
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'assigns error' do
|
|
|
|
go
|
2021-12-11 22:18:48 +05:30
|
|
|
expect(assigns[:error]).to eq({ message: 'Invalid pin code.' })
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'assigns qr_code' do
|
|
|
|
code = double('qr code')
|
|
|
|
expect(subject).to receive(:build_qr_code).and_return(code)
|
|
|
|
|
|
|
|
go
|
2022-06-21 17:19:12 +05:30
|
|
|
expect(assigns[:qr_code]).to eq(code)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'assigns account_string' do
|
|
|
|
go
|
|
|
|
expect(assigns[:account_string]).to eq("#{Gitlab.config.gitlab.host}:#{user.email}")
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
2016-06-16 23:09:34 +05:30
|
|
|
it 'renders show' do
|
2015-09-11 14:41:01 +05:30
|
|
|
go
|
2016-06-16 23:09:34 +05:30
|
|
|
expect(response).to render_template(:show)
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
end
|
2021-09-30 23:02:18 +05:30
|
|
|
|
|
|
|
it_behaves_like 'user must enter a valid current password'
|
2021-11-11 11:23:49 +05:30
|
|
|
|
|
|
|
it_behaves_like 'user must first verify their primary email address'
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe 'POST codes' do
|
2021-11-11 11:23:49 +05:30
|
|
|
let_it_be_with_reload(:user) { create(:user, :two_factor) }
|
2015-09-11 14:41:01 +05:30
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
let(:current_password) { user.password }
|
|
|
|
|
2015-09-11 14:41:01 +05:30
|
|
|
it 'presents plaintext codes for the user to save' do
|
|
|
|
expect(user).to receive(:generate_otp_backup_codes!).and_return(%w(a b c))
|
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
post :codes, params: { current_password: current_password }
|
2015-09-11 14:41:01 +05:30
|
|
|
expect(assigns[:codes]).to match_array %w(a b c)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'persists the generated codes' do
|
2021-09-30 23:02:18 +05:30
|
|
|
post :codes, params: { current_password: current_password }
|
2015-09-11 14:41:01 +05:30
|
|
|
|
|
|
|
user.reload
|
|
|
|
expect(user.otp_backup_codes).not_to be_empty
|
|
|
|
end
|
2021-09-30 23:02:18 +05:30
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
it 'dismisses the `TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK` callout' do
|
|
|
|
expect(controller.helpers).to receive(:dismiss_two_factor_auth_recovery_settings_check)
|
2021-10-27 15:23:28 +05:30
|
|
|
|
|
|
|
post :codes, params: { current_password: current_password }
|
|
|
|
end
|
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
it_behaves_like 'user must enter a valid current password' do
|
|
|
|
let(:go) { post :codes, params: { current_password: current_password } }
|
|
|
|
end
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe 'DELETE destroy' do
|
2021-11-11 11:23:49 +05:30
|
|
|
def go
|
|
|
|
delete :destroy, params: { current_password: current_password }
|
|
|
|
end
|
2021-09-30 23:02:18 +05:30
|
|
|
|
|
|
|
let(:current_password) { user.password }
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
context 'for a user that has 2FA enabled' do
|
2021-11-11 11:23:49 +05:30
|
|
|
let_it_be_with_reload(:user) { create(:user, :two_factor) }
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
it 'disables two factor' do
|
2021-11-11 11:23:49 +05:30
|
|
|
go
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
expect(user.reload.two_factor_enabled?).to eq(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'redirects to profile_account_path' do
|
2021-11-11 11:23:49 +05:30
|
|
|
go
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
expect(response).to redirect_to(profile_account_path)
|
|
|
|
end
|
2015-09-11 14:41:01 +05:30
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
it 'displays a notice on success' do
|
2021-11-11 11:23:49 +05:30
|
|
|
go
|
2015-09-11 14:41:01 +05:30
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
expect(flash[:notice])
|
|
|
|
.to eq _('Two-factor authentication has been disabled successfully!')
|
|
|
|
end
|
2021-09-30 23:02:18 +05:30
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
it_behaves_like 'user must enter a valid current password'
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
context 'for a user that does not have 2FA enabled' do
|
2021-11-11 11:23:49 +05:30
|
|
|
let_it_be_with_reload(:user) { create(:user) }
|
2015-09-11 14:41:01 +05:30
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
it 'redirects to profile_account_path' do
|
2021-11-11 11:23:49 +05:30
|
|
|
go
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
expect(response).to redirect_to(profile_account_path)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'displays an alert on failure' do
|
2021-11-11 11:23:49 +05:30
|
|
|
go
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
expect(flash[:alert])
|
|
|
|
.to eq _('Two-factor authentication is not enabled for this user')
|
|
|
|
end
|
2015-09-11 14:41:01 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|