# frozen_string_literal: true require 'spec_helper' RSpec.describe RegistrationsController do include TermsHelper before do stub_application_setting(require_admin_approval_after_user_signup: false) end describe '#new' do subject { get :new } it 'renders new template and sets the resource variable' do expect(subject).to render_template(:new) expect(response).to have_gitlab_http_status(:ok) expect(assigns(:resource)).to be_a(User) end end describe '#create' do let_it_be(:base_user_params) do { first_name: 'first', last_name: 'last', username: 'new_username', email: 'new@user.com', password: 'Any_password' } end let_it_be(:user_params) { { user: base_user_params } } let(:session_params) { {} } subject { 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 `send_user_confirmation_email` is true' do before do stub_application_setting(send_user_confirmation_email: true) 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 `send_user_confirmation_email` is true' do before do stub_application_setting(send_user_confirmation_email: true) end it 'sends a confirmation email' do expect { subject } .to have_enqueued_mail(DeviseMailer, :confirmation_instructions) end end end end end context 'email confirmation' do context 'when send_user_confirmation_email is false' do it 'signs the user in' do stub_application_setting(send_user_confirmation_email: false) expect { subject }.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions) expect(controller.current_user).not_to be_nil end end context 'when send_user_confirmation_email is true' do before do stub_application_setting(send_user_confirmation_email: true) end context 'when soft email confirmation is not enabled' do before do stub_feature_flags(soft_email_confirmation: false) 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 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(:extra_session_params) { {} } let(:session_params) do { invite_email: user_params.dig(:user, :email), originating_member_id: originating_member_id }.merge extra_session_params 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 ) end end context 'when member does not exist from the session key value' do let(:originating_member_id) { -1 } it 'does not track invite acceptance' do subject expect_no_snowplow_event( category: 'RegistrationsController', action: 'accepted', label: 'invite_email' ) end end context 'with the invite_email_preview_text experiment', :experiment do let(:extra_session_params) { { invite_email_experiment_name: 'invite_email_preview_text' } } context 'when member and invite_email_experiment_name exists from the session key value' do it 'tracks the invite acceptance' do expect(experiment(:invite_email_preview_text)).to track(:accepted) .with_context(actor: member) .on_next_instance subject end end context 'when member does not exist from the session key value' do let(:originating_member_id) { -1 } it 'does not track invite acceptance' do expect(experiment(:invite_email_preview_text)).not_to track(:accepted) subject end end context 'when invite_email_experiment_name does not exist from the session key value' do let(:extra_session_params) { {} } it 'does not track invite acceptance' do expect(experiment(:invite_email_preview_text)).not_to track(:accepted) subject end end end context 'with the invite_email_preview_text experiment', :experiment do let(:extra_session_params) { { invite_email_experiment_name: 'invite_email_from' } } context 'when member and invite_email_experiment_name exists from the session key value' do it 'tracks the invite acceptance' do expect(experiment(:invite_email_from)).to track(:accepted) .with_context(actor: member) .on_next_instance subject end end context 'when member does not exist from the session key value' do let(:originating_member_id) { -1 } it 'does not track invite acceptance' do expect(experiment(:invite_email_from)).not_to track(:accepted) subject end end context 'when invite_email_experiment_name does not exist from the session key value' do let(:extra_session_params) { {} } it 'does not track invite acceptance' do expect(experiment(:invite_email_from)).not_to track(:accepted) subject end 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 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: 'Any_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 context 'when soft email confirmation is enabled' do before do stub_feature_flags(soft_email_confirmation: true) 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 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 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 it 'displays an error when the reCAPTCHA is not solved' do allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) 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 '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 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("#{base_user_params[:first_name]} #{base_user_params[:last_name]}") end it 'sets the username and 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.user' => base_user_params[:username], 'meta.caller_id' => 'RegistrationsController#create') end subject 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: '12345678' } 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: '12345678' } 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