Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-01 18:08:30 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-01 18:08:30 +0300
commita0fdcfcdd514c2af98f18cadfa75f8a6a85b4d2c (patch)
treeecba106fd4d1426cc2109a6ba3da091be2de1f87 /spec/features/users
parent2828f81d2a41f46b89e13dc057b982f27aeee547 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/features/users')
-rw-r--r--spec/features/users/email_verification_on_login_spec.rb357
1 files changed, 357 insertions, 0 deletions
diff --git a/spec/features/users/email_verification_on_login_spec.rb b/spec/features/users/email_verification_on_login_spec.rb
new file mode 100644
index 00000000000..54eb11ca857
--- /dev/null
+++ b/spec/features/users/email_verification_on_login_spec.rb
@@ -0,0 +1,357 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Email Verification On Login', :clean_gitlab_redis_rate_limiting do
+ include EmailHelpers
+
+ let_it_be(:user) { create(:user) }
+
+ let(:require_email_verification_enabled) { true }
+
+ before do
+ stub_feature_flags(require_email_verification: require_email_verification_enabled)
+ end
+
+ shared_examples 'email verification required' do
+ before do
+ allow(Gitlab::AppLogger).to receive(:info)
+ end
+
+ it 'requires email verification before being able to access GitLab' do
+ perform_enqueued_jobs do
+ # When logging in
+ gitlab_sign_in(user)
+ expect_log_message(message: "Account Locked: username=#{user.username}")
+ expect_log_message('Instructions Sent')
+
+ # Expect the user to be locked and the unlock_token to be set
+ user.reload
+ expect(user.locked_at).not_to be_nil
+ expect(user.unlock_token).not_to be_nil
+
+ # Expect to see the verification form on the login page
+ expect(page).to have_current_path(new_user_session_path)
+ expect(page).to have_content('Help us protect your account')
+
+ # Expect an instructions email to be sent with a code
+ code = expect_instructions_email_and_extract_code
+
+ # Signing in again prompts for the code and doesn't send a new one
+ gitlab_sign_in(user)
+ expect(page).to have_current_path(new_user_session_path)
+ expect(page).to have_content('Help us protect your account')
+
+ # Verify the code
+ verify_code(code)
+ expect_log_message('Successful')
+ expect_log_message(message: "Successful Login: username=#{user.username} "\
+ "ip=127.0.0.1 method=standard admin=false")
+
+ # Expect the user to be unlocked
+ expect_user_to_be_unlocked
+
+ # Expect a confirmation page with a meta refresh tag for 3 seconds to the root
+ expect(page).to have_current_path(users_successful_verification_path)
+ expect(page).to have_content('Verification successful')
+ expect(page).to have_selector("meta[http-equiv='refresh'][content='3; url=#{root_path}']", visible: false)
+ end
+ end
+
+ describe 'resending a new code' do
+ it 'resends a new code' do
+ perform_enqueued_jobs do
+ # When logging in
+ gitlab_sign_in(user)
+
+ # Expect an instructions email to be sent with a code
+ code = expect_instructions_email_and_extract_code
+
+ # Request a new code
+ click_link 'Resend code'
+ expect_log_message('Instructions Sent', 2)
+ new_code = expect_instructions_email_and_extract_code
+
+ # Verify the old code is different from the new code
+ expect(code).not_to eq(new_code)
+ end
+ end
+
+ it 'rate limits resends' do
+ # When logging in
+ gitlab_sign_in(user)
+
+ # It shows a resend button
+ expect(page).to have_link 'Resend code'
+
+ # Resend more than the rate limited amount of times
+ 10.times do
+ click_link 'Resend code'
+ end
+
+ # Expect the link to be gone
+ expect(page).not_to have_link 'Resend code'
+
+ # Wait for 1 hour
+ travel 1.hour
+
+ # Now it's visible again
+ gitlab_sign_in(user)
+ expect(page).to have_link 'Resend code'
+ end
+ end
+
+ describe 'verification errors' do
+ it 'rate limits verifications' do
+ perform_enqueued_jobs do
+ # When logging in
+ gitlab_sign_in(user)
+
+ # Expect an instructions email to be sent with a code
+ code = expect_instructions_email_and_extract_code
+
+ # Verify an invalid token more than the rate limited amount of times
+ 11.times do
+ verify_code('123456')
+ end
+
+ # Expect an error message
+ expect_log_message('Failed Attempt', reason: 'rate_limited')
+ expect(page).to have_content("You've reached the maximum amount of tries. "\
+ 'Wait 10 minutes or resend a new code and try again.')
+
+ # Wait for 10 minutes
+ travel 10.minutes
+
+ # Now it works again
+ verify_code(code)
+ expect_log_message('Successful')
+ end
+ end
+
+ it 'verifies invalid codes' do
+ # When logging in
+ gitlab_sign_in(user)
+
+ # Verify an invalid code
+ verify_code('123456')
+
+ # Expect an error message
+ expect_log_message('Failed Attempt', reason: 'invalid')
+ expect(page).to have_content('The code is incorrect. Enter it again, or resend a new code.')
+ end
+
+ it 'verifies expired codes' do
+ perform_enqueued_jobs do
+ # When logging in
+ gitlab_sign_in(user)
+
+ # Expect an instructions email to be sent with a code
+ code = expect_instructions_email_and_extract_code
+
+ # Wait for the code to expire before verifying
+ travel VerifiesWithEmail::TOKEN_VALID_FOR_MINUTES.minutes + 1.second
+ verify_code(code)
+
+ # Expect an error message
+ expect_log_message('Failed Attempt', reason: 'expired')
+ expect(page).to have_content('The code has expired. Resend a new code and try again.')
+ end
+ end
+ end
+ end
+
+ shared_examples 'no email verification required' do |**login_args|
+ it 'does not lock the user and redirects to the root page after logging in' do
+ gitlab_sign_in(user, **login_args)
+
+ expect_user_to_be_unlocked
+
+ expect(page).to have_current_path(root_path)
+ end
+ end
+
+ shared_examples 'no email verification required when 2fa enabled or ff disabled' do
+ context 'when 2FA is enabled' do
+ let_it_be(:user) { create(:user, :two_factor) }
+
+ it_behaves_like 'no email verification required', two_factor_auth: true
+ end
+
+ context 'when the feature flag is disabled' do
+ let(:require_email_verification_enabled) { false }
+
+ it_behaves_like 'no email verification required'
+ end
+ end
+
+ describe 'when failing to login the maximum allowed number of times' do
+ before do
+ # See comment in RequireEmailVerification::MAXIMUM_ATTEMPTS on why this is divided by 2
+ (RequireEmailVerification::MAXIMUM_ATTEMPTS / 2).times do
+ gitlab_sign_in(user, password: 'wrong_password')
+ end
+ end
+
+ it 'locks the user, but does not set the unlock token', :aggregate_failures do
+ user.reload
+ expect(user.locked_at).not_to be_nil
+ expect(user.unlock_token).to be_nil # The unlock token is only set after logging in with valid credentials
+ expect(user.failed_attempts).to eq(RequireEmailVerification::MAXIMUM_ATTEMPTS)
+ end
+
+ it_behaves_like 'email verification required'
+ it_behaves_like 'no email verification required when 2fa enabled or ff disabled'
+
+ describe 'when waiting for the auto unlock time' do
+ before do
+ travel User::UNLOCK_IN + 1.second
+ end
+
+ it_behaves_like 'no email verification required'
+ end
+ end
+
+ describe 'when no previous authentication event exists' do
+ it_behaves_like 'no email verification required'
+ end
+
+ describe 'when a previous authentication event exists for another ip address' do
+ before do
+ create(:authentication_event, :successful, user: user, ip_address: '1.2.3.4')
+ end
+
+ it_behaves_like 'email verification required'
+ it_behaves_like 'no email verification required when 2fa enabled or ff disabled'
+ end
+
+ describe 'when a previous authentication event exists for the same ip address' do
+ before do
+ create(:authentication_event, :successful, user: user)
+ end
+
+ it_behaves_like 'no email verification required'
+ end
+
+ describe 'rate limiting password guessing' do
+ before do
+ 5.times { gitlab_sign_in(user, password: 'wrong_password') }
+ gitlab_sign_in(user)
+ end
+
+ it 'shows an error message on on the login page' do
+ expect(page).to have_current_path(new_user_session_path)
+ expect(page).to have_content('Maximum login attempts exceeded. Wait 10 minutes and try again.')
+ end
+ end
+
+ describe 'inconsistent states' do
+ context 'when the feature flag is toggled off after being prompted for a verification token' do
+ before do
+ create(:authentication_event, :successful, user: user, ip_address: '1.2.3.4')
+ end
+
+ it 'still accepts the token' do
+ perform_enqueued_jobs do
+ # The user is prompted for a verification code
+ gitlab_sign_in(user)
+ expect(page).to have_content('Help us protect your account')
+ code = expect_instructions_email_and_extract_code
+
+ # We toggle the feature flag off
+ stub_feature_flags(require_email_verification: false)
+
+ # Resending and veryfying the code work as expected
+ click_link 'Resend code'
+ new_code = expect_instructions_email_and_extract_code
+
+ verify_code(code)
+ expect(page).to have_content('The code is incorrect. Enter it again, or resend a new code.')
+
+ travel VerifiesWithEmail::TOKEN_VALID_FOR_MINUTES.minutes + 1.second
+
+ verify_code(new_code)
+ expect(page).to have_content('The code has expired. Resend a new code and try again.')
+
+ click_link 'Resend code'
+ another_code = expect_instructions_email_and_extract_code
+
+ verify_code(another_code)
+ expect_user_to_be_unlocked
+ expect(page).to have_current_path(users_successful_verification_path)
+ end
+ end
+ end
+
+ context 'when the feature flag is toggled on after Devise sent unlock instructions' do
+ let(:require_email_verification_enabled) { false }
+
+ before do
+ perform_enqueued_jobs do
+ (User.maximum_attempts / 2).times do
+ gitlab_sign_in(user, password: 'wrong_password')
+ end
+ end
+ end
+
+ it 'the unlock link still works' do
+ # The user is locked and unlock instructions are sent
+ expect(page).to have_content('Invalid login or password.')
+ user.reload
+ expect(user.locked_at).not_to be_nil
+ expect(user.unlock_token).not_to be_nil
+ mail = find_email_for(user)
+
+ expect(mail.to).to match_array([user.email])
+ expect(mail.subject).to eq('Unlock instructions')
+ unlock_url = mail.body.parts.first.to_s[/http.*/]
+
+ # We toggle the feature flag on
+ stub_feature_flags(require_email_verification: true)
+
+ # Unlocking works as expected
+ visit unlock_url
+ expect_user_to_be_unlocked
+ expect(page).to have_current_path(new_user_session_path)
+ expect(page).to have_content('Your account has been unlocked successfully')
+
+ gitlab_sign_in(user)
+ expect(page).to have_current_path(root_path)
+ end
+ end
+ end
+
+ def expect_user_to_be_unlocked
+ user.reload
+
+ aggregate_failures do
+ expect(user.locked_at).to be_nil
+ expect(user.unlock_token).to be_nil
+ expect(user.failed_attempts).to eq(0)
+ end
+ end
+
+ def expect_instructions_email_and_extract_code
+ mail = find_email_for(user)
+ expect(mail.to).to match_array([user.email])
+ expect(mail.subject).to eq('Verify your identity')
+ code = mail.body.parts.first.to_s[/\d{#{VerifiesWithEmail::TOKEN_LENGTH}}/]
+ reset_delivered_emails!
+ code
+ end
+
+ def verify_code(code)
+ fill_in 'Verification code', with: code
+ click_button 'Verify code'
+ end
+
+ def expect_log_message(event = nil, times = 1, reason: '', message: nil)
+ expect(Gitlab::AppLogger).to have_received(:info)
+ .exactly(times).times
+ .with(message || hash_including(message: 'Email Verification',
+ event: event,
+ username: user.username,
+ ip: '127.0.0.1',
+ reason: reason))
+ end
+end