From 6528a71ac448f759b5615a7679abd3c0ab1afcb5 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 1 Sep 2020 16:52:22 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@13-1-stable-ee --- spec/controllers/application_controller_spec.rb | 9 +- .../omniauth_callbacks_controller_spec.rb | 16 +++ .../profiles/active_sessions_controller_spec.rb | 23 ++++ .../profiles/two_factor_auths_controller_spec.rb | 17 ++- spec/controllers/projects/hooks_controller_spec.rb | 22 ++++ spec/controllers/sessions_controller_spec.rb | 32 ++--- spec/features/users/login_spec.rb | 12 +- spec/finders/user_recent_events_finder_spec.rb | 37 +++++- spec/graphql/gitlab_schema_spec.rb | 49 ++++++++ .../gitlab/auth/two_factor_auth_verifier_spec.rb | 112 +++++++++++++++++ spec/lib/gitlab/auth_spec.rb | 15 ++- spec/lib/gitlab/git_access_spec.rb | 132 ++++++++++++++++++--- spec/models/active_session_spec.rb | 53 +++++++++ spec/models/member_spec.rb | 18 ++- spec/models/user_spec.rb | 50 ++++++++ .../api/graphql/mutations/snippets/destroy_spec.rb | 25 ++++ spec/requests/api/helpers_spec.rb | 21 ++++ ...ntainer_registry_authentication_service_spec.rb | 13 ++ spec/services/members/destroy_service_spec.rb | 40 +++++++ 19 files changed, 655 insertions(+), 41 deletions(-) create mode 100644 spec/controllers/profiles/active_sessions_controller_spec.rb create mode 100644 spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb (limited to 'spec') diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 4002b7aca63..ce8eeb7a3d1 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -239,6 +239,7 @@ RSpec.describe ApplicationController do it 'does not redirect if 2FA is not required' do allow(controller).to receive(:two_factor_authentication_required?).and_return(false) + allow(controller).to receive(:current_user).and_return(create(:user)) expect(controller).not_to receive(:redirect_to) @@ -356,13 +357,17 @@ RSpec.describe ApplicationController 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) + allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier| + allow(verifier).to receive(:two_factor_grace_period).and_return(2) + end 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) + allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier| + allow(verifier).to receive(:two_factor_grace_period).and_return(3) + end expect(subject).to be_falsey end diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 0b99f28f79b..86c08e3162f 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -40,6 +40,22 @@ RSpec.describe OmniauthCallbacksController, type: :controller do end end + context 'when sign in is not valid' do + let(:provider) { :github } + let(:extern_uid) { 'my-uid' } + + it 'renders omniauth error page' do + allow_next_instance_of(Gitlab::Auth::OAuth::User) do |instance| + allow(instance).to receive(:valid_sign_in?).and_return(false) + end + + post provider + + expect(response).to render_template("errors/omniauth_error") + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end + end + context 'when the user is on the last sign in attempt' do let(:extern_uid) { 'my-uid' } diff --git a/spec/controllers/profiles/active_sessions_controller_spec.rb b/spec/controllers/profiles/active_sessions_controller_spec.rb new file mode 100644 index 00000000000..f54f69d853d --- /dev/null +++ b/spec/controllers/profiles/active_sessions_controller_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Profiles::ActiveSessionsController do + describe 'DELETE destroy' do + let_it_be(:user) { create(:user) } + + before do + sign_in(user) + end + + it 'invalidates all remember user tokens' do + ActiveSession.set(user, request) + session_id = request.session.id.public_id + user.remember_me! + + delete :destroy, params: { id: session_id } + + expect(user.reload.remember_created_at).to be_nil + end + end +end diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb index f645081219a..1fb0b18622b 100644 --- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb +++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb @@ -14,10 +14,9 @@ RSpec.describe Profiles::TwoFactorAuthsController do let(:user) { create(:user) } it 'generates otp_secret for user' do - expect(User).to receive(:generate_otp_secret).with(32).and_return('secret').once + expect(User).to receive(:generate_otp_secret).with(32).and_call_original.once get :show - get :show # Second hit shouldn't re-generate it end it 'assigns qr_code' do @@ -27,6 +26,14 @@ RSpec.describe Profiles::TwoFactorAuthsController do get :show expect(assigns[:qr_code]).to eq code end + + it 'generates a unique otp_secret every time the page is loaded' do + expect(User).to receive(:generate_otp_secret).with(32).and_call_original.twice + + 2.times do + get :show + end + end end describe 'POST create' do @@ -57,6 +64,12 @@ RSpec.describe Profiles::TwoFactorAuthsController do expect(assigns[:codes]).to match_array %w(a b c) end + it 'calls to delete other sessions' do + expect(ActiveSession).to receive(:destroy_all_but_current) + + go + end + it 'renders create' do go expect(response).to render_template(:create) diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb index 440e6b2a74c..68e91fa9c1f 100644 --- a/spec/controllers/projects/hooks_controller_spec.rb +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -46,4 +46,26 @@ RSpec.describe Projects::HooksController do expect(ProjectHook.first).to have_attributes(hook_params) end end + + describe '#test' do + let(:hook) { create(:project_hook, project: project) } + + context 'when the endpoint receives requests above the limit' do + before do + allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits) + .and_return(project_testing_hook: { threshold: 1, interval: 1.minute }) + end + + it 'prevents making test requests' do + expect_next_instance_of(TestHooks::ProjectService) do |service| + expect(service).to receive(:execute).and_return(http_status: 200) + end + + 2.times { post :test, params: { namespace_id: project.namespace, project_id: project, id: hook } } + + expect(response.body).to eq(_('This endpoint has been requested too many times. Try again later.')) + expect(response).to have_gitlab_http_status(:too_many_requests) + end + end + end end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 16a58112479..f45ea58b1f7 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -288,8 +288,8 @@ RSpec.describe SessionsController do context 'when using two-factor authentication via OTP' do let(:user) { create(:user, :two_factor) } - def authenticate_2fa(user_params) - post(:create, params: { user: user_params }, session: { otp_user_id: user.id }) + def authenticate_2fa(user_params, otp_user_id: user.id) + post(:create, params: { user: user_params }, session: { otp_user_id: otp_user_id }) end context 'remember_me field' do @@ -326,8 +326,22 @@ RSpec.describe SessionsController do end end + # See issue gitlab-org/gitlab#20302. + context 'when otp_user_id is stale' do + render_views + + it 'favors login over otp_user_id when password is present and does not authenticate the user' do + authenticate_2fa( + { login: 'random_username', password: user.password }, + otp_user_id: user.id + ) + + expect(response).to set_flash.now[:alert].to /Invalid Login or password/ + end + end + ## - # See #14900 issue + # See issue gitlab-org/gitlab-foss#14900 # context 'when authenticating with login and OTP of another user' do context 'when another user has 2FA enabled' do @@ -413,18 +427,6 @@ RSpec.describe SessionsController do end end end - - context 'when another user does not have 2FA enabled' do - let(:another_user) { create(:user) } - - it 'does not leak that 2FA is disabled for another user' do - authenticate_2fa(login: another_user.username, - otp_attempt: 'invalid') - - expect(response).to set_flash.now[:alert] - .to /Invalid two-factor code/ - end - end end end diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 7ba663d08d4..c791fa5c0a5 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -177,6 +177,14 @@ RSpec.describe 'Login' do expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated')) end + it 'does not allow sign-in if the user password is updated before entering a one-time code' do + user.update!(password: 'new_password') + + enter_code(user.current_otp) + + expect(page).to have_content('An error occurred. Please sign in again.') + end + context 'using one-time code' do it 'allows login with valid code' do expect(authentication_metrics) @@ -232,7 +240,7 @@ RSpec.describe 'Login' do expect(codes.size).to eq 10 # Ensure the generated codes get saved - user.save + user.save(touch: false) end context 'with valid code' do @@ -290,7 +298,7 @@ RSpec.describe 'Login' do code = codes.sample expect(user.invalidate_otp_backup_code!(code)).to eq true - user.save! + user.save!(touch: false) expect(user.reload.otp_backup_codes.size).to eq 9 enter_code(code) diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb index 04ba05c68e4..c32a0b06b0f 100644 --- a/spec/finders/user_recent_events_finder_spec.rb +++ b/spec/finders/user_recent_events_finder_spec.rb @@ -11,8 +11,10 @@ RSpec.describe UserRecentEventsFinder do let!(:private_event) { create(:event, project: private_project, author: project_owner) } let!(:internal_event) { create(:event, project: internal_project, author: project_owner) } let!(:public_event) { create(:event, project: public_project, author: project_owner) } + let(:limit) { nil } + let(:params) { { limit: limit } } - subject(:finder) { described_class.new(current_user, project_owner) } + subject(:finder) { described_class.new(current_user, project_owner, params) } describe '#execute' do context 'when profile is public' do @@ -36,5 +38,38 @@ RSpec.describe UserRecentEventsFinder do expect(finder.execute).to be_empty end + + context 'limits' do + before do + stub_const("#{described_class}::DEFAULT_LIMIT", 1) + stub_const("#{described_class}::MAX_LIMIT", 3) + end + + context 'when limit is not set' do + it 'returns events limited to DEFAULT_LIMIT' do + expect(finder.execute.size).to eq(described_class::DEFAULT_LIMIT) + end + end + + context 'when limit is set' do + let(:limit) { 2 } + + it 'returns events limited to specified limit' do + expect(finder.execute.size).to eq(limit) + end + end + + context 'when limit is set to a number that exceeds maximum limit' do + let(:limit) { 4 } + + before do + create(:event, project: public_project, author: project_owner) + end + + it 'returns events limited to MAX_LIMIT' do + expect(finder.execute.size).to eq(described_class::MAX_LIMIT) + end + end + end end end diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index 5d6aa863994..e52f60a2c02 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -212,6 +212,55 @@ RSpec.describe GitlabSchema do end end + describe '.parse_gid' do + let_it_be(:global_id) { 'gid://gitlab/TestOne/2147483647' } + + before do + test_base = Class.new + test_one = Class.new(test_base) + test_two = Class.new(test_base) + + stub_const('TestBase', test_base) + stub_const('TestOne', test_one) + stub_const('TestTwo', test_two) + end + + it 'parses the gid' do + gid = described_class.parse_gid(global_id) + + expect(gid.model_id).to eq '2147483647' + expect(gid.model_class).to eq TestOne + end + + context 'when gid is malformed' do + let_it_be(:global_id) { 'malformed://gitlab/TestOne/2147483647' } + + it 'raises an error' do + expect { described_class.parse_gid(global_id) } + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id.") + end + end + + context 'when using expected_type' do + it 'accepts a single type' do + gid = described_class.parse_gid(global_id, expected_type: TestOne) + + expect(gid.model_class).to eq TestOne + end + + it 'accepts an ancestor type' do + gid = described_class.parse_gid(global_id, expected_type: TestBase) + + expect(gid.model_class).to eq TestOne + end + + it 'rejects an unknown type' do + expect { described_class.parse_gid(global_id, expected_type: TestTwo) } + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid id for TestTwo.") + end + end + end + def field_instrumenters described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins] end diff --git a/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb b/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb new file mode 100644 index 00000000000..f906870195a --- /dev/null +++ b/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Auth::TwoFactorAuthVerifier do + let(:user) { create(:user) } + + subject { described_class.new(user) } + + describe '#two_factor_authentication_required?' do + describe 'when it is required on application level' do + it 'returns true' do + stub_application_setting require_two_factor_authentication: true + + expect(subject.two_factor_authentication_required?).to be_truthy + end + end + + describe 'when it is required on group level' do + it 'returns true' do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true) + + expect(subject.two_factor_authentication_required?).to be_truthy + end + end + + describe 'when it is not required' do + it 'returns false when not required on group level' do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(false) + + expect(subject.two_factor_authentication_required?).to be_falsey + end + end + end + + describe '#current_user_needs_to_setup_two_factor?' do + it 'returns false when current_user is nil' do + expect(described_class.new(nil).current_user_needs_to_setup_two_factor?).to be_falsey + end + + it 'returns false when current_user does not have temp email' do + allow(user).to receive(:two_factor_enabled?).and_return(false) + allow(user).to receive(:temp_oauth_email?).and_return(true) + + expect(subject.current_user_needs_to_setup_two_factor?).to be_falsey + end + + it 'returns false when current_user has 2fa disabled' do + allow(user).to receive(:temp_oauth_email?).and_return(false) + allow(user).to receive(:two_factor_enabled?).and_return(true) + + expect(subject.current_user_needs_to_setup_two_factor?).to be_falsey + end + + it 'returns true when user requires 2fa authentication' do + allow(user).to receive(:two_factor_enabled?).and_return(false) + allow(user).to receive(:temp_oauth_email?).and_return(false) + + expect(subject.current_user_needs_to_setup_two_factor?).to be_truthy + end + end + + describe '#two_factor_grace_period' do + it 'returns grace period from settings if there is no period from groups' do + stub_application_setting two_factor_grace_period: 2 + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(false) + + expect(subject.two_factor_grace_period).to eq(2) + end + + it 'returns grace period from groups if there is no period from settings' do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true) + allow(user).to receive(:two_factor_grace_period).and_return(3) + + expect(subject.two_factor_grace_period).to eq(3) + end + + it 'returns minimal grace period if there is grace period from settings and from group' do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true) + allow(user).to receive(:two_factor_grace_period).and_return(3) + stub_application_setting two_factor_grace_period: 2 + + expect(subject.two_factor_grace_period).to eq(2) + end + end + + describe '#two_factor_grace_period_expired?' do + before do + allow(user).to receive(:otp_grace_period_started_at).and_return(4.hours.ago) + end + + it 'returns true if the grace period has expired' do + allow(subject).to receive(:two_factor_grace_period).and_return(2) + + expect(subject.two_factor_grace_period_expired?).to be_truthy + end + + it 'returns false if the grace period has not expired' do + allow(subject).to receive(:two_factor_grace_period).and_return(6) + + expect(subject.two_factor_grace_period_expired?).to be_falsey + end + + context 'when otp_grace_period_started_at is nil' do + it 'returns false' do + allow(user).to receive(:otp_grace_period_started_at).and_return(nil) + + expect(subject.two_factor_grace_period_expired?).to be_falsey + end + end + end +end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 21767af9abf..19708333731 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -431,7 +431,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do end end - shared_examples 'deploy token with disabled registry' do + shared_examples 'deploy token with disabled feature' do context 'when registry disabled' do before do stub_container_registry_config(enabled: false) @@ -442,6 +442,15 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do .to eq(auth_failure) end end + + context 'when repository is disabled' do + let(:project) { create(:project, :repository_disabled) } + + it 'fails when login and token are valid' do + expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip')) + .to eq(auth_failure) + end + end end context 'when deploy token and user have the same username' do @@ -594,7 +603,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it_behaves_like 'registry token scope' end - it_behaves_like 'deploy token with disabled registry' + it_behaves_like 'deploy token with disabled feature' end context 'when the deploy token has write_registry as a scope' do @@ -616,7 +625,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it_behaves_like 'registry token scope' end - it_behaves_like 'deploy token with disabled registry' + it_behaves_like 'deploy token with disabled feature' end end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 7c09fc5cc79..0a6db18ad76 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -487,31 +487,135 @@ describe Gitlab::GitAccess do let(:actor) { key } context 'pull code' do - context 'when project is authorized' do - before do - key.projects << project + context 'when project is public' do + let(:project) { create(:project, :public, :repository, *options) } + + context 'when deploy key exists in the project' do + before do + key.projects << project + end + + context 'when the repository is public' do + let(:options) { %i[repository_enabled] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end end - it { expect { pull_access_check }.not_to raise_error } + context 'when deploy key does not exist in the project' do + context 'when the repository is public' do + let(:options) { %i[repository_enabled] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end + end end - context 'when unauthorized' do - context 'from public project' do - let(:project) { create(:project, :public, :repository) } + context 'when project is internal' do + let(:project) { create(:project, :internal, :repository, *options) } - it { expect { pull_access_check }.not_to raise_error } + context 'when deploy key exists in the project' do + before do + key.projects << project + end + + context 'when the repository is public' do + let(:options) { %i[repository_enabled] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end end - context 'from internal project' do - let(:project) { create(:project, :internal, :repository) } + context 'when deploy key does not exist in the project' do + context 'when the repository is public' do + let(:options) { %i[repository_enabled] } - it { expect { pull_access_check }.to raise_not_found } + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end end + end - context 'from private project' do - let(:project) { create(:project, :private, :repository) } + context 'when project is private' do + let(:project) { create(:project, :private, :repository, *options) } - it { expect { pull_access_check }.to raise_not_found } + context 'when deploy key exists in the project' do + before do + key.projects << project + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end + end + + context 'when deploy key does not exist in the project' do + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end end end end diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index 24b47be3c69..de39c8c7c5c 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -296,6 +296,59 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do end end + describe '.destroy_all_but_current' do + it 'gracefully handles a nil session ID' do + expect(described_class).not_to receive(:destroy_sessions) + + ActiveSession.destroy_all_but_current(user, nil) + end + + context 'with user sessions' do + let(:current_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' } + + before do + Gitlab::Redis::SharedState.with do |redis| + redis.set(described_class.key_name(user.id, current_session_id), + Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new(current_session_id)))) + redis.set(described_class.key_name(user.id, '59822c7d9fcdfa03725eff41782ad97d'), + Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('59822c7d9fcdfa03725eff41782ad97d')))) + redis.set(described_class.key_name(9999, '5c8611e4f9c69645ad1a1492f4131358'), + Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('5c8611e4f9c69645ad1a1492f4131358')))) + redis.sadd(described_class.lookup_key_name(user.id), '59822c7d9fcdfa03725eff41782ad97d') + redis.sadd(described_class.lookup_key_name(user.id), current_session_id) + redis.sadd(described_class.lookup_key_name(9999), '5c8611e4f9c69645ad1a1492f4131358') + end + end + + it 'removes the entry associated with the all user sessions but current' do + expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(2).to(1) + + expect(ActiveSession.session_ids_for_user(9999).size).to eq(1) + end + + it 'removes the lookup entry of deleted sessions' do + ActiveSession.destroy_all_but_current(user, request.session) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.smembers(described_class.lookup_key_name(user.id))).to eq [current_session_id] + end + end + + it 'does not remove impersonated sessions' do + impersonated_session_id = '6919a6f1bb119dd7396fadc38fd18eee' + Gitlab::Redis::SharedState.with do |redis| + redis.set(described_class.key_name(user.id, impersonated_session_id), + Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new(impersonated_session_id), is_impersonated: true))) + redis.sadd(described_class.lookup_key_name(user.id), impersonated_session_id) + end + + expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(3).to(2) + + expect(ActiveSession.session_ids_for_user(9999).size).to eq(1) + end + end + end + describe '.cleanup' do before do stub_const("ActiveSession::ALLOWED_NUMBER_OF_ACTIVE_SESSIONS", 5) diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 7c40bb24b56..2879b325663 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -91,9 +91,10 @@ describe Member do end describe 'Scopes & finders' do + let(:project) { create(:project, :public) } + let(:group) { create(:group) } + before do - project = create(:project, :public) - group = create(:group) @owner_user = create(:user).tap { |u| group.add_owner(u) } @owner = group.members.find_by(user_id: @owner_user.id) @@ -172,6 +173,19 @@ describe Member do it { expect(described_class.non_request).to include @accepted_request_member } end + describe '.not_accepted_invitations_by_user' do + let(:invited_by_user) { create(:project_member, :invited, project: project, created_by: @owner_user) } + + before do + create(:project_member, :invited, invite_email: 'test@test.com', project: project, created_by: @owner_user, invite_accepted_at: Time.zone.now) + create(:project_member, :invited, invite_email: 'test2@test.com', project: project, created_by: @maintainer_user) + end + + subject { described_class.not_accepted_invitations_by_user(@owner_user) } + + it { is_expected.to contain_exactly(invited_by_user) } + end + describe '.search_invite_email' do it 'returns only members the matching e-mail' do create(:group_member, :invited) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index dd4b174a38f..40f3bd20382 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -893,6 +893,20 @@ describe User do expect(described_class.without_ghosts).to match_array([user1, user2]) end end + + describe '.by_id_and_login' do + let_it_be(:user) { create(:user) } + + it 'finds a user regardless of case' do + expect(described_class.by_id_and_login(user.id, user.username.upcase)) + .to contain_exactly(user) + end + + it 'finds a user when login is an email address regardless of case' do + expect(described_class.by_id_and_login(user.id, user.email.upcase)) + .to contain_exactly(user) + end + end end describe "Respond to" do @@ -3578,6 +3592,42 @@ describe User do end end + describe '#source_groups_of_two_factor_authentication_requirement' do + let_it_be(:group_not_requiring_2FA) { create :group } + let(:user) { create :user } + + before do + group.add_user(user, GroupMember::OWNER) + group_not_requiring_2FA.add_user(user, GroupMember::OWNER) + end + + context 'when user is direct member of group requiring 2FA' do + let_it_be(:group) { create :group, require_two_factor_authentication: true } + + it 'returns group requiring 2FA' do + expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group) + end + end + + context 'when user is member of group which parent requires 2FA' do + let_it_be(:parent_group) { create :group, require_two_factor_authentication: true } + let_it_be(:group) { create :group, parent: parent_group } + + it 'returns group requiring 2FA' do + expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group) + end + end + + context 'when user is member of group which child requires 2FA' do + let_it_be(:group) { create :group } + let_it_be(:child_group) { create :group, require_two_factor_authentication: true, parent: group } + + it 'returns group requiring 2FA' do + expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group) + end + end + end + describe '.active' do before do described_class.ghost diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb index cb9aeea74b2..0c0e52fde53 100644 --- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb @@ -46,6 +46,31 @@ describe 'Destroying a Snippet' do expect(mutation_response).to have_key('snippet') expect(mutation_response['snippet']).to be_nil end + + context 'when a bad gid is given' do + let!(:project) { create(:project, :private) } + let!(:snippet) { create(:project_snippet, :private, project: project, author: create(:user)) } + let!(:snippet_gid) { project.to_gid.to_s } + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_errors) + .to include(a_hash_including('message' => "#{snippet_gid} is not a valid id for Snippet.")) + end + + it 'does not destroy the Snippet' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Snippet.count } + end + + it 'does not destroy the Project' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Project.count } + end + end end end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index d65c89f48ea..872d7252d0f 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -84,6 +84,27 @@ describe API::Helpers do end it { is_expected.to eq(user) } + + context 'when user should have 2fa enabled' do + before do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true) + allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier| + allow(verifier).to receive(:two_factor_grace_period_expired?).and_return(true) + end + end + + context 'when 2fa is not enabled' do + it { is_expected.to be_nil } + end + + context 'when 2fa is enabled' do + before do + allow(user).to receive(:two_factor_enabled?).and_return(true) + end + + it { is_expected.to eq(user) } + end + end end context "PUT request" do diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 70eb35f0826..e0ea661d15d 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -654,6 +654,19 @@ describe Auth::ContainerRegistryAuthenticationService do it_behaves_like 'not a container repository factory' end end + + context 'for project that disables repository' do + let(:project) { create(:project, :public, :repository_disabled) } + + context 'disallow when pulling' do + let(:current_params) do + { scopes: ["repository:#{project.full_path}:pull"] } + end + + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' + end + end end context 'registry catalog browsing authorized as admin' do diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 73ac0bd7716..99e60318c42 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -249,6 +249,10 @@ describe Members::DestroyService do before do create(:group_member, :developer, group: subsubgroup, user: member_user) + create(:project_member, :invited, project: group_project, created_by: member_user) + create(:group_member, :invited, group: group, created_by: member_user) + create(:project_member, :invited, project: subsubproject, created_by: member_user) + create(:group_member, :invited, group: subgroup, created_by: member_user) subsubproject.add_developer(member_user) control_project.add_maintainer(user) @@ -282,5 +286,41 @@ describe Members::DestroyService do it 'does not remove the user from the control project' do expect(control_project.members.map(&:user)).to include(user) end + + it 'removes group members invited by deleted user' do + expect(group.members.not_accepted_invitations_by_user(member_user)).to be_empty + end + + it 'removes project members invited by deleted user' do + expect(group_project.members.not_accepted_invitations_by_user(member_user)).to be_empty + end + + it 'removes subgroup members invited by deleted user' do + expect(subgroup.members.not_accepted_invitations_by_user(member_user)).to be_empty + end + + it 'removes subproject members invited by deleted user' do + expect(subsubproject.members.not_accepted_invitations_by_user(member_user)).to be_empty + end + end + + context 'deletion of invitations created by deleted project member' do + let(:user) { project.owner } + let(:member_user) { create(:user) } + let(:opts) { {} } + + let(:project) { create(:project) } + + before do + create(:project_member, :invited, project: project, created_by: member_user) + + project_member = create(:project_member, :maintainer, user: member_user, project: project) + + described_class.new(user).execute(project_member, opts) + end + + it 'removes project members invited by deleted user' do + expect(project.members.not_accepted_invitations_by_user(member_user)).to be_empty + end end end -- cgit v1.2.1