debian-mirror-gitlab/spec/controllers/registrations_controller_spec.rb
2023-07-09 08:55:56 +05:30

767 lines
25 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
RSpec.describe RegistrationsController, feature_category: :user_profile do
include TermsHelper
include FullNameHelper
before do
stub_application_setting(require_admin_approval_after_user_signup: false)
stub_feature_flags(arkose_labs_signup_challenge: false)
end
describe '#new' do
subject(:new) { get :new }
it 'renders new template and sets the resource variable' do
expect(new).to render_template(:new)
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:resource)).to be_a(User)
end
it_behaves_like "switches to user preferred language", 'Sign up'
render_views
it 'has the expected registration url' do
new
expect(response.body).to include("action=\"#{user_registration_path}\"")
end
end
describe '#create' do
before do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
end
let_it_be(:base_user_params) do
{ first_name: 'first', last_name: 'last', username: 'new_username', email: 'new@user.com', password: User.random_password }
end
let_it_be(:user_params) { { user: base_user_params } }
let(:session_params) { {} }
subject(:post_create) { post(:create, params: user_params, session: session_params) }
context '`blocked_pending_approval` state' do
context 'when the `require_admin_approval_after_user_signup` setting is turned on' do
before do
stub_application_setting(require_admin_approval_after_user_signup: true)
end
it 'signs up the user in `blocked_pending_approval` state' do
subject
created_user = User.find_by(email: 'new@user.com')
expect(created_user).to be_present
expect(created_user.blocked_pending_approval?).to eq(true)
end
it 'does not log in the user after sign up' do
subject
expect(controller.current_user).to be_nil
end
it 'shows flash message after signing up' do
subject
expect(response).to redirect_to(new_user_session_path(anchor: 'login-pane'))
expect(flash[:notice])
.to eq('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator.')
end
it 'emails the access request to approvers' do
expect_next_instance_of(NotificationService) do |notification|
allow(notification).to receive(:new_instance_access_request).with(User.find_by(email: 'new@user.com'))
end
subject
end
context 'email confirmation' do
context 'when email confirmation setting is set to `hard`' do
before do
stub_application_setting_enum('email_confirmation_setting', 'hard')
end
it 'does not send a confirmation email' do
expect { subject }
.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
end
end
end
context 'audit events' do
context 'when not licensed' do
before do
stub_licensed_features(admin_audit_log: false)
end
it 'does not log any audit event' do
expect { subject }.not_to change(AuditEvent, :count)
end
end
end
end
context 'when the `require_admin_approval_after_user_signup` setting is turned off' do
it 'signs up the user in `active` state' do
subject
created_user = User.find_by(email: 'new@user.com')
expect(created_user).to be_present
expect(created_user.active?).to eq(true)
end
it 'does not show any flash message after signing up' do
subject
expect(flash[:notice]).to be_nil
end
it 'does not email the approvers' do
expect(NotificationService).not_to receive(:new)
subject
end
context 'email confirmation' do
context 'when email confirmation setting is set to `hard`' do
before do
stub_application_setting_enum('email_confirmation_setting', 'hard')
stub_feature_flags(identity_verification: false)
end
it 'sends a confirmation email' do
expect { subject }
.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
end
end
end
end
end
context 'private profile' do
context 'when the `user_defaults_to_private_profile` setting is turned on' do
before do
stub_application_setting(user_defaults_to_private_profile: true)
end
it 'creates new user with profile set to private' do
subject
user = User.find_by(email: base_user_params[:email], private_profile: true)
expect(user).to be_present
end
end
end
context 'email confirmation' do
before do
stub_feature_flags(identity_verification: false)
end
context 'when email confirmation setting is set to `off`' do
it 'signs the user in' do
stub_application_setting_enum('email_confirmation_setting', 'off')
expect { subject }.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
expect(controller.current_user).not_to be_nil
end
end
context 'when email confirmation setting is set to `hard`' do
before do
stub_application_setting_enum('email_confirmation_setting', 'hard')
allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
end
it 'does not authenticate the user and sends a confirmation email' do
expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
expect(controller.current_user).to be_nil
end
it 'tracks an almost there redirect' do
post_create
expect_snowplow_event(
category: described_class.name,
action: 'render',
user: User.find_by(email: base_user_params[:email])
)
end
context 'when registration is triggered from an accepted invite' do
context 'when it is part from the initial invite email', :snowplow do
let_it_be(:member) { create(:project_member, :invited, invite_email: user_params.dig(:user, :email)) }
let(:originating_member_id) { member.id }
let(:session_params) do
{
invite_email: user_params.dig(:user, :email),
originating_member_id: originating_member_id
}
end
context 'when member exists from the session key value' do
it 'tracks the invite acceptance' do
subject
expect_snowplow_event(
category: 'RegistrationsController',
action: 'accepted',
label: 'invite_email',
property: member.id.to_s,
user: member.reload.user
)
expect_snowplow_event(
category: 'RegistrationsController',
action: 'create_user',
label: 'invited',
user: member.reload.user
)
end
end
context 'when member does not exist from the session key value' do
let(:originating_member_id) { nil }
it 'does not track invite acceptance' do
subject
expect_no_snowplow_event(
category: 'RegistrationsController',
action: 'accepted',
label: 'invite_email'
)
expect_snowplow_event(
category: 'RegistrationsController',
action: 'create_user',
label: 'signup',
user: member.reload.user
)
end
end
end
context 'when invite email matches email used on registration' do
let(:session_params) { { invite_email: user_params.dig(:user, :email) } }
it 'signs the user in without sending a confirmation email', :aggregate_failures do
expect { subject }.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
expect(controller.current_user).to be_confirmed
end
end
context 'when invite email does not match the email used on registration' do
let(:session_params) { { invite_email: 'bogus@email.com' } }
it 'does not authenticate the user and sends a confirmation email', :aggregate_failures do
expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
expect(controller.current_user).to be_nil
end
end
end
context 'when the registration fails' do
let_it_be(:member) { create(:project_member, :invited) }
let_it_be(:missing_user_params) do
{ username: '', email: member.invite_email, password: User.random_password }
end
let_it_be(:user_params) { { user: missing_user_params } }
let(:session_params) { { invite_email: member.invite_email } }
subject { post(:create, params: user_params, session: session_params) }
it 'does not delete the invitation or register the new user' do
subject
expect(member.invite_token).not_to be_nil
expect(controller.current_user).to be_nil
end
end
end
context 'when email confirmation setting is set to `soft`' do
before do
stub_application_setting_enum('email_confirmation_setting', 'soft')
allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
end
it 'authenticates the user and sends a confirmation email' do
expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
expect(controller.current_user).to be_present
expect(response).to redirect_to(users_sign_up_welcome_path)
end
it 'does not track an almost there redirect' do
post_create
expect_no_snowplow_event(
category: described_class.name,
action: 'render',
user: User.find_by(email: base_user_params[:email])
)
end
context 'when invite email matches email used on registration' do
let(:session_params) { { invite_email: user_params.dig(:user, :email) } }
it 'signs the user in without sending a confirmation email', :aggregate_failures do
expect { subject }.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
expect(controller.current_user).to be_confirmed
end
end
context 'when invite email does not match the email used on registration' do
let(:session_params) { { invite_email: 'bogus@email.com' } }
it 'authenticates the user and sends a confirmation email without confirming', :aggregate_failures do
expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
expect(controller.current_user).not_to be_confirmed
end
end
end
context 'when signup_enabled? is false' do
it 'redirects to sign_in' do
stub_application_setting(signup_enabled: false)
expect { subject }.not_to change(User, :count)
expect(response).to redirect_to(new_user_session_path)
end
end
end
context 'when reCAPTCHA is enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
end
after do
# Avoid test ordering issue and ensure `verify_recaptcha` returns true
unless Recaptcha.configuration.skip_verify_env.include?('test')
Recaptcha.configuration.skip_verify_env << 'test'
end
end
context 'when the reCAPTCHA is not solved' do
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
end
it 'displays an error' do
subject
expect(response).to render_template(:new)
expect(flash[:alert]).to eq(_('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'))
end
it 'sets gon variables' do
Gon.clear
subject
expect(response).to render_template(:new)
expect(Gon.all_variables).not_to be_empty
end
end
it 'redirects to the welcome page when the reCAPTCHA is solved' do
subject
expect(response).to redirect_to(users_sign_up_welcome_path)
end
end
context 'when invisible captcha is enabled' do
before do
stub_application_setting(invisible_captcha_enabled: true)
InvisibleCaptcha.timestamp_threshold = treshold
end
let(:treshold) { 4 }
let(:session_params) { { invisible_captcha_timestamp: form_rendered_time.iso8601 } }
let(:form_rendered_time) { Time.current }
let(:submit_time) { form_rendered_time + treshold }
let(:auth_log_attributes) do
{
message: auth_log_message,
env: :invisible_captcha_signup_bot_detected,
remote_ip: '0.0.0.0',
request_method: 'POST',
path: '/users'
}
end
describe 'the honeypot has not been filled and the signup form has not been submitted too quickly' do
it 'creates an account' do
travel_to(submit_time) do
expect { post(:create, params: user_params, session: session_params) }.to change(User, :count).by(1)
end
end
end
describe 'honeypot spam detection' do
let(:user_params) { super().merge(firstname: 'Roy', lastname: 'Batty') }
let(:auth_log_message) { 'Invisible_Captcha_Honeypot_Request' }
it 'logs the request, refuses to create an account and renders an empty body' do
travel_to(submit_time) do
expect(Gitlab::Metrics).to receive(:counter)
.with(:bot_blocked_by_invisible_captcha_honeypot, 'Counter of blocked sign up attempts with filled honeypot')
.and_call_original
expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to be_empty
end
end
end
describe 'timestamp spam detection' do
let(:auth_log_message) { 'Invisible_Captcha_Timestamp_Request' }
context 'the sign up form has been submitted without the invisible_captcha_timestamp parameter' do
let(:session_params) { nil }
it 'logs the request, refuses to create an account and displays a flash alert' do
travel_to(submit_time) do
expect(Gitlab::Metrics).to receive(:counter)
.with(:bot_blocked_by_invisible_captcha_timestamp, 'Counter of blocked sign up attempts with invalid timestamp')
.and_call_original
expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
expect(response).to redirect_to(new_user_session_path)
expect(flash[:alert]).to eq(I18n.t('invisible_captcha.timestamp_error_message'))
end
end
end
context 'the sign up form has been submitted too quickly' do
let(:submit_time) { form_rendered_time }
it 'logs the request, refuses to create an account and displays a flash alert' do
travel_to(submit_time) do
expect(Gitlab::Metrics).to receive(:counter)
.with(:bot_blocked_by_invisible_captcha_timestamp, 'Counter of blocked sign up attempts with invalid timestamp')
.and_call_original
expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
expect(response).to redirect_to(new_user_session_path)
expect(flash[:alert]).to eq(I18n.t('invisible_captcha.timestamp_error_message'))
end
end
end
end
end
context 'terms of service' do
context 'when terms are enforced' do
before do
enforce_terms
end
it 'creates the user with accepted terms' do
subject
expect(controller.current_user).to be_present
expect(controller.current_user.terms_accepted?).to be(true)
end
end
context 'when terms are not enforced' do
it 'creates the user without accepted terms' do
subject
expect(controller.current_user).to be_present
expect(controller.current_user.terms_accepted?).to be(false)
end
end
end
context 'when the rate limit has been reached' do
it 'returns status 429 Too Many Requests', :aggregate_failures do
ip = '1.2.3.4'
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:user_sign_up, scope: ip).and_return(true)
controller.request.env['REMOTE_ADDR'] = ip
post(:create, params: user_params, session: session_params)
expect(response).to have_gitlab_http_status(:too_many_requests)
end
end
it "logs a 'User Created' message" do
expect(Gitlab::AppLogger).to receive(:info).with(/\AUser Created: username=new_username email=new@user.com.+\z/).and_call_original
subject
end
it 'handles when params are new_user' do
post(:create, params: { new_user: base_user_params })
expect(controller.current_user).not_to be_nil
end
it 'sets name from first and last name' do
post :create, params: { new_user: base_user_params }
expect(User.last.first_name).to eq(base_user_params[:first_name])
expect(User.last.last_name).to eq(base_user_params[:last_name])
expect(User.last.name).to eq full_name(base_user_params[:first_name], base_user_params[:last_name])
end
it 'sets the caller_id in the context' do
expect(controller).to receive(:create).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
.to include('meta.caller_id' => 'RegistrationsController#create')
end
subject
end
context 'when the password is weak' do
render_views
let_it_be(:new_user_params) { { new_user: base_user_params.merge({ password: "password" }) } }
subject(:post_create) { post(:create, params: new_user_params) }
it 'renders the form with errors' do
expect { post_create }.not_to change(User, :count)
expect(controller.current_user).to be_nil
expect(response).to render_template(:new)
expect(response.body).to include(_('Password must not contain commonly used combinations of words and letters'))
end
it 'tracks a weak password error' do
post_create
expect_snowplow_event(
category: 'Gitlab::Tracking::Helpers::WeakPasswordErrorEvent',
action: 'track_weak_password_error',
controller: 'RegistrationsController',
method: 'create'
)
end
it 'does not track failed form submission' do
post_create
expect_no_snowplow_event(
category: described_class.name,
action: 'successfully_submitted_form'
)
end
end
context 'when the password is not weak' do
it 'does not track a weak password error' do
post_create
expect_no_snowplow_event(
category: 'Gitlab::Tracking::Helpers::WeakPasswordErrorEvent',
action: 'track_weak_password_error'
)
end
it 'tracks successful form submission' do
post_create
expect_snowplow_event(
category: described_class.name,
action: 'successfully_submitted_form',
user: User.find_by(email: base_user_params[:email])
)
end
end
context 'with preferred language' do
let(:user_preferred_language) { nil }
before do
cookies['preferred_language'] = user_preferred_language
post :create, params: { new_user: base_user_params }
end
subject { User.last.preferred_language }
context 'with default behavior' do
it 'sets preferred language to default' do
is_expected.to eq(Gitlab::CurrentSettings.default_preferred_language)
end
end
context 'when user sets preferred language' do
let(:user_preferred_language) { 'zh_CN' }
it 'sets name from first and last name' do
is_expected.to eq(user_preferred_language)
end
end
end
context 'when the first or last name is not "present?"' do
using RSpec::Parameterized::TableSyntax
render_views
shared_examples 'a user without present first name or last name' do
it 'renders the form with errors' do
subject
expect(controller.current_user).to be_nil
expect(response).to render_template(:new)
expect(response.body).to include(_('name cannot be blank')) # include 'First name' or 'Last name' or both
end
end
where(:first_name, :last_name) do
nil | 'last'
'' | 'last'
' ' | 'last'
'first' | nil
'first' | ''
'first' | ' '
'' | ''
end
with_them do
before do
base_user_params.merge!({ first_name: first_name, last_name: last_name })
end
it_behaves_like 'a user without present first name or last name'
end
end
end
describe '#destroy' do
let(:user) { create(:user) }
before do
sign_in(user)
end
def expect_failure(message)
expect(flash[:alert]).to eq(message)
expect(response).to have_gitlab_http_status(:see_other)
expect(response).to redirect_to profile_account_path
end
def expect_password_failure
expect_failure(s_('Profiles|Invalid password'))
end
def expect_username_failure
expect_failure(s_('Profiles|Invalid username'))
end
def expect_success
expect(flash[:notice]).to eq s_('Profiles|Account scheduled for removal.')
expect(response).to have_gitlab_http_status(:see_other)
expect(response).to redirect_to new_user_session_path
end
context 'user requires password confirmation' do
it 'fails if password confirmation is not provided' do
post :destroy
expect_password_failure
end
it 'fails if password confirmation is wrong' do
post :destroy, params: { password: 'wrong password' }
expect_password_failure
end
it 'succeeds if password is confirmed' do
post :destroy, params: { password: user.password }
expect_success
end
end
context 'user does not require password confirmation' do
before do
stub_application_setting(password_authentication_enabled_for_web: false)
stub_application_setting(password_authentication_enabled_for_git: false)
end
it 'fails if username confirmation is not provided' do
post :destroy
expect_username_failure
end
it 'fails if username confirmation is wrong' do
post :destroy, params: { username: 'wrong username' }
expect_username_failure
end
it 'succeeds if username is confirmed' do
post :destroy, params: { username: user.username }
expect_success
end
end
context 'prerequisites for account deletion' do
context 'solo-owned groups' do
let(:group) { create(:group) }
context 'if the user is the sole owner of at least one group' do
before do
create(:group_member, :owner, group: group, user: user)
end
it 'fails' do
delete :destroy, params: { password: user.password }
expect_failure(s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account'))
end
end
end
end
context 'when user did not accept app terms' do
let(:user) { create(:user, accepted_term: nil) }
before do
stub_application_setting(password_authentication_enabled_for_web: false)
stub_application_setting(password_authentication_enabled_for_git: false)
stub_application_setting(enforce_terms: true)
end
it 'fails with message' do
post :destroy, params: { username: user.username }
expect_failure(s_('Profiles|You must accept the Terms of Service in order to perform this action.'))
end
end
it 'sets the username and caller_id in the context' do
expect(controller).to receive(:destroy).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
.to include('meta.user' => user.username, 'meta.caller_id' => 'RegistrationsController#destroy')
end
post :destroy
end
end
end