# frozen_string_literal: true require 'spec_helper' RSpec.describe Gitlab::Auth::CurrentUserMode, :request_store do let(:user) { build_stubbed(:user) } subject { described_class.new(user) } context 'when session is available' do include_context 'custom session' before do allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session]) end shared_examples 'admin mode cannot be enabled' do it 'is false by default' do expect(subject.admin_mode?).to be(false) end it 'cannot be enabled with a valid password' do subject.enable_admin_mode!(password: user.password) expect(subject.admin_mode?).to be(false) end it 'cannot be enabled with an invalid password' do subject.enable_admin_mode!(password: nil) expect(subject.admin_mode?).to be(false) end it 'cannot be enabled with empty params' do subject.enable_admin_mode! expect(subject.admin_mode?).to be(false) end it 'disable has no effect' do subject.enable_admin_mode! subject.disable_admin_mode! expect(subject.admin_mode?).to be(false) end context 'skipping password validation' do it 'cannot be enabled with a valid password' do subject.enable_admin_mode!(password: user.password, skip_password_validation: true) expect(subject.admin_mode?).to be(false) end it 'cannot be enabled with an invalid password' do subject.enable_admin_mode!(skip_password_validation: true) expect(subject.admin_mode?).to be(false) end end end describe '#admin_mode?' do context 'when the user is a regular user' do it_behaves_like 'admin mode cannot be enabled' context 'bypassing session' do it_behaves_like 'admin mode cannot be enabled' do around do |example| described_class.bypass_session!(user.id) { example.run } end end end end context 'when the user is an admin' do let(:user) { build_stubbed(:user, :admin) } context 'when admin mode not requested' do it 'is false by default' do expect(subject.admin_mode?).to be(false) end it 'raises exception if we try to enable it' do expect do subject.enable_admin_mode!(password: user.password) end.to raise_error(::Gitlab::Auth::CurrentUserMode::NotRequestedError) expect(subject.admin_mode?).to be(false) end end context 'when admin mode requested first' do before do subject.request_admin_mode! end it 'is false by default' do expect(subject.admin_mode?).to be(false) end it 'cannot be enabled with an invalid password' do subject.enable_admin_mode!(password: nil) expect(subject.admin_mode?).to be(false) end it 'can be enabled with a valid password' do subject.enable_admin_mode!(password: user.password) expect(subject.admin_mode?).to be(true) end it 'can be disabled' do subject.enable_admin_mode!(password: user.password) subject.disable_admin_mode! expect(subject.admin_mode?).to be(false) end it 'will expire in the future' do subject.enable_admin_mode!(password: user.password) expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present' travel_to(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do # in the future this will be a new request, simulate by clearing the RequestStore Gitlab::SafeRequestStore.clear! expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future' end end context 'skipping password validation' do it 'can be enabled with a valid password' do subject.enable_admin_mode!(password: user.password, skip_password_validation: true) expect(subject.admin_mode?).to be(true) end it 'can be enabled with an invalid password' do subject.enable_admin_mode!(skip_password_validation: true) expect(subject.admin_mode?).to be(true) end end context 'with two independent sessions' do let(:another_session) { {} } let(:another_subject) { described_class.new(user) } before do allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session]) end it 'cannot be enabled in one and seen in the other' do Gitlab::Session.with_session(another_session) do another_subject.request_admin_mode! another_subject.enable_admin_mode!(password: user.password) end expect(subject.admin_mode?).to be(false) end end end context 'bypassing session' do it 'is active by default' do described_class.bypass_session!(user.id) do expect(subject.admin_mode?).to be(true) end end it 'enable has no effect' do described_class.bypass_session!(user.id) do subject.request_admin_mode! subject.enable_admin_mode!(password: user.password) expect(subject.admin_mode?).to be(true) end end it 'disable has no effect' do described_class.bypass_session!(user.id) do subject.disable_admin_mode! expect(subject.admin_mode?).to be(true) end end end end end describe '#enable_admin_mode!' do let(:user) { build_stubbed(:user, :admin) } it 'creates a timestamp in the session' do subject.request_admin_mode! subject.enable_admin_mode!(password: user.password) expect(session).to include(expected_session_entry(be_within(1.second).of(Time.now))) end it 'returns true after successful enable' do subject.request_admin_mode! expect(subject.enable_admin_mode!(password: user.password)).to eq(true) end it 'returns false after unsuccessful enable' do subject.request_admin_mode! expect(subject.enable_admin_mode!(password: 'wrong password')).to eq(false) end context 'when user is not an admin' do let(:user) { build_stubbed(:user) } it 'returns false' do subject.request_admin_mode! expect(subject.enable_admin_mode!(password: user.password)).to eq(false) end end context 'when admin mode is not requested' do it 'raises error' do expect do subject.enable_admin_mode!(password: user.password) end.to raise_error(Gitlab::Auth::CurrentUserMode::NotRequestedError) end end end describe '#disable_admin_mode!' do let(:user) { build_stubbed(:user, :admin) } it 'sets the session timestamp to nil' do subject.request_admin_mode! subject.disable_admin_mode! expect(session).to include(expected_session_entry(be_nil)) end end describe '.with_current_request_admin_mode' do context 'with a regular user' do it 'user is not available inside nor outside the yielded block' do described_class.with_current_admin(user) do expect(described_class.current_admin).to be_nil end expect(described_class.bypass_session_admin_id).to be_nil end end context 'with an admin user' do let(:user) { build_stubbed(:user, :admin) } context 'admin mode is disabled' do it 'user is not available inside nor outside the yielded block' do described_class.with_current_admin(user) do expect(described_class.current_admin).to be_nil end expect(described_class.bypass_session_admin_id).to be_nil end end context 'admin mode is enabled' do before do subject.request_admin_mode! subject.enable_admin_mode!(password: user.password) end it 'user is available only inside the yielded block' do described_class.with_current_admin(user) do expect(described_class.current_admin).to be(user) end expect(described_class.current_admin).to be_nil end end end end def expected_session_entry(value_matcher) { Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including( Gitlab::Auth::CurrentUserMode::ADMIN_MODE_START_TIME_KEY => value_matcher) } end end context 'when no session available' do around do |example| Gitlab::Session.with_session(nil) do example.run end end describe '.bypass_session!' do context 'when providing a block' do context 'with a regular user' do it 'admin mode is false' do described_class.bypass_session!(user.id) do expect(Gitlab::Session.current).to be_nil expect(subject.admin_mode?).to be(false) expect(described_class.bypass_session_admin_id).to be(user.id) end expect(described_class.bypass_session_admin_id).to be_nil end end context 'with an admin user' do let(:user) { build_stubbed(:user, :admin) } it 'admin mode is true' do described_class.bypass_session!(user.id) do expect(Gitlab::Session.current).to be_nil expect(subject.admin_mode?).to be(true) expect(described_class.bypass_session_admin_id).to be(user.id) end expect(described_class.bypass_session_admin_id).to be_nil end end end context 'when not providing a block' do context 'with a regular user' do it 'admin mode is false' do described_class.bypass_session!(user.id) expect(Gitlab::Session.current).to be_nil expect(subject.admin_mode?).to be(false) expect(described_class.bypass_session_admin_id).to be(user.id) described_class.reset_bypass_session! expect(described_class.bypass_session_admin_id).to be_nil end end context 'with an admin user' do let(:user) { build_stubbed(:user, :admin) } it 'admin mode is true' do described_class.bypass_session!(user.id) expect(Gitlab::Session.current).to be_nil expect(subject.admin_mode?).to be(true) expect(described_class.bypass_session_admin_id).to be(user.id) described_class.reset_bypass_session! expect(described_class.bypass_session_admin_id).to be_nil end end end end end end