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
2020-10-24 23:57:45 +05:30
let ( :access_class ) do
Class . new ( described_class ) do
def push_ability
:push_code
end
def download_ability
:download_code
end
end
end
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-10-24 23:57:45 +05:30
expect { pull_access_check } . not_to raise_error
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-10-24 23:57:45 +05:30
expect { pull_access_check } . not_to raise_error
2018-05-09 12:01:36 +05:30
end
end
end
end
2017-09-10 17:25:29 +05:30
end
end
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-10-24 23:57:45 +05:30
let ( :access ) { access_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
2020-09-03 11:15:55 +05:30
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
2017-09-10 17:25:29 +05:30
end
2015-04-26 12:48:37 +05:30
2020-09-03 11:15:55 +05:30
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
2016-08-24 12:49:21 +05:30
end
2020-09-03 11:15:55 +05:30
context 'when project is internal' do
let ( :project ) { create ( :project , :internal , :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
2016-08-24 12:49:21 +05:30
2020-09-03 11:15:55 +05:30
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
2016-08-24 12:49:21 +05:30
end
2020-09-03 11:15:55 +05:30
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_error ( 'The project you were looking for could not be found.' ) }
end
2016-08-24 12:49:21 +05:30
2020-09-03 11:15:55 +05:30
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
2016-08-24 12:49:21 +05:30
end
2020-09-03 11:15:55 +05:30
end
2016-08-24 12:49:21 +05:30
2020-09-03 11:15:55 +05:30
context 'when project is private' do
let ( :project ) { create ( :project , :private , :repository , * options ) }
2016-08-24 12:49:21 +05:30
2020-09-03 11:15:55 +05:30
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
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 \n Timing information for debugging purposes: \n Running checks for ref: wow "
expect_next_instance_of ( Gitlab :: Checks :: ChangeAccess ) do | check |
2020-10-24 23:57:45 +05:30
expect ( check ) . to receive ( :validate! ) . and_raise ( Gitlab :: Checks :: TimedLogger :: TimeoutError )
2018-12-13 13:39:08 +05:30
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
2020-10-24 23:57:45 +05:30
access_class . new ( actor , project , protocol ,
2018-11-18 11:00:15 +05:30
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 )
2020-10-24 23:57:45 +05:30
raise_error ( described_class :: ForbiddenError , message )
2017-09-10 17:25:29 +05:30
end
def raise_not_found
2020-10-24 23:57:45 +05:30
raise_error ( described_class :: NotFoundError , described_class :: ERROR_MESSAGES [ :project_not_found ] )
2020-06-23 00:09:42 +05:30
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