debian-mirror-gitlab/spec/lib/gitlab/git_access_spec.rb

1107 lines
35 KiB
Ruby
Raw Normal View History

2019-12-26 22:10:19 +05:30
# frozen_string_literal: true
2015-04-26 12:48:37 +05:30
require 'spec_helper'
2020-07-28 23:09:34 +05:30
RSpec.describe Gitlab::GitAccess do
2018-10-15 14:42:47 +05:30
include TermsHelper
2018-12-05 23:21:45 +05:30
include GitHelpers
2018-10-15 14:42:47 +05:30
let(:user) { create(:user) }
2017-09-10 17:25:29 +05:30
2015-04-26 12:48:37 +05:30
let(:actor) { user }
2017-09-10 17:25:29 +05:30
let(:project) { create(:project, :repository) }
2020-06-23 00:09:42 +05:30
let(:project_path) { project&.path }
2018-03-17 18:26:18 +05:30
let(:namespace_path) { project&.namespace&.path }
2017-09-10 17:25:29 +05:30
let(:protocol) { 'ssh' }
let(:authentication_abilities) { %i[read_project download_code push_code] }
let(:redirected_path) { nil }
2018-05-09 12:01:36 +05:30
let(:auth_result_type) { nil }
2019-02-15 15:39:39 +05:30
let(:changes) { Gitlab::GitAccess::ANY }
2018-03-17 18:26:18 +05:30
let(:push_access_check) { access.check('git-receive-pack', changes) }
let(:pull_access_check) { access.check('git-upload-pack', changes) }
2015-04-26 12:48:37 +05:30
2016-08-24 12:49:21 +05:30
describe '#check with single protocols allowed' do
def disable_protocol(protocol)
2017-09-10 17:25:29 +05:30
allow(Gitlab::ProtocolAccess).to receive(:allowed?).with(protocol).and_return(false)
2015-04-26 12:48:37 +05:30
end
2016-08-24 12:49:21 +05:30
context 'ssh disabled' do
2015-04-26 12:48:37 +05:30
before do
2016-08-24 12:49:21 +05:30
disable_protocol('ssh')
2015-04-26 12:48:37 +05:30
end
2017-09-10 17:25:29 +05:30
it 'blocks ssh git push and pull' do
aggregate_failures do
2020-04-08 14:13:33 +05:30
expect { push_access_check }.to raise_forbidden('Git access over SSH is not allowed')
expect { pull_access_check }.to raise_forbidden('Git access over SSH is not allowed')
2017-09-10 17:25:29 +05:30
end
2015-04-26 12:48:37 +05:30
end
end
2016-08-24 12:49:21 +05:30
context 'http disabled' do
2017-09-10 17:25:29 +05:30
let(:protocol) { 'http' }
2015-04-26 12:48:37 +05:30
before do
2016-08-24 12:49:21 +05:30
disable_protocol('http')
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2015-04-26 12:48:37 +05:30
end
2017-09-10 17:25:29 +05:30
it 'blocks http push and pull' do
aggregate_failures do
2020-04-08 14:13:33 +05:30
expect { push_access_check }.to raise_forbidden('Git access over HTTP is not allowed')
expect { pull_access_check }.to raise_forbidden('Git access over HTTP is not allowed')
2017-09-10 17:25:29 +05:30
end
2015-04-26 12:48:37 +05:30
end
2018-05-09 12:01:36 +05:30
context 'when request is made from CI' do
let(:auth_result_type) { :build }
it "doesn't block http pull" do
aggregate_failures do
2020-04-08 14:13:33 +05:30
expect { pull_access_check }.not_to raise_forbidden('Git access over HTTP is not allowed')
2018-05-09 12:01:36 +05:30
end
end
context 'when legacy CI credentials are used' do
let(:auth_result_type) { :ci }
it "doesn't block http pull" do
aggregate_failures do
2020-04-08 14:13:33 +05:30
expect { pull_access_check }.not_to raise_forbidden('Git access over HTTP is not allowed')
2018-05-09 12:01:36 +05:30
end
end
end
end
2017-09-10 17:25:29 +05:30
end
end
2020-03-13 15:44:24 +05:30
describe '#check_namespace!' do
context 'when namespace exists' do
before do
project.add_maintainer(user)
end
it 'allows push and pull access' do
aggregate_failures do
expect { push_access_check }.not_to raise_error
expect { pull_access_check }.not_to raise_error
end
end
end
2020-06-23 00:09:42 +05:30
context 'when namespace and project are nil' do
let(:project) { nil }
2020-03-13 15:44:24 +05:30
let(:namespace_path) { nil }
it 'does not allow push and pull access' do
aggregate_failures do
2020-06-23 00:09:42 +05:30
expect { push_access_check }.to raise_namespace_not_found
expect { pull_access_check }.to raise_namespace_not_found
2020-03-13 15:44:24 +05:30
end
end
end
end
2017-09-10 17:25:29 +05:30
describe '#check_project_accessibility!' do
context 'when the project exists' do
context 'when actor exists' do
context 'when actor is a DeployKey' do
2018-03-17 18:26:18 +05:30
let(:deploy_key) { create(:deploy_key, user: user) }
2017-09-10 17:25:29 +05:30
let(:actor) { deploy_key }
context 'when the DeployKey has access to the project' do
before do
2018-03-17 18:26:18 +05:30
deploy_key.deploy_keys_projects.create(project: project, can_push: true)
2017-09-10 17:25:29 +05:30
end
2015-04-26 12:48:37 +05:30
2017-09-10 17:25:29 +05:30
it 'allows push and pull access' do
aggregate_failures do
expect { push_access_check }.not_to raise_error
expect { pull_access_check }.not_to raise_error
end
end
end
context 'when the Deploykey does not have access to the project' do
it 'blocks push and pull with "not found"' do
aggregate_failures do
expect { push_access_check }.to raise_not_found
expect { pull_access_check }.to raise_not_found
end
end
end
end
context 'when actor is a User' do
context 'when the User can read the project' do
before do
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2017-09-10 17:25:29 +05:30
end
it 'allows push and pull access' do
aggregate_failures do
expect { pull_access_check }.not_to raise_error
expect { push_access_check }.not_to raise_error
end
end
end
context 'when the User cannot read the project' do
it 'blocks push and pull with "not found"' do
aggregate_failures do
expect { push_access_check }.to raise_not_found
expect { pull_access_check }.to raise_not_found
end
end
end
end
# For backwards compatibility
context 'when actor is :ci' do
let(:actor) { :ci }
let(:authentication_abilities) { build_authentication_abilities }
it 'allows pull access' do
expect { pull_access_check }.not_to raise_error
end
it 'does not block pushes with "not found"' do
2020-04-08 14:13:33 +05:30
expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload])
2017-09-10 17:25:29 +05:30
end
end
2018-05-09 12:01:36 +05:30
context 'when actor is DeployToken' do
let(:actor) { create(:deploy_token, projects: [project]) }
context 'when DeployToken is active and belongs to project' do
it 'allows pull access' do
expect { pull_access_check }.not_to raise_error
end
it 'blocks the push' do
2020-04-08 14:13:33 +05:30
expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:upload])
2018-05-09 12:01:36 +05:30
end
end
context 'when DeployToken does not belong to project' do
let(:another_project) { create(:project) }
let(:actor) { create(:deploy_token, projects: [another_project]) }
it 'blocks pull access' do
expect { pull_access_check }.to raise_not_found
end
it 'blocks the push' do
expect { push_access_check }.to raise_not_found
end
end
end
2017-09-10 17:25:29 +05:30
end
context 'when actor is nil' do
let(:actor) { nil }
context 'when guests can read the project' do
let(:project) { create(:project, :repository, :public) }
it 'allows pull access' do
expect { pull_access_check }.not_to raise_error
end
it 'does not block pushes with "not found"' do
2020-04-08 14:13:33 +05:30
expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:upload])
2017-09-10 17:25:29 +05:30
end
end
context 'when guests cannot read the project' do
it 'blocks pulls with "not found"' do
expect { pull_access_check }.to raise_not_found
end
it 'blocks pushes with "not found"' do
expect { push_access_check }.to raise_not_found
end
end
2015-04-26 12:48:37 +05:30
end
end
2017-09-10 17:25:29 +05:30
context 'when the project is nil' do
let(:project) { nil }
2018-03-17 18:26:18 +05:30
let(:project_path) { "new-project" }
2020-06-23 00:09:42 +05:30
let(:namespace_path) { user.namespace.path }
2017-09-10 17:25:29 +05:30
it 'blocks push and pull with "not found"' do
aggregate_failures do
expect { pull_access_check }.to raise_not_found
expect { push_access_check }.to raise_not_found
end
end
2018-03-17 18:26:18 +05:30
end
end
shared_examples '#check with a key that is not valid' do
before do
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2018-03-17 18:26:18 +05:30
end
context 'key is too small' do
before do
stub_application_setting(rsa_key_restriction: 4096)
end
it 'does not allow keys which are too small', :aggregate_failures do
expect(actor).not_to be_valid
2020-04-08 14:13:33 +05:30
expect { pull_access_check }.to raise_forbidden('Your SSH key must be at least 4096 bits.')
expect { push_access_check }.to raise_forbidden('Your SSH key must be at least 4096 bits.')
2018-03-17 18:26:18 +05:30
end
end
context 'key type is not allowed' do
before do
stub_application_setting(rsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE)
end
it 'does not allow keys which are too small', :aggregate_failures do
expect(actor).not_to be_valid
2020-04-08 14:13:33 +05:30
expect { pull_access_check }.to raise_forbidden(/Your SSH key type is forbidden/)
expect { push_access_check }.to raise_forbidden(/Your SSH key type is forbidden/)
2018-03-17 18:26:18 +05:30
end
end
end
it_behaves_like '#check with a key that is not valid' do
let(:actor) { build(:rsa_key_2048, user: user) }
end
it_behaves_like '#check with a key that is not valid' do
let(:actor) { build(:rsa_deploy_key_2048, user: user) }
end
shared_examples 'check_project_moved' do
2018-05-09 12:01:36 +05:30
it 'enqueues a redirected message for pushing' do
2018-03-17 18:26:18 +05:30
push_access_check
expect(Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id)).not_to be_nil
2017-09-10 17:25:29 +05:30
end
2018-05-09 12:01:36 +05:30
it 'allows push and pull access' do
aggregate_failures do
expect { push_access_check }.not_to raise_error
expect { pull_access_check }.not_to raise_error
end
end
2017-09-10 17:25:29 +05:30
end
2016-08-24 12:49:21 +05:30
2018-05-09 12:01:36 +05:30
describe '#add_project_moved_message!', :clean_gitlab_redis_shared_state do
2017-09-10 17:25:29 +05:30
before do
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2017-09-10 17:25:29 +05:30
end
2015-04-26 12:48:37 +05:30
2017-09-10 17:25:29 +05:30
context 'when a redirect was not followed to find the project' do
it 'allows push and pull access' do
aggregate_failures do
expect { push_access_check }.not_to raise_error
expect { pull_access_check }.not_to raise_error
end
2015-04-26 12:48:37 +05:30
end
end
2018-05-09 12:01:36 +05:30
context 'with a redirect and ssh protocol' do
2018-03-17 18:26:18 +05:30
let(:redirected_path) { 'some/other-path' }
it_behaves_like 'check_project_moved'
end
2018-05-09 12:01:36 +05:30
context 'with a redirect and http protocol' do
2018-03-17 18:26:18 +05:30
let(:redirected_path) { 'some/other-path' }
let(:protocol) { 'http' }
it_behaves_like 'check_project_moved'
end
end
describe '#check_authentication_abilities!' do
before do
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2018-03-17 18:26:18 +05:30
end
context 'when download' do
let(:authentication_abilities) { [] }
it 'raises unauthorized with download error' do
2020-04-08 14:13:33 +05:30
expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_download])
2018-03-17 18:26:18 +05:30
end
context 'when authentication abilities include download code' do
let(:authentication_abilities) { [:download_code] }
it 'does not raise any errors' do
expect { pull_access_check }.not_to raise_error
end
end
context 'when authentication abilities include build download code' do
let(:authentication_abilities) { [:build_download_code] }
it 'does not raise any errors' do
expect { pull_access_check }.not_to raise_error
end
end
end
context 'when upload' do
let(:authentication_abilities) { [] }
it 'raises unauthorized with push error' do
2020-04-08 14:13:33 +05:30
expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload])
2018-03-17 18:26:18 +05:30
end
context 'when authentication abilities include push code' do
let(:authentication_abilities) { [:push_code] }
it 'does not raise any errors' do
expect { push_access_check }.not_to raise_error
2017-09-10 17:25:29 +05:30
end
2015-04-26 12:48:37 +05:30
end
end
2017-09-10 17:25:29 +05:30
end
2015-04-26 12:48:37 +05:30
2017-09-10 17:25:29 +05:30
describe '#check_command_disabled!' do
before do
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2017-09-10 17:25:29 +05:30
end
context 'over http' do
let(:protocol) { 'http' }
context 'when the git-upload-pack command is disabled in config' do
before do
allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
end
context 'when calling git-upload-pack' do
2020-04-08 14:13:33 +05:30
it { expect { pull_access_check }.to raise_forbidden('Pulling over HTTP is not allowed.') }
2017-09-10 17:25:29 +05:30
end
context 'when calling git-receive-pack' do
it { expect { push_access_check }.not_to raise_error }
end
2015-04-26 12:48:37 +05:30
end
2017-09-10 17:25:29 +05:30
context 'when the git-receive-pack command is disabled in config' do
before do
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
end
context 'when calling git-receive-pack' do
2020-04-08 14:13:33 +05:30
it { expect { push_access_check }.to raise_forbidden('Pushing over HTTP is not allowed.') }
2017-09-10 17:25:29 +05:30
end
context 'when calling git-upload-pack' do
it { expect { pull_access_check }.not_to raise_error }
end
2015-04-26 12:48:37 +05:30
end
end
2017-09-10 17:25:29 +05:30
end
2018-03-17 18:26:18 +05:30
describe '#check_db_accessibility!' do
context 'when in a read-only GitLab instance' do
before do
create(:protected_branch, name: 'feature', project: project)
allow(Gitlab::Database).to receive(:read_only?) { true }
end
2020-04-08 14:13:33 +05:30
it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:cannot_push_to_read_only]) }
2018-03-17 18:26:18 +05:30
end
end
2017-09-10 17:25:29 +05:30
describe '#check_download_access!' do
2018-11-18 11:00:15 +05:30
it 'allows maintainers to pull' do
project.add_maintainer(user)
2017-09-10 17:25:29 +05:30
expect { pull_access_check }.not_to raise_error
end
it 'disallows guests to pull' do
project.add_guest(user)
2020-04-08 14:13:33 +05:30
expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:download])
2017-09-10 17:25:29 +05:30
end
it 'disallows blocked users to pull' do
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2017-09-10 17:25:29 +05:30
user.block
2020-04-08 14:13:33 +05:30
expect { pull_access_check }.to raise_forbidden('Your account has been blocked.')
2017-09-10 17:25:29 +05:30
end
2015-04-26 12:48:37 +05:30
2019-12-21 20:55:43 +05:30
it 'disallows deactivated users to pull' do
project.add_maintainer(user)
user.deactivate!
2020-04-08 14:13:33 +05:30
expect { pull_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
2019-12-21 20:55:43 +05:30
end
2018-03-17 18:26:18 +05:30
context 'when the project repository does not exist' do
it 'returns not found' do
project.add_guest(user)
repo = project.repository
2018-11-08 19:23:39 +05:30
Gitlab::GitalyClient::StorageSettings.allow_disk_access { FileUtils.rm_rf(repo.path) }
2018-03-17 18:26:18 +05:30
# Sanity check for rm_rf
expect(repo.exists?).to eq(false)
expect { pull_access_check }.to raise_error(Gitlab::GitAccess::NotFoundError, 'A repository for this project does not exist yet.')
end
end
2017-08-17 22:00:37 +05:30
describe 'without access to project' do
2015-04-26 12:48:37 +05:30
context 'pull code' do
2017-09-10 17:25:29 +05:30
it { expect { pull_access_check }.to raise_not_found }
2015-04-26 12:48:37 +05:30
end
2016-11-24 13:41:30 +05:30
context 'when project is public' do
2017-08-17 22:00:37 +05:30
let(:public_project) { create(:project, :public, :repository) }
2018-03-17 18:26:18 +05:30
let(:project_path) { public_project.path }
let(:namespace_path) { public_project.namespace.path }
2020-04-08 14:13:33 +05:30
let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: [:download_code], repository_path: project_path, namespace_path: namespace_path) }
2016-11-24 13:41:30 +05:30
context 'when repository is enabled' do
it 'give access to download code' do
2017-09-10 17:25:29 +05:30
expect { pull_access_check }.not_to raise_error
2016-11-24 13:41:30 +05:30
end
end
context 'when repository is disabled' do
it 'does not give access to download code' do
public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
2020-04-08 14:13:33 +05:30
expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:download])
2016-11-24 13:41:30 +05:30
end
end
end
2015-04-26 12:48:37 +05:30
end
describe 'deploy key permissions' do
2017-08-17 22:00:37 +05:30
let(:key) { create(:deploy_key, user: user) }
2015-04-26 12:48:37 +05:30
let(:actor) { key }
context 'pull code' do
2016-08-24 12:49:21 +05:30
context 'when project is authorized' do
2017-09-10 17:25:29 +05:30
before do
key.projects << project
end
2015-04-26 12:48:37 +05:30
2017-09-10 17:25:29 +05:30
it { expect { pull_access_check }.not_to raise_error }
2016-08-24 12:49:21 +05:30
end
context 'when unauthorized' do
context 'from public project' do
2017-08-17 22:00:37 +05:30
let(:project) { create(:project, :public, :repository) }
2016-08-24 12:49:21 +05:30
2017-09-10 17:25:29 +05:30
it { expect { pull_access_check }.not_to raise_error }
2016-08-24 12:49:21 +05:30
end
context 'from internal project' do
2017-08-17 22:00:37 +05:30
let(:project) { create(:project, :internal, :repository) }
2016-08-24 12:49:21 +05:30
2017-09-10 17:25:29 +05:30
it { expect { pull_access_check }.to raise_not_found }
2016-08-24 12:49:21 +05:30
end
context 'from private project' do
2017-08-17 22:00:37 +05:30
let(:project) { create(:project, :private, :repository) }
2016-08-24 12:49:21 +05:30
2017-09-10 17:25:29 +05:30
it { expect { pull_access_check }.to raise_not_found }
2016-08-24 12:49:21 +05:30
end
end
2015-04-26 12:48:37 +05:30
end
end
2016-09-29 09:46:39 +05:30
2018-05-09 12:01:36 +05:30
describe 'deploy token permissions' do
let(:deploy_token) { create(:deploy_token) }
let(:actor) { deploy_token }
context 'pull code' do
context 'when project is authorized' do
before do
deploy_token.projects << project
end
it { expect { pull_access_check }.not_to raise_error }
end
context 'when unauthorized' do
context 'from public project' do
let(:project) { create(:project, :public, :repository) }
it { expect { pull_access_check }.not_to raise_error }
end
context 'from internal project' do
let(:project) { create(:project, :internal, :repository) }
it { expect { pull_access_check }.to raise_not_found }
end
context 'from private project' do
let(:project) { create(:project, :private, :repository) }
it { expect { pull_access_check }.to raise_not_found }
end
end
end
end
2016-09-29 09:46:39 +05:30
describe 'build authentication_abilities permissions' do
let(:authentication_abilities) { build_authentication_abilities }
2016-11-24 13:41:30 +05:30
describe 'owner' do
2017-08-17 22:00:37 +05:30
let(:project) { create(:project, :repository, namespace: user.namespace) }
2016-11-24 13:41:30 +05:30
context 'pull code' do
2017-09-10 17:25:29 +05:30
it { expect { pull_access_check }.not_to raise_error }
2016-11-24 13:41:30 +05:30
end
end
2016-09-29 09:46:39 +05:30
describe 'reporter user' do
2017-09-10 17:25:29 +05:30
before do
2018-03-17 18:26:18 +05:30
project.add_reporter(user)
2017-09-10 17:25:29 +05:30
end
2016-09-29 09:46:39 +05:30
context 'pull code' do
2017-09-10 17:25:29 +05:30
it { expect { pull_access_check }.not_to raise_error }
2016-09-29 09:46:39 +05:30
end
end
describe 'admin user' do
let(:user) { create(:admin) }
context 'when member of the project' do
2017-09-10 17:25:29 +05:30
before do
2018-03-17 18:26:18 +05:30
project.add_reporter(user)
2017-09-10 17:25:29 +05:30
end
2016-09-29 09:46:39 +05:30
context 'pull code' do
2017-09-10 17:25:29 +05:30
it { expect { pull_access_check }.not_to raise_error }
2016-09-29 09:46:39 +05:30
end
end
context 'when is not member of the project' do
context 'pull code' do
2020-04-08 14:13:33 +05:30
it { expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:download]) }
2016-09-29 09:46:39 +05:30
end
end
end
2017-09-10 17:25:29 +05:30
describe 'generic CI (build without a user)' do
let(:actor) { :ci }
context 'pull code' do
it { expect { pull_access_check }.not_to raise_error }
end
end
2016-09-29 09:46:39 +05:30
end
2015-04-26 12:48:37 +05:30
end
2018-03-17 18:26:18 +05:30
describe 'check LFS integrity' do
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'] }
before do
project.add_developer(user)
end
2019-02-15 15:39:39 +05:30
context 'when LFS is not enabled' do
it 'does not run LFSIntegrity check' do
expect(Gitlab::Checks::LfsIntegrity).not_to receive(:new)
2018-03-17 18:26:18 +05:30
2019-02-15 15:39:39 +05:30
push_access_check
end
end
context 'when LFS is enabled' do
it 'checks LFS integrity only for first change' do
allow(project).to receive(:lfs_enabled?).and_return(true)
2020-01-01 13:55:28 +05:30
expect_next_instance_of(Gitlab::Checks::LfsIntegrity) do |instance|
2020-03-13 15:44:24 +05:30
expect(instance).to receive(:objects_missing?).once
2020-01-01 13:55:28 +05:30
end
2019-02-15 15:39:39 +05:30
push_access_check
end
2018-03-17 18:26:18 +05:30
end
end
2017-08-17 22:00:37 +05:30
describe '#check_push_access!' do
2018-11-18 11:00:15 +05:30
let(:unprotected_branch) { 'unprotected_branch' }
2017-09-10 17:25:29 +05:30
before do
merge_into_protected_branch
end
2015-04-26 12:48:37 +05:30
2016-08-24 12:49:21 +05:30
let(:changes) do
2019-02-15 15:39:39 +05:30
{ any: Gitlab::GitAccess::ANY,
push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
2015-04-26 12:48:37 +05:30
push_master: '6f6d7e7ed 570e7b2ab refs/heads/master',
push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature',
push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\
'refs/heads/feature',
push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0',
push_new_tag: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/tags/v7.8.9",
2016-08-24 12:49:21 +05:30
push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'],
merge_into_protected_branch: "0b4bc9a #{merge_into_protected_branch} refs/heads/feature" }
2015-04-26 12:48:37 +05:30
end
2016-08-24 12:49:21 +05:30
def merge_into_protected_branch
@protected_branch_merge_commit ||= begin
2018-12-05 23:21:45 +05:30
project.repository.add_branch(user, unprotected_branch, 'feature')
rugged = rugged_repo(project.repository)
target_branch = rugged.rev_parse('feature')
source_branch = project.repository.create_file(
user,
'filename',
'This is the file content',
message: 'This is a good commit message',
branch_name: unprotected_branch)
author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
merge_index = rugged.merge_commits(target_branch, source_branch)
Rugged::Commit.create(rugged, author: author, committer: author, message: "commit message", parents: [target_branch, source_branch], tree: merge_index.write_tree(rugged))
2016-08-24 12:49:21 +05:30
end
2015-04-26 12:48:37 +05:30
end
2016-08-24 12:49:21 +05:30
def self.run_permission_checks(permissions_matrix)
2017-09-10 17:25:29 +05:30
permissions_matrix.each_pair do |role, matrix|
# Run through the entire matrix for this role in one test to avoid
# repeated setup.
#
# Expectations are given a custom failure message proc so that it's
# easier to identify which check(s) failed.
it "has the correct permissions for #{role}s" do
if role == :admin
user.update_attribute(:admin, true)
2019-03-02 22:35:43 +05:30
project.add_guest(user)
2017-09-10 17:25:29 +05:30
else
2018-03-17 18:26:18 +05:30
project.add_role(user, role)
2017-08-17 22:00:37 +05:30
end
2015-04-26 12:48:37 +05:30
2019-03-02 22:35:43 +05:30
protected_branch.save
2017-09-10 17:25:29 +05:30
aggregate_failures do
matrix.each do |action, allowed|
2018-11-18 11:00:15 +05:30
check = -> { push_changes(changes[action]) }
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
if allowed
expect(&check).not_to raise_error,
-> { "expected #{action} to be allowed" }
else
2020-04-08 14:13:33 +05:30
expect(&check).to raise_error(Gitlab::GitAccess::ForbiddenError),
2017-09-10 17:25:29 +05:30
-> { "expected #{action} to be disallowed" }
2016-11-03 12:29:30 +05:30
end
2016-08-24 12:49:21 +05:30
end
2015-04-26 12:48:37 +05:30
end
end
end
end
2016-08-24 12:49:21 +05:30
permissions_matrix = {
2016-09-13 17:45:13 +05:30
admin: {
2019-02-15 15:39:39 +05:30
any: true,
2016-09-13 17:45:13 +05:30
push_new_branch: true,
push_master: true,
push_protected_branch: true,
push_remove_protected_branch: false,
push_tag: true,
push_new_tag: true,
push_all: true,
merge_into_protected_branch: true
},
2018-11-18 11:00:15 +05:30
maintainer: {
2019-02-15 15:39:39 +05:30
any: true,
2016-08-24 12:49:21 +05:30
push_new_branch: true,
push_master: true,
push_protected_branch: true,
push_remove_protected_branch: false,
push_tag: true,
push_new_tag: true,
push_all: true,
merge_into_protected_branch: true
},
developer: {
2019-02-15 15:39:39 +05:30
any: true,
2016-08-24 12:49:21 +05:30
push_new_branch: true,
push_master: true,
push_protected_branch: false,
push_remove_protected_branch: false,
2019-09-30 21:07:59 +05:30
push_tag: true,
2016-08-24 12:49:21 +05:30
push_new_tag: true,
push_all: false,
merge_into_protected_branch: false
},
reporter: {
2019-02-15 15:39:39 +05:30
any: false,
2016-08-24 12:49:21 +05:30
push_new_branch: false,
push_master: false,
push_protected_branch: false,
push_remove_protected_branch: false,
push_tag: false,
push_new_tag: false,
push_all: false,
merge_into_protected_branch: false
},
guest: {
2019-02-15 15:39:39 +05:30
any: false,
2016-08-24 12:49:21 +05:30
push_new_branch: false,
push_master: false,
push_protected_branch: false,
push_remove_protected_branch: false,
push_tag: false,
push_new_tag: false,
push_all: false,
merge_into_protected_branch: false
}
}
2015-04-26 12:48:37 +05:30
2017-08-17 22:00:37 +05:30
[%w(feature exact), ['feat*', 'wildcard']].each do |protected_branch_name, protected_branch_type|
2016-08-24 12:49:21 +05:30
context do
2019-03-02 22:35:43 +05:30
let(:protected_branch) { create(:protected_branch, :maintainers_can_push, name: protected_branch_name, project: project) }
2015-04-26 12:48:37 +05:30
2016-08-24 12:49:21 +05:30
run_permission_checks(permissions_matrix)
end
2016-09-13 17:45:13 +05:30
context "when developers are allowed to push into the #{protected_branch_type} protected branch" do
2019-03-02 22:35:43 +05:30
let(:protected_branch) { create(:protected_branch, :developers_can_push, name: protected_branch_name, project: project) }
2016-08-24 12:49:21 +05:30
run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
end
2016-09-13 17:45:13 +05:30
context "developers are allowed to merge into the #{protected_branch_type} protected branch" do
2019-03-02 22:35:43 +05:30
let(:protected_branch) { create(:protected_branch, :developers_can_merge, name: protected_branch_name, project: project) }
2016-08-24 12:49:21 +05:30
context "when a merge request exists for the given source/target branch" do
context "when the merge request is in progress" do
before do
2016-09-13 17:45:13 +05:30
create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature',
state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch)
2015-04-26 12:48:37 +05:30
end
2016-08-24 12:49:21 +05:30
run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: true }))
end
context "when the merge request is not in progress" do
before do
create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', in_progress_merge_commit_sha: nil)
end
run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
end
2016-09-13 17:45:13 +05:30
context "when a merge request does not exist for the given source/target branch" do
run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
end
2016-08-24 12:49:21 +05:30
end
end
2016-09-13 17:45:13 +05:30
context "when developers are allowed to push and merge into the #{protected_branch_type} protected branch" do
2019-03-02 22:35:43 +05:30
let(:protected_branch) { create(:protected_branch, :developers_can_merge, :developers_can_push, name: protected_branch_name, project: project) }
2016-08-24 12:49:21 +05:30
run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
end
2016-09-13 17:45:13 +05:30
context "when no one is allowed to push to the #{protected_branch_name} protected branch" do
2019-03-02 22:35:43 +05:30
let(:protected_branch) { build(:protected_branch, :no_one_can_push, name: protected_branch_name, project: project) }
2016-09-13 17:45:13 +05:30
run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false },
2018-11-18 11:00:15 +05:30
maintainer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false },
2016-09-13 17:45:13 +05:30
admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }))
end
2016-08-24 12:49:21 +05:30
end
2018-05-09 12:01:36 +05:30
context 'when pushing to a project' do
let(:project) { create(:project, :public, :repository) }
let(:changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow" }
before do
project.add_developer(user)
end
2019-12-21 20:55:43 +05:30
it 'does not allow deactivated users to push' do
user.deactivate!
2020-04-08 14:13:33 +05:30
expect { push_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
2019-12-21 20:55:43 +05:30
end
2018-05-09 12:01:36 +05:30
it 'cleans up the files' do
expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
expect { push_access_check }.not_to raise_error
end
2018-11-08 19:23:39 +05:30
it 'avoids N+1 queries', :request_store do
# Run this once to establish a baseline. Cached queries should get
# cached, so that when we introduce another change we shouldn't see
# additional queries.
access.check('git-receive-pack', changes)
control_count = ActiveRecord::QueryRecorder.new do
access.check('git-receive-pack', changes)
end
changes = ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature']
# There is still an N+1 query with protected branches
2019-12-04 20:38:33 +05:30
expect { access.check('git-receive-pack', changes) }.not_to exceed_query_limit(control_count).with_threshold(2)
2018-11-08 19:23:39 +05:30
end
2018-12-13 13:39:08 +05:30
it 'raises TimeoutError when #check_single_change_access raises a timeout error' do
message = "Push operation timed out\n\nTiming information for debugging purposes:\nRunning checks for ref: wow"
expect_next_instance_of(Gitlab::Checks::ChangeAccess) do |check|
expect(check).to receive(:exec).and_raise(Gitlab::Checks::TimedLogger::TimeoutError)
end
expect { access.check('git-receive-pack', changes) }.to raise_error(described_class::TimeoutError, message)
end
2018-05-09 12:01:36 +05:30
end
2016-09-13 17:45:13 +05:30
end
2016-08-24 12:49:21 +05:30
2017-09-10 17:25:29 +05:30
describe 'build authentication abilities' do
let(:authentication_abilities) { build_authentication_abilities }
2016-09-29 09:46:39 +05:30
context 'when project is authorized' do
2017-09-10 17:25:29 +05:30
before do
2018-03-17 18:26:18 +05:30
project.add_reporter(user)
2017-09-10 17:25:29 +05:30
end
2016-08-24 12:49:21 +05:30
2020-04-08 14:13:33 +05:30
it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload]) }
2016-09-29 09:46:39 +05:30
end
2016-08-24 12:49:21 +05:30
2016-09-29 09:46:39 +05:30
context 'when unauthorized' do
context 'to public project' do
2017-08-17 22:00:37 +05:30
let(:project) { create(:project, :public, :repository) }
2016-08-24 12:49:21 +05:30
2020-04-08 14:13:33 +05:30
it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload]) }
2016-09-13 17:45:13 +05:30
end
2016-08-24 12:49:21 +05:30
2016-09-29 09:46:39 +05:30
context 'to internal project' do
2017-08-17 22:00:37 +05:30
let(:project) { create(:project, :internal, :repository) }
2016-08-24 12:49:21 +05:30
2020-04-08 14:13:33 +05:30
it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload]) }
2016-09-29 09:46:39 +05:30
end
2016-08-24 12:49:21 +05:30
2016-09-29 09:46:39 +05:30
context 'to private project' do
2017-08-17 22:00:37 +05:30
let(:project) { create(:project, :private, :repository) }
2016-08-24 12:49:21 +05:30
2020-04-08 14:13:33 +05:30
it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload]) }
2015-04-26 12:48:37 +05:30
end
end
end
2016-09-29 09:46:39 +05:30
2018-03-17 18:26:18 +05:30
context 'when the repository is read only' do
let(:project) { create(:project, :repository, :read_only) }
it 'denies push access' do
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2018-03-17 18:26:18 +05:30
2020-04-08 14:13:33 +05:30
expect { push_access_check }.to raise_forbidden('The repository is temporarily read-only. Please try again later.')
2018-03-17 18:26:18 +05:30
end
end
2016-09-29 09:46:39 +05:30
describe 'deploy key permissions' do
2018-03-17 18:26:18 +05:30
let(:key) { create(:deploy_key, user: user) }
2016-09-29 09:46:39 +05:30
let(:actor) { key }
2017-08-17 22:00:37 +05:30
context 'when deploy_key can push' do
2017-09-10 17:25:29 +05:30
context 'when project is authorized' do
before do
2018-03-17 18:26:18 +05:30
key.deploy_keys_projects.create(project: project, can_push: true)
2017-08-17 22:00:37 +05:30
end
2017-09-10 17:25:29 +05:30
it { expect { push_access_check }.not_to raise_error }
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public, :repository) }
2020-04-08 14:13:33 +05:30
it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
2017-09-10 17:25:29 +05:30
end
context 'to internal project' do
let(:project) { create(:project, :internal, :repository) }
it { expect { push_access_check }.to raise_not_found }
end
context 'to private project' do
let(:project) { create(:project, :private, :repository) }
it { expect { push_access_check }.to raise_not_found }
end
2017-08-17 22:00:37 +05:30
end
end
context 'when deploy_key cannot push' do
2017-09-10 17:25:29 +05:30
context 'when project is authorized' do
before do
2018-03-17 18:26:18 +05:30
key.deploy_keys_projects.create(project: project, can_push: false)
2017-08-17 22:00:37 +05:30
end
2017-09-10 17:25:29 +05:30
2020-04-08 14:13:33 +05:30
it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
2017-09-10 17:25:29 +05:30
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public, :repository) }
2020-04-08 14:13:33 +05:30
it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
2017-09-10 17:25:29 +05:30
end
context 'to internal project' do
let(:project) { create(:project, :internal, :repository) }
it { expect { push_access_check }.to raise_not_found }
end
context 'to private project' do
let(:project) { create(:project, :private, :repository) }
it { expect { push_access_check }.to raise_not_found }
end
2016-09-29 09:46:39 +05:30
end
end
end
2018-10-15 14:42:47 +05:30
context 'terms are enforced' do
before do
enforce_terms
end
shared_examples 'access after accepting terms' do
let(:actions) do
[-> { pull_access_check },
-> { push_access_check }]
end
it 'blocks access when the user did not accept terms', :aggregate_failures do
actions.each do |action|
2020-04-08 14:13:33 +05:30
expect { action.call }.to raise_forbidden(/must accept the Terms of Service in order to perform this action/)
2018-10-15 14:42:47 +05:30
end
end
it 'allows access when the user accepted the terms', :aggregate_failures do
accept_terms(user)
actions.each do |action|
expect { action.call }.not_to raise_error
end
end
end
describe 'as an anonymous user to a public project' do
let(:actor) { nil }
let(:project) { create(:project, :public, :repository) }
it { expect { pull_access_check }.not_to raise_error }
end
describe 'as a guest to a public project' do
let(:project) { create(:project, :public, :repository) }
it_behaves_like 'access after accepting terms' do
let(:actions) { [-> { pull_access_check }] }
end
end
describe 'as a reporter to the project' do
before do
project.add_reporter(user)
end
it_behaves_like 'access after accepting terms' do
let(:actions) { [-> { pull_access_check }] }
end
end
describe 'as a developer of the project' do
before do
project.add_developer(user)
end
it_behaves_like 'access after accepting terms'
end
2018-11-18 11:00:15 +05:30
describe 'as a maintainer of the project' do
2018-10-15 14:42:47 +05:30
before do
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2018-10-15 14:42:47 +05:30
end
it_behaves_like 'access after accepting terms'
end
describe 'as an owner of the project' do
let(:project) { create(:project, :repository, namespace: user.namespace) }
it_behaves_like 'access after accepting terms'
end
describe 'when a ci build clones the project' do
let(:protocol) { 'http' }
let(:authentication_abilities) { [:build_download_code] }
let(:auth_result_type) { :build }
before do
project.add_developer(user)
end
it "doesn't block http pull" do
aggregate_failures do
expect { pull_access_check }.not_to raise_error
end
end
end
end
2016-09-29 09:46:39 +05:30
private
2018-11-18 11:00:15 +05:30
def access
described_class.new(actor, project, protocol,
authentication_abilities: authentication_abilities,
2020-04-08 14:13:33 +05:30
namespace_path: namespace_path, repository_path: project_path,
2018-11-18 11:00:15 +05:30
redirected_path: redirected_path, auth_result_type: auth_result_type)
end
def push_changes(changes)
access.check('git-receive-pack', changes)
end
2020-04-08 14:13:33 +05:30
def raise_forbidden(message)
raise_error(Gitlab::GitAccess::ForbiddenError, message)
2017-09-10 17:25:29 +05:30
end
def raise_not_found
2018-03-17 18:26:18 +05:30
raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found])
2017-09-10 17:25:29 +05:30
end
2020-06-23 00:09:42 +05:30
def raise_namespace_not_found
raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:namespace_not_found])
end
2016-09-29 09:46:39 +05:30
def build_authentication_abilities
[
:read_project,
:build_download_code
]
end
def full_authentication_abilities
[
:read_project,
:download_code,
:push_code
]
end
2015-04-26 12:48:37 +05:30
end