# coding: utf-8 require 'spec_helper' describe ApplicationController do include TermsHelper let(:user) { create(:user) } describe '#check_password_expiration' do let(:controller) { described_class.new } before do allow(controller).to receive(:session).and_return({}) end it 'redirects if the user is over their password expiry' do user.password_expires_at = Time.new(2002) expect(user.ldap_user?).to be_falsey allow(controller).to receive(:current_user).and_return(user) expect(controller).to receive(:redirect_to) expect(controller).to receive(:new_profile_password_path) controller.send(:check_password_expiration) end it 'does not redirect if the user is under their password expiry' do user.password_expires_at = Time.now + 20010101 expect(user.ldap_user?).to be_falsey allow(controller).to receive(:current_user).and_return(user) expect(controller).not_to receive(:redirect_to) controller.send(:check_password_expiration) end it 'does not redirect if the user is over their password expiry but they are an ldap user' do user.password_expires_at = Time.new(2002) allow(user).to receive(:ldap_user?).and_return(true) allow(controller).to receive(:current_user).and_return(user) expect(controller).not_to receive(:redirect_to) controller.send(:check_password_expiration) end it 'does not redirect if the user is over their password expiry but password authentication is disabled for the web interface' do stub_application_setting(password_authentication_enabled_for_web: false) stub_application_setting(password_authentication_enabled_for_git: false) user.password_expires_at = Time.new(2002) allow(controller).to receive(:current_user).and_return(user) expect(controller).not_to receive(:redirect_to) controller.send(:check_password_expiration) end end describe '#add_gon_variables' do before do Gon.clear sign_in user end let(:json_response) { JSON.parse(response.body) } controller(described_class) do def index render json: Gon.all_variables end end shared_examples 'setting gon variables' do it 'sets gon variables' do get :index, format: format expect(json_response.size).not_to be_zero end end shared_examples 'not setting gon variables' do it 'does not set gon variables' do get :index, format: format expect(json_response.size).to be_zero end end context 'with html format' do let(:format) { :html } it_behaves_like 'setting gon variables' context 'for peek requests' do before do request.path = '/-/peek' end it_behaves_like 'not setting gon variables' end end context 'with json format' do let(:format) { :json } it_behaves_like 'not setting gon variables' end end describe "#authenticate_user_from_personal_access_token!" do before do stub_authentication_activity_metrics(debug: false) end controller(described_class) do def index render text: 'authenticated' end end let(:personal_access_token) { create(:personal_access_token, user: user) } context "when the 'personal_access_token' param is populated with the personal access token" do it "logs the user in" do expect(authentication_metrics) .to increment(:user_authenticated_counter) .and increment(:user_session_override_counter) .and increment(:user_sessionless_authentication_counter) get :index, private_token: personal_access_token.token expect(response).to have_gitlab_http_status(200) expect(response.body).to eq('authenticated') end end context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do it "logs the user in" do expect(authentication_metrics) .to increment(:user_authenticated_counter) .and increment(:user_session_override_counter) .and increment(:user_sessionless_authentication_counter) @request.headers["PRIVATE-TOKEN"] = personal_access_token.token get :index expect(response).to have_gitlab_http_status(200) expect(response.body).to eq('authenticated') end end it "doesn't log the user in otherwise" do expect(authentication_metrics) .to increment(:user_unauthenticated_counter) get :index, private_token: "token" expect(response.status).not_to eq(200) expect(response.body).not_to eq('authenticated') end end describe 'session expiration' do controller(described_class) do # The anonymous controller will report 401 and fail to run any actions. # Normally, GitLab will just redirect you to sign in. skip_before_action :authenticate_user!, only: :index def index render text: 'authenticated' end end context 'authenticated user' do it 'does not set the expire_after option' do sign_in(create(:user)) get :index expect(request.env['rack.session.options'][:expire_after]).to be_nil end end context 'unauthenticated user' do it 'sets the expire_after option' do get :index expect(request.env['rack.session.options'][:expire_after]).to eq(Settings.gitlab['unauthenticated_session_expire_delay']) end end end describe 'rescue from Gitlab::Git::Storage::Inaccessible' do controller(described_class) do def index raise Gitlab::Git::Storage::Inaccessible.new('broken', 100) end end it 'renders a 503 when storage is not available' do sign_in(create(:user)) get :index expect(response.status).to eq(503) end it 'renders includes a Retry-After header' do sign_in(create(:user)) get :index expect(response.headers['Retry-After']).to eq(100) end end describe 'response format' do controller(described_class) do def index respond_to do |format| format.json do head :ok end end end end before do sign_in user end context 'when format is handled' do let(:requested_format) { :json } it 'returns 200 response' do get :index, format: requested_format expect(response).to have_gitlab_http_status 200 end end context 'when format is not handled' do it 'returns 404 response' do get :index expect(response).to have_gitlab_http_status 404 end end end describe '#authenticate_sessionless_user!' do before do stub_authentication_activity_metrics(debug: false) end describe 'authenticating a user from a feed token' do controller(described_class) do def index render text: 'authenticated' end end context "when the 'feed_token' param is populated with the feed token" do context 'when the request format is atom' do it "logs the user in" do expect(authentication_metrics) .to increment(:user_authenticated_counter) .and increment(:user_session_override_counter) .and increment(:user_sessionless_authentication_counter) get :index, feed_token: user.feed_token, format: :atom expect(response).to have_gitlab_http_status 200 expect(response.body).to eq 'authenticated' end end context 'when the request format is ics' do it "logs the user in" do expect(authentication_metrics) .to increment(:user_authenticated_counter) .and increment(:user_session_override_counter) .and increment(:user_sessionless_authentication_counter) get :index, feed_token: user.feed_token, format: :ics expect(response).to have_gitlab_http_status 200 expect(response.body).to eq 'authenticated' end end context 'when the request format is neither atom nor ics' do it "doesn't log the user in" do expect(authentication_metrics) .to increment(:user_unauthenticated_counter) get :index, feed_token: user.feed_token expect(response.status).not_to have_gitlab_http_status 200 expect(response.body).not_to eq 'authenticated' end end end context "when the 'feed_token' param is populated with an invalid feed token" do it "doesn't log the user" do expect(authentication_metrics) .to increment(:user_unauthenticated_counter) get :index, feed_token: 'token', format: :atom expect(response.status).not_to eq 200 expect(response.body).not_to eq 'authenticated' end end end end describe '#route_not_found' do it 'renders 404 if authenticated' do allow(controller).to receive(:current_user).and_return(user) expect(controller).to receive(:not_found) controller.send(:route_not_found) end it 'does redirect to login page via authenticate_user! if not authenticated' do allow(controller).to receive(:current_user).and_return(nil) expect(controller).to receive(:authenticate_user!) controller.send(:route_not_found) end end describe '#set_page_title_header' do let(:controller) { described_class.new } it 'URI encodes UTF-8 characters in the title' do response = double(headers: {}) allow_any_instance_of(PageLayoutHelper).to receive(:page_title).and_return('€100 · GitLab') allow(controller).to receive(:response).and_return(response) controller.send(:set_page_title_header) expect(response.headers['Page-Title']).to eq('%E2%82%AC100%20%C2%B7%20GitLab') end end context 'two-factor authentication' do let(:controller) { described_class.new } describe '#check_two_factor_requirement' do subject { controller.send :check_two_factor_requirement } it 'does not redirect if 2FA is not required' do allow(controller).to receive(:two_factor_authentication_required?).and_return(false) expect(controller).not_to receive(:redirect_to) subject end it 'does not redirect if user is not logged in' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) allow(controller).to receive(:current_user).and_return(nil) expect(controller).not_to receive(:redirect_to) subject end it 'does not redirect if user has 2FA enabled' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) allow(controller).to receive(:current_user).twice.and_return(user) allow(user).to receive(:two_factor_enabled?).and_return(true) expect(controller).not_to receive(:redirect_to) subject end it 'does not redirect if 2FA setup can be skipped' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) allow(controller).to receive(:current_user).twice.and_return(user) allow(user).to receive(:two_factor_enabled?).and_return(false) allow(controller).to receive(:skip_two_factor?).and_return(true) expect(controller).not_to receive(:redirect_to) subject end it 'redirects to 2FA setup otherwise' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) allow(controller).to receive(:current_user).twice.and_return(user) allow(user).to receive(:two_factor_enabled?).and_return(false) allow(controller).to receive(:skip_two_factor?).and_return(false) allow(controller).to receive(:profile_two_factor_auth_path) expect(controller).to receive(:redirect_to) subject end end describe '#two_factor_authentication_required?' do subject { controller.send :two_factor_authentication_required? } it 'returns false if no 2FA requirement is present' do allow(controller).to receive(:current_user).and_return(nil) expect(subject).to be_falsey end it 'returns true if a 2FA requirement is set in the application settings' do stub_application_setting require_two_factor_authentication: true allow(controller).to receive(:current_user).and_return(nil) expect(subject).to be_truthy end it 'returns true if a 2FA requirement is set on the user' do user.require_two_factor_authentication_from_group = true allow(controller).to receive(:current_user).and_return(user) expect(subject).to be_truthy end end describe '#two_factor_grace_period' do subject { controller.send :two_factor_grace_period } it 'returns the grace period from the application settings' do stub_application_setting two_factor_grace_period: 23 allow(controller).to receive(:current_user).and_return(nil) expect(subject).to eq 23 end context 'with a 2FA requirement set on the user' do let(:user) { create :user, require_two_factor_authentication_from_group: true, two_factor_grace_period: 23 } it 'returns the user grace period if lower than the application grace period' do stub_application_setting two_factor_grace_period: 24 allow(controller).to receive(:current_user).and_return(user) expect(subject).to eq 23 end it 'returns the application grace period if lower than the user grace period' do stub_application_setting two_factor_grace_period: 22 allow(controller).to receive(:current_user).and_return(user) expect(subject).to eq 22 end end end describe '#two_factor_grace_period_expired?' do subject { controller.send :two_factor_grace_period_expired? } before do allow(controller).to receive(:current_user).and_return(user) end it 'returns false if the user has not started their grace period yet' do expect(subject).to be_falsey end context 'with grace period started' do let(:user) { create :user, otp_grace_period_started_at: 2.hours.ago } it 'returns true if the grace period has expired' do allow(controller).to receive(:two_factor_grace_period).and_return(1) expect(subject).to be_truthy end it 'returns false if the grace period is still active' do allow(controller).to receive(:two_factor_grace_period).and_return(3) expect(subject).to be_falsey end end end describe '#two_factor_skippable' do subject { controller.send :two_factor_skippable? } before do allow(controller).to receive(:current_user).and_return(user) end it 'returns false if 2FA is not required' do allow(controller).to receive(:two_factor_authentication_required?).and_return(false) expect(subject).to be_falsey end it 'returns false if the user has already enabled 2FA' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) allow(user).to receive(:two_factor_enabled?).and_return(true) expect(subject).to be_falsey end it 'returns false if the 2FA grace period has expired' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) allow(user).to receive(:two_factor_enabled?).and_return(false) allow(controller).to receive(:two_factor_grace_period_expired?).and_return(true) expect(subject).to be_falsey end it 'returns true otherwise' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) allow(user).to receive(:two_factor_enabled?).and_return(false) allow(controller).to receive(:two_factor_grace_period_expired?).and_return(false) expect(subject).to be_truthy end end describe '#skip_two_factor?' do subject { controller.send :skip_two_factor? } it 'returns false if 2FA setup was not skipped' do allow(controller).to receive(:session).and_return({}) expect(subject).to be_falsey end context 'with 2FA setup skipped' do before do allow(controller).to receive(:session).and_return({ skip_two_factor: 2.hours.from_now }) end it 'returns false if the grace period has expired' do Timecop.freeze(3.hours.from_now) do expect(subject).to be_falsey end end it 'returns true if the grace period is still active' do Timecop.freeze(1.hour.from_now) do expect(subject).to be_truthy end end end end end context 'terms' do controller(described_class) do def index render text: 'authenticated' end end before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') sign_in user end it 'does not query more when terms are enforced' do control = ActiveRecord::QueryRecorder.new { get :index } enforce_terms expect { get :index }.not_to exceed_query_limit(control) end context 'when terms are enforced' do before do enforce_terms end it 'redirects if the user did not accept the terms' do get :index expect(response).to have_gitlab_http_status(302) end it 'does not redirect when the user accepted terms' do accept_terms(user) get :index expect(response).to have_gitlab_http_status(200) end context 'for sessionless users' do render_views before do sign_out user end it 'renders a 403 when the sessionless user did not accept the terms' do get :index, feed_token: user.feed_token, format: :atom expect(response).to have_gitlab_http_status(403) end it 'renders the error message when the format was html' do get :index, private_token: create(:personal_access_token, user: user).token, format: :html expect(response.body).to have_content /accept the terms of service/i end it 'renders a 200 when the sessionless user accepted the terms' do accept_terms(user) get :index, feed_token: user.feed_token, format: :atom expect(response).to have_gitlab_http_status(200) end end end end describe '#append_info_to_payload' do controller(described_class) do attr_reader :last_payload def index render text: 'authenticated' end def append_info_to_payload(payload) super @last_payload = payload end end it 'does not log errors with a 200 response' do get :index expect(controller.last_payload.has_key?(:response)).to be_falsey end context '422 errors' do it 'logs a response with a string' do response = spy(ActionDispatch::Response, status: 422, body: 'Hello world', content_type: 'application/json', cookies: {}) allow(controller).to receive(:response).and_return(response) get :index expect(controller.last_payload[:response]).to eq('Hello world') end it 'logs a response with an array' do body = ['I want', 'my hat back'] response = spy(ActionDispatch::Response, status: 422, body: body, content_type: 'application/json', cookies: {}) allow(controller).to receive(:response).and_return(response) get :index expect(controller.last_payload[:response]).to eq(body) end it 'does not log a string with an empty body' do response = spy(ActionDispatch::Response, status: 422, body: nil, content_type: 'application/json', cookies: {}) allow(controller).to receive(:response).and_return(response) get :index expect(controller.last_payload.has_key?(:response)).to be_falsey end it 'does not log an HTML body' do response = spy(ActionDispatch::Response, status: 422, body: 'This is a test', content_type: 'application/html', cookies: {}) allow(controller).to receive(:response).and_return(response) get :index expect(controller.last_payload.has_key?(:response)).to be_falsey end end end describe '#access_denied' do controller(described_class) do def index access_denied!(params[:message]) end end before do sign_in user end it 'renders a 404 without a message' do get :index expect(response).to have_gitlab_http_status(404) end it 'renders a 403 when a message is passed to access denied' do get :index, message: 'None shall pass' expect(response).to have_gitlab_http_status(403) end end context 'when invalid UTF-8 parameters are received' do controller(described_class) do def index params[:text].split(' ') render json: :ok end end before do sign_in user end context 'html' do it 'renders 412' do get :index, text: "hi \255" expect(response).to have_gitlab_http_status(412) expect(response).to render_template :precondition_failed end end context 'js' do it 'renders 412' do get :index, text: "hi \255", format: :js json_response = JSON.parse(response.body) expect(response).to have_gitlab_http_status(412) expect(json_response['error']).to eq('Invalid UTF-8') end end end context 'X-GitLab-Custom-Error header' do before do sign_in user end context 'given a 422 error page' do controller do def index render 'errors/omniauth_error', layout: 'errors', status: 422 end end it 'sets a custom header' do get :index expect(response.headers['X-GitLab-Custom-Error']).to eq '1' end end context 'given a 500 error page' do controller do def index render 'errors/omniauth_error', layout: 'errors', status: 500 end end it 'sets a custom header' do get :index expect(response.headers['X-GitLab-Custom-Error']).to eq '1' end end context 'given a 200 success page' do controller do def index render 'errors/omniauth_error', layout: 'errors', status: 200 end end it 'does not set a custom header' do get :index expect(response.headers['X-GitLab-Custom-Error']).to be_nil end end context 'given a json response' do controller do def index render json: {}, status: :unprocessable_entity end end it 'does not set a custom header' do get :index, format: :json expect(response.headers['X-GitLab-Custom-Error']).to be_nil end end context 'given a json response for an html request' do controller do def index render json: {}, status: :unprocessable_entity end end it 'does not set a custom header' do get :index expect(response.headers['X-GitLab-Custom-Error']).to be_nil end end end end