diff options
Diffstat (limited to 'spec/lib')
-rw-r--r-- | spec/lib/banzai/filter/autolink_filter_spec.rb | 20 | ||||
-rw-r--r-- | spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 151 | ||||
-rw-r--r-- | spec/lib/container_registry/blob_spec.rb | 52 | ||||
-rw-r--r-- | spec/lib/container_registry/tag_spec.rb | 49 | ||||
-rw-r--r-- | spec/lib/gitlab/badge/build_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/gitlab/bitbucket_import/client_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/gitlab/diff/file_spec.rb | 14 | ||||
-rw-r--r-- | spec/lib/gitlab/diff/position_tracer_spec.rb | 3 | ||||
-rw-r--r-- | spec/lib/gitlab/git_access_spec.rb | 319 | ||||
-rw-r--r-- | spec/lib/gitlab/import_export/avatar_restorer_spec.rb | 25 | ||||
-rw-r--r-- | spec/lib/gitlab/import_export/avatar_saver_spec.rb | 27 | ||||
-rw-r--r-- | spec/lib/gitlab/import_export/project.json | 18 | ||||
-rw-r--r-- | spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 7 | ||||
-rw-r--r-- | spec/lib/gitlab/import_export/project_tree_saver_spec.rb | 9 | ||||
-rw-r--r-- | spec/lib/gitlab/lfs/lfs_router_spec.rb | 730 | ||||
-rw-r--r-- | spec/lib/gitlab/user_access_spec.rb | 88 |
16 files changed, 547 insertions, 969 deletions
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb index 84c2ddf444e..dca7f997570 100644 --- a/spec/lib/banzai/filter/autolink_filter_spec.rb +++ b/spec/lib/banzai/filter/autolink_filter_spec.rb @@ -15,6 +15,16 @@ describe Banzai::Filter::AutolinkFilter, lib: true do expect(filter(act).to_html).to eq exp end + context 'when the input contains no links' do + it 'does not parse_html back the rinku returned value' do + act = HTML::Pipeline.parse('<p>This text contains no links to autolink</p>') + + expect_any_instance_of(described_class).not_to receive(:parse_html) + + filter(act).to_html + end + end + context 'Rinku schemes' do it 'autolinks http' do doc = filter("See #{link}") @@ -58,6 +68,16 @@ describe Banzai::Filter::AutolinkFilter, lib: true do expect(filter(act).to_html).to eq exp end end + + context 'when the input contains link' do + it 'does parse_html back the rinku returned value' do + act = HTML::Pipeline.parse("<p>See #{link}</p>") + + expect_any_instance_of(described_class).to receive(:parse_html).at_least(:once).and_call_original + + filter(act).to_html + end + end end context 'other schemes' do diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index bcbf409c8b0..d20fd4ab7dd 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -19,15 +19,14 @@ module Ci expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({ stage: "test", stage_idx: 1, - except: nil, name: :rspec, - only: nil, commands: "pwd\nrspec", tag_list: [], options: {}, allow_failure: false, when: "on_success", environment: nil, + yaml_variables: [] }) end @@ -432,11 +431,9 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ - except: nil, stage: "test", stage_idx: 1, name: :rspec, - only: nil, commands: "pwd\nrspec", tag_list: [], options: { @@ -446,6 +443,7 @@ module Ci allow_failure: false, when: "on_success", environment: nil, + yaml_variables: [] }) end @@ -461,11 +459,9 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ - except: nil, stage: "test", stage_idx: 1, name: :rspec, - only: nil, commands: "pwd\nrspec", tag_list: [], options: { @@ -475,101 +471,126 @@ module Ci allow_failure: false, when: "on_success", environment: nil, + yaml_variables: [] }) end end describe 'Variables' do - context 'when global variables are defined' do - it 'returns global variables' do - variables = { - VAR1: 'value1', - VAR2: 'value2', - } + let(:config_processor) { GitlabCiYamlProcessor.new(YAML.dump(config), path) } - config = YAML.dump({ + subject { config_processor.builds.first[:yaml_variables] } + + context 'when global variables are defined' do + let(:variables) do + { VAR1: 'value1', VAR2: 'value2' } + end + let(:config) do + { variables: variables, before_script: ['pwd'], rspec: { script: 'rspec' } - }) + } + end - config_processor = GitlabCiYamlProcessor.new(config, path) + it 'returns global variables' do + expect(subject).to contain_exactly( + { key: :VAR1, value: 'value1', public: true }, + { key: :VAR2, value: 'value2', public: true } + ) + end + end + + context 'when job and global variables are defined' do + let(:global_variables) do + { VAR1: 'global1', VAR3: 'global3' } + end + let(:job_variables) do + { VAR1: 'value1', VAR2: 'value2' } + end + let(:config) do + { + before_script: ['pwd'], + variables: global_variables, + rspec: { script: 'rspec', variables: job_variables } + } + end - expect(config_processor.global_variables).to eq(variables) + it 'returns all unique variables' do + expect(subject).to contain_exactly( + { key: :VAR3, value: 'global3', public: true }, + { key: :VAR1, value: 'value1', public: true }, + { key: :VAR2, value: 'value2', public: true } + ) end end context 'when job variables are defined' do - context 'when syntax is correct' do - it 'returns job variables' do - variables = { - KEY1: 'value1', - SOME_KEY_2: 'value2' - } + let(:config) do + { + before_script: ['pwd'], + rspec: { script: 'rspec', variables: variables } + } + end + + context 'when also global variables are defined' do - config = YAML.dump( - { before_script: ['pwd'], - rspec: { - variables: variables, - script: 'rspec' } - }) + end - config_processor = GitlabCiYamlProcessor.new(config, path) + context 'when syntax is correct' do + let(:variables) do + { VAR1: 'value1', VAR2: 'value2' } + end - expect(config_processor.job_variables(:rspec)).to eq variables + it 'returns job variables' do + expect(subject).to contain_exactly( + { key: :VAR1, value: 'value1', public: true }, + { key: :VAR2, value: 'value2', public: true } + ) end end context 'when syntax is incorrect' do context 'when variables defined but invalid' do - it 'raises error' do - variables = [:KEY1, 'value1', :KEY2, 'value2'] - - config = YAML.dump( - { before_script: ['pwd'], - rspec: { - variables: variables, - script: 'rspec' } - }) + let(:variables) do + [ :VAR1, 'value1', :VAR2, 'value2' ] + end - expect { GitlabCiYamlProcessor.new(config, path) } + it 'raises error' do + expect { subject } .to raise_error(GitlabCiYamlProcessor::ValidationError, - /job: variables should be a map/) + /job: variables should be a map/) end end context 'when variables key defined but value not specified' do - it 'returns empty array' do - config = YAML.dump( - { before_script: ['pwd'], - rspec: { - variables: nil, - script: 'rspec' } - }) - - config_processor = GitlabCiYamlProcessor.new(config, path) + let(:variables) do + nil + end + it 'returns empty array' do ## # When variables config is empty, we assume this is a valid # configuration, see issue #18775 # - expect(config_processor.job_variables(:rspec)) - .to be_an_instance_of(Array).and be_empty + expect(subject).to be_an_instance_of(Array) + expect(subject).to be_empty end end end end context 'when job variables are not defined' do - it 'returns empty array' do - config = YAML.dump({ + let(:config) do + { before_script: ['pwd'], rspec: { script: 'rspec' } - }) - - config_processor = GitlabCiYamlProcessor.new(config, path) + } + end - expect(config_processor.job_variables(:rspec)).to eq [] + it 'returns empty array' do + expect(subject).to be_an_instance_of(Array) + expect(subject).to be_empty end end end @@ -681,11 +702,9 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ - except: nil, stage: "test", stage_idx: 1, name: :rspec, - only: nil, commands: "pwd\nrspec", tag_list: [], options: { @@ -701,6 +720,7 @@ module Ci when: "on_success", allow_failure: false, environment: nil, + yaml_variables: [] }) end @@ -819,17 +839,16 @@ module Ci it "doesn't create jobs that start with dot" do expect(subject.size).to eq(1) expect(subject.first).to eq({ - except: nil, stage: "test", stage_idx: 1, name: :normal_job, - only: nil, commands: "test", tag_list: [], options: {}, when: "on_success", allow_failure: false, environment: nil, + yaml_variables: [] }) end end @@ -865,30 +884,28 @@ module Ci it "is correctly supported for jobs" do expect(subject.size).to eq(2) expect(subject.first).to eq({ - except: nil, stage: "build", stage_idx: 0, name: :job1, - only: nil, commands: "execute-script-for-job", tag_list: [], options: {}, when: "on_success", allow_failure: false, environment: nil, + yaml_variables: [] }) expect(subject.second).to eq({ - except: nil, stage: "build", stage_idx: 0, name: :job2, - only: nil, commands: "execute-script-for-job", tag_list: [], options: {}, when: "on_success", allow_failure: false, environment: nil, + yaml_variables: [] }) end end @@ -1124,7 +1141,7 @@ EOT config = YAML.dump({ rspec: { script: "test", when: 1 } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure, always or manual") end it "returns errors if job artifacts:name is not an a string" do diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb index 4d8cb787dde..bbacdc67ebd 100644 --- a/spec/lib/container_registry/blob_spec.rb +++ b/spec/lib/container_registry/blob_spec.rb @@ -9,8 +9,9 @@ describe ContainerRegistry::Blob do 'size' => 1000 } end + let(:token) { 'authorization-token' } - let(:registry) { ContainerRegistry::Registry.new('http://example.com') } + let(:registry) { ContainerRegistry::Registry.new('http://example.com', token: token) } let(:repository) { registry.repository('group/test') } let(:blob) { repository.blob(config) } @@ -58,4 +59,53 @@ describe ContainerRegistry::Blob do it { is_expected.to be_truthy } end + + context '#data' do + let(:data) { '{"key":"value"}' } + + subject { blob.data } + + context 'when locally stored' do + before do + stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:0123456789012345'). + to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: data) + end + + it { is_expected.to eq(data) } + end + + context 'when externally stored' do + before do + stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:0123456789012345'). + with(headers: { 'Authorization' => "bearer #{token}" }). + to_return( + status: 307, + headers: { 'Location' => location }) + end + + context 'for a valid address' do + let(:location) { 'http://external.com/blob/file' } + + before do + stub_request(:get, location). + with(headers: { 'Authorization' => nil }). + to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: data) + end + + it { is_expected.to eq(data) } + end + + context 'for invalid file' do + let(:location) { 'file:///etc/passwd' } + + it { expect{ subject }.to raise_error(ArgumentError, 'invalid address') } + end + end + end end diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb index c7324c2bf77..c5e31ae82b6 100644 --- a/spec/lib/container_registry/tag_spec.rb +++ b/spec/lib/container_registry/tag_spec.rb @@ -77,24 +77,47 @@ describe ContainerRegistry::Tag do end context 'config processing' do - before do - stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac'). - with(headers: { 'Accept' => 'application/octet-stream' }). - to_return( - status: 200, - body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json')) - end + shared_examples 'a processable' do + context '#config' do + subject { tag.config } - context '#config' do - subject { tag.config } + it { is_expected.not_to be_nil } + end + + context '#created_at' do + subject { tag.created_at } - it { is_expected.not_to be_nil } + it { is_expected.not_to be_nil } + end end - context '#created_at' do - subject { tag.created_at } + context 'when locally stored' do + before do + stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac'). + with(headers: { 'Accept' => 'application/octet-stream' }). + to_return( + status: 200, + body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json')) + end + + it_behaves_like 'a processable' + end - it { is_expected.not_to be_nil } + context 'when externally stored' do + before do + stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac'). + with(headers: { 'Accept' => 'application/octet-stream' }). + to_return( + status: 307, + headers: { 'Location' => 'http://external.com/blob/file' }) + + stub_request(:get, 'http://external.com/blob/file'). + to_return( + status: 200, + body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json')) + end + + it_behaves_like 'a processable' end end end diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb index 2034445a197..f3b522a02f5 100644 --- a/spec/lib/gitlab/badge/build_spec.rb +++ b/spec/lib/gitlab/badge/build_spec.rb @@ -113,7 +113,7 @@ describe Gitlab::Badge::Build do sha: sha, ref: branch) - create(:ci_build, pipeline: pipeline) + create(:ci_build, pipeline: pipeline, stage: 'notify') end def status_node(data, status) diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb index 760d66a1488..7543c29bcc4 100644 --- a/spec/lib/gitlab/bitbucket_import/client_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb @@ -54,12 +54,12 @@ describe Gitlab::BitbucketImport::Client, lib: true do context 'project import' do it 'calls .from_project with no errors' do project = create(:empty_project) + project.import_url = "ssh://git@bitbucket.org/test/test.git" project.create_or_update_import_data(credentials: { user: "git", password: nil, bb_session: { bitbucket_access_token: "test", bitbucket_access_token_secret: "test" } }) - project.import_url = "ssh://git@bitbucket.org/test/test.git" expect { described_class.from_project(project) }.not_to raise_error end diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 0460dcf4658..e883a6eb9c2 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -32,4 +32,18 @@ describe Gitlab::Diff::File, lib: true do expect(diff_file.too_large?).to eq(false) end end + + describe '#collapsed?' do + it 'returns true for a file that is quite big' do + expect(diff).to receive(:collapsed?).and_return(true) + + expect(diff_file.collapsed?).to eq(true) + end + + it 'returns false for a file that is small enough' do + expect(diff).to receive(:collapsed?).and_return(false) + + expect(diff_file.collapsed?).to eq(false) + end + end end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index 08312e60f4a..c268f84c759 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -1639,7 +1639,8 @@ describe Gitlab::Diff::PositionTracer, lib: true do committer: committer } - repository.merge(current_user, second_create_file_commit.sha, branch_name, options) + merge_request = create(:merge_request, source_branch: second_create_file_commit.sha, target_branch: branch_name, source_project: project) + repository.merge(current_user, merge_request, options) project.commit(branch_name) end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index c79ba11f782..ae064a878b0 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -6,67 +6,6 @@ describe Gitlab::GitAccess, lib: true do let(:user) { create(:user) } let(:actor) { user } - describe 'can_push_to_branch?' do - describe 'push to none protected branch' do - it "returns true if user is a master" do - project.team << [user, :master] - expect(access.can_push_to_branch?("random_branch")).to be_truthy - end - - it "returns true if user is a developer" do - project.team << [user, :developer] - expect(access.can_push_to_branch?("random_branch")).to be_truthy - end - - it "returns false if user is a reporter" do - project.team << [user, :reporter] - expect(access.can_push_to_branch?("random_branch")).to be_falsey - end - end - - describe 'push to protected branch' do - before do - @branch = create :protected_branch, project: project - end - - it "returns true if user is a master" do - project.team << [user, :master] - expect(access.can_push_to_branch?(@branch.name)).to be_truthy - end - - it "returns false if user is a developer" do - project.team << [user, :developer] - expect(access.can_push_to_branch?(@branch.name)).to be_falsey - end - - it "returns false if user is a reporter" do - project.team << [user, :reporter] - expect(access.can_push_to_branch?(@branch.name)).to be_falsey - end - end - - describe 'push to protected branch if allowed for developers' do - before do - @branch = create :protected_branch, project: project, developers_can_push: true - end - - it "returns true if user is a master" do - project.team << [user, :master] - expect(access.can_push_to_branch?(@branch.name)).to be_truthy - end - - it "returns true if user is a developer" do - project.team << [user, :developer] - expect(access.can_push_to_branch?(@branch.name)).to be_truthy - end - - it "returns false if user is a reporter" do - project.team << [user, :reporter] - expect(access.can_push_to_branch?(@branch.name)).to be_falsey - end - end - end - describe '#check with single protocols allowed' do def disable_protocol(protocol) settings = ::ApplicationSetting.create_from_defaults @@ -105,12 +44,12 @@ describe Gitlab::GitAccess, lib: true do end describe 'download_access_check' do + subject { access.check('git-upload-pack') } + describe 'master permissions' do before { project.team << [user, :master] } context 'pull code' do - subject { access.download_access_check } - it { expect(subject.allowed?).to be_truthy } end end @@ -119,8 +58,6 @@ describe Gitlab::GitAccess, lib: true do before { project.team << [user, :guest] } context 'pull code' do - subject { access.download_access_check } - it { expect(subject.allowed?).to be_falsey } end end @@ -132,16 +69,12 @@ describe Gitlab::GitAccess, lib: true do end context 'pull code' do - subject { access.download_access_check } - it { expect(subject.allowed?).to be_falsey } end end describe 'without acccess to project' do context 'pull code' do - subject { access.download_access_check } - it { expect(subject.allowed?).to be_falsey } end end @@ -151,110 +84,208 @@ describe Gitlab::GitAccess, lib: true do let(:actor) { key } context 'pull code' do - before { key.projects << project } - subject { access.download_access_check } + context 'when project is authorized' do + before { key.projects << project } - it { expect(subject.allowed?).to be_truthy } + it { expect(subject).to be_allowed } + end + + context 'when unauthorized' do + context 'from public project' do + let(:project) { create(:project, :public) } + + it { expect(subject).to be_allowed } + end + + context 'from internal project' do + let(:project) { create(:project, :internal) } + + it { expect(subject).not_to be_allowed } + end + + context 'from private project' do + let(:project) { create(:project, :internal) } + + it { expect(subject).not_to be_allowed } + end + end end end end describe 'push_access_check' do - def protect_feature_branch - create(:protected_branch, name: 'feature', project: project) - end + before { merge_into_protected_branch } + let(:unprotected_branch) { FFaker::Internet.user_name } - def changes - { - push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow", + let(:changes) do + { push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow", 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", - push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'] - } + push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'], + merge_into_protected_branch: "0b4bc9a #{merge_into_protected_branch} refs/heads/feature" } end - def self.permissions_matrix - { - master: { - 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, - }, - - developer: { - push_new_branch: true, - push_master: true, - push_protected_branch: false, - push_remove_protected_branch: false, - push_tag: false, - push_new_tag: true, - push_all: false, - }, - - reporter: { - 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, - }, - - guest: { - 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, - } - } + def stub_git_hooks + # Running the `pre-receive` hook is expensive, and not necessary for this test. + allow_any_instance_of(GitHooksService).to receive(:execute).and_yield end - def self.updated_permissions_matrix - updated_permissions_matrix = permissions_matrix.dup - updated_permissions_matrix[:developer][:push_protected_branch] = true - updated_permissions_matrix[:developer][:push_all] = true - updated_permissions_matrix + def merge_into_protected_branch + @protected_branch_merge_commit ||= begin + stub_git_hooks + project.repository.add_branch(user, unprotected_branch, 'feature') + target_branch = project.repository.lookup('feature') + source_branch = project.repository.commit_file(user, FFaker::InternetSE.login_user_name, FFaker::HipsterIpsum.paragraph, FFaker::HipsterIpsum.sentence, unprotected_branch, false) + rugged = project.repository.rugged + 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)) + end end - permissions_matrix.keys.each do |role| - describe "#{role} access" do - before { protect_feature_branch } - before { project.team << [user, role] } + def self.run_permission_checks(permissions_matrix) + permissions_matrix.keys.each do |role| + describe "#{role} access" do + before { project.team << [user, role] } - permissions_matrix[role].each do |action, allowed| - context action do - subject { access.push_access_check(changes[action]) } + permissions_matrix[role].each do |action, allowed| + context action do + subject { access.push_access_check(changes[action]) } - it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } + it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } + end end end end end - context "with enabled developers push to protected branches " do - updated_permissions_matrix.keys.each do |role| - describe "#{role} access" do - before { create(:protected_branch, name: 'feature', developers_can_push: true, project: project) } - before { project.team << [user, role] } + permissions_matrix = { + master: { + 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: { + push_new_branch: true, + push_master: true, + push_protected_branch: false, + push_remove_protected_branch: false, + push_tag: false, + push_new_tag: true, + push_all: false, + merge_into_protected_branch: false + }, + + reporter: { + 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: { + 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 + } + } - updated_permissions_matrix[role].each do |action, allowed| - context action do - subject { access.push_access_check(changes[action]) } + [['feature', 'exact'], ['feat*', 'wildcard']].each do |protected_branch_name, protected_branch_type| + context do + before { create(:protected_branch, name: protected_branch_name, project: project) } - it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } + run_permission_checks(permissions_matrix) + end + + context "when 'developers can push' is turned on for the #{protected_branch_type} protected branch" do + before { create(:protected_branch, name: protected_branch_name, developers_can_push: true, project: project) } + + run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true })) + end + + context "when 'developers can merge' is turned on for the #{protected_branch_type} protected branch" do + before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, project: project) } + + context "when a merge request exists for the given source/target branch" do + context "when the merge request is in progress" do + before do + create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch) end + + 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 + end + + 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 + end + + context "when 'developers can merge' and 'developers can push' are turned on for the #{protected_branch_type} protected branch" do + before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, developers_can_push: true, project: project) } + + run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true })) + end + end + + describe 'deploy key permissions' do + let(:key) { create(:deploy_key) } + let(:actor) { key } + + context 'push code' do + subject { access.check('git-receive-pack') } + + context 'when project is authorized' do + before { key.projects << project } + + it { expect(subject).not_to be_allowed } + end + + context 'when unauthorized' do + context 'to public project' do + let(:project) { create(:project, :public) } + + it { expect(subject).not_to be_allowed } + end + + context 'to internal project' do + let(:project) { create(:project, :internal) } + + it { expect(subject).not_to be_allowed } + end + + context 'to private project' do + let(:project) { create(:project, :internal) } + + it { expect(subject).not_to be_allowed } end end end diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb new file mode 100644 index 00000000000..5ae178414cc --- /dev/null +++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::AvatarRestorer, lib: true do + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } + let(:project) { create(:empty_project) } + + before do + allow_any_instance_of(described_class).to receive(:avatar_export_file) + .and_return(Rails.root + "spec/fixtures/dk.png") + end + + after do + project.remove_avatar! + end + + it 'restores a project avatar' do + expect(described_class.new(project: project, shared: shared).restore).to be true + end + + it 'saves the avatar into the project' do + described_class.new(project: project, shared: shared).restore + + expect(project.reload.avatar.file.exists?).to be true + end +end diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb new file mode 100644 index 00000000000..d6ee94442cb --- /dev/null +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::AvatarSaver, lib: true do + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } + let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" } + let(:project_with_avatar) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let(:project) { create(:empty_project) } + + before do + FileUtils.mkdir_p("#{shared.export_path}/avatar/") + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + end + + after do + FileUtils.rm_rf("#{shared.export_path}/avatar") + end + + it 'saves a project avatar' do + described_class.new(project: project_with_avatar, shared: shared).save + + expect(File).to exist("#{shared.export_path}/avatar/dk.png") + end + + it 'is fine not to have an avatar' do + expect(described_class.new(project: project, shared: shared).save).to be true + end +end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 4113d829c3c..b1a5d72c624 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -2765,7 +2765,7 @@ "committer_email": "dmitriy.zaporozhets@gmail.com" } ], - "st_diffs": [ + "utf8_st_diffs": [ { "diff": "Binary files a/.DS_Store and /dev/null differ\n", "new_path": ".DS_Store", @@ -3138,7 +3138,7 @@ "committer_email": "dmitriy.zaporozhets@gmail.com" } ], - "st_diffs": [ + "utf8_st_diffs": [ { "diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,5 @@\n+class Feature\n+ def foo\n+ puts 'bar'\n+ end\n+end\n", "new_path": "files/ruby/feature.rb", @@ -3423,7 +3423,7 @@ "committer_email": "james@jameslopez.es" } ], - "st_diffs": [ + "utf8_st_diffs": [ { "diff": "--- /dev/null\n+++ b/test\n", "new_path": "test", @@ -3960,7 +3960,7 @@ "committer_email": "dmitriy.zaporozhets@gmail.com" } ], - "st_diffs": [ + "utf8_st_diffs": [ { "diff": "Binary files a/.DS_Store and /dev/null differ\n", "new_path": ".DS_Store", @@ -4597,7 +4597,7 @@ "committer_email": "marmis85@gmail.com" } ], - "st_diffs": [ + "utf8_st_diffs": [ { "diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n", "new_path": "CHANGELOG", @@ -5108,7 +5108,7 @@ "committer_email": "stanhu@packetzoom.com" } ], - "st_diffs": [ + "utf8_st_diffs": [ { "diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n", "new_path": "CHANGELOG", @@ -5434,7 +5434,7 @@ "id": 11, "state": "empty", "st_commits": null, - "st_diffs": [ + "utf8_st_diffs": [ ], "merge_request_id": 11, @@ -5961,7 +5961,7 @@ "committer_email": "dmitriy.zaporozhets@gmail.com" } ], - "st_diffs": [ + "utf8_st_diffs": [ { "diff": "Binary files a/.DS_Store and /dev/null differ\n", "new_path": ".DS_Store", @@ -6400,7 +6400,7 @@ "committer_email": "james@jameslopez.es" } ], - "st_diffs": [ + "utf8_st_diffs": [ { "diff": "--- /dev/null\n+++ b/test\n", "new_path": "test", diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 877be300262..6ae20c943b1 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do describe 'restore project tree' do + let(:user) { create(:user) } let(:namespace) { create(:namespace, owner: user) } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } @@ -53,6 +54,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(event.note.noteable.project).not_to be_nil end end + + it 'has the correct data for merge request st_diffs' do + # makes sure we are renaming the custom method +utf8_st_diffs+ into +st_diffs+ + + expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9) + end end end end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 1424de9e60b..057ef6e76a0 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -102,12 +102,17 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do it 'has ci pipeline notes' do expect(saved_project_json['pipelines'].first['notes']).not_to be_empty end + + it 'does not complain about non UTF-8 characters in MR diffs' do + ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'") + + expect(project_tree_saver.save).to be true + end end end def setup_project issue = create(:issue, assignee: user) - merge_request = create(:merge_request) label = create(:label) snippet = create(:project_snippet) release = create(:release) @@ -115,12 +120,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do project = create(:project, :public, issues: [issue], - merge_requests: [merge_request], labels: [label], snippets: [snippet], releases: [release] ) + merge_request = create(:merge_request, source_project: project) commit_status = create(:commit_status, project: project) ci_pipeline = create(:ci_pipeline, diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb deleted file mode 100644 index 659facd6c19..00000000000 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ /dev/null @@ -1,730 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Lfs::Router, lib: true do - let(:project) { create(:project) } - let(:public_project) { create(:project, :public) } - let(:forked_project) { fork_project(public_project, user) } - - let(:user) { create(:user) } - let(:user_two) { create(:user) } - let!(:lfs_object) { create(:lfs_object, :with_file) } - - let(:request) { Rack::Request.new(env) } - let(:env) do - { - 'rack.input' => '', - 'REQUEST_METHOD' => 'GET', - } - end - - let(:lfs_router_auth) { new_lfs_router(project, user: user) } - let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) } - let(:lfs_router_noauth) { new_lfs_router(project) } - let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) } - let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) } - let(:lfs_router_public_noauth) { new_lfs_router(public_project) } - let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) } - let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) } - let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) } - - let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } - let(:sample_size) { 499013 } - let(:respond_with_deprecated) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} - let(:respond_with_disabled) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} - - describe 'when lfs is disabled' do - before do - allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) - env['REQUEST_METHOD'] = 'POST' - body = { - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ], - 'operation' => 'upload' - }.to_json - env['rack.input'] = StringIO.new(body) - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" - end - - it 'responds with 501' do - expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) - end - end - - describe 'when fetching lfs object using deprecated API' do - before do - enable_lfs - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" - end - - it 'responds with 501' do - expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) - end - end - - describe 'when fetching lfs object' do - before do - enable_lfs - env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" - end - - describe 'and request comes from gitlab-workhorse' do - context 'without user being authorized' do - it "responds with status 401" do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - - context 'with required headers' do - before do - project.lfs_objects << lfs_object - env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile" - end - - context 'when user does not have project access' do - it "responds with status 403" do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - - context 'when user has project access' do - before do - project.team << [user, :master] - end - - it "responds with status 200" do - expect(lfs_router_auth.try_call.first).to eq(200) - end - - it "responds with the file location" do - expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") - expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) - end - end - - context 'when CI is authorized' do - it "responds with status 200" do - expect(lfs_router_ci_auth.try_call.first).to eq(200) - end - - it "responds with the file location" do - expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") - expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) - end - end - end - - context 'without required headers' do - it "responds with status 403" do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - end - end - - describe 'when handling lfs request using deprecated API' do - before do - enable_lfs - env['REQUEST_METHOD'] = 'POST' - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects" - end - - it 'responds with 501' do - expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) - end - end - - describe 'when handling lfs batch request' do - before do - enable_lfs - env['REQUEST_METHOD'] = 'POST' - env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" - end - - describe 'download' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - shared_examples 'an authorized requests' do - context 'when downloading an lfs object that is assigned to our project' do - before do - project.lfs_objects << lfs_object - end - - it 'responds with status 200 and href to download' do - response = router.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => { 'Authorization' => auth } - } - } - }]) - end - end - - context 'when downloading an lfs object that is assigned to other project' do - before do - public_project.lfs_objects << lfs_object - end - - it 'responds with status 200 and error message' do - response = router.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) - end - end - - context 'when downloading a lfs object that does not exist' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - it "responds with status 200 and error message" do - response = router.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) - end - end - - context 'when downloading one new and one existing lfs object' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ] - }.to_json - env['rack.input'] = StringIO.new(body) - project.lfs_objects << lfs_object - end - - it "responds with status 200 with upload hypermedia link for the new object" do - response = router.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }, - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => { 'Authorization' => auth } - } - } - }]) - end - end - end - - context 'when user is authenticated' do - let(:auth) { authorize(user) } - - before do - env["HTTP_AUTHORIZATION"] = auth - project.team << [user, role] - end - - it_behaves_like 'an authorized requests' do - let(:role) { :reporter } - let(:router) { lfs_router_auth } - end - - context 'when user does is not member of the project' do - let(:role) { :guest } - - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - - context 'when user does not have download access' do - let(:role) { :guest } - - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - end - - context 'when CI is authorized' do - let(:auth) { 'gitlab-ci-token:password' } - - before do - env["HTTP_AUTHORIZATION"] = auth - end - - it_behaves_like 'an authorized requests' do - let(:router) { lfs_router_ci_auth } - end - end - - context 'when user is not authenticated' do - describe 'is accessing public project' do - before do - public_project.lfs_objects << lfs_object - end - - it 'responds with status 200 and href to download' do - response = lfs_router_public_noauth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => {} - } - } - }]) - end - end - - describe 'is accessing non-public project' do - before do - project.lfs_objects << lfs_object - end - - it 'responds with authorization required' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - end - end - - describe 'upload' do - before do - body = { 'operation' => 'upload', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - describe 'when request is authenticated' do - describe 'when user has project push access' do - before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :developer] - end - - context 'when pushing an lfs object that already exists' do - before do - public_project.lfs_objects << lfs_object - end - - it "responds with status 200 and links the object to the project" do - response_body = lfs_router_auth.try_call.last - response = ActiveSupport::JSON.decode(response_body.first) - - expect(response['objects']).to be_kind_of(Array) - expect(response['objects'].first['oid']).to eq(sample_oid) - expect(response['objects'].first['size']).to eq(sample_size) - expect(lfs_object.projects.pluck(:id)).not_to include(project.id) - expect(lfs_object.projects.pluck(:id)).to include(public_project.id) - expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}") - expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) - end - end - - context 'when pushing a lfs object that does not exist' do - before do - body = { 'operation' => 'upload', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - it "responds with status 200 and upload hypermedia link" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - - response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body['objects']).to be_kind_of(Array) - expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") - expect(response_body['objects'].first['size']).to eq(1575078) - expect(lfs_object.projects.pluck(:id)).not_to include(project.id) - expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") - expect(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) - end - end - - context 'when pushing one new and one existing lfs object' do - before do - body = { 'operation' => 'upload', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ] - }.to_json - env['rack.input'] = StringIO.new(body) - project.lfs_objects << lfs_object - end - - it "responds with status 200 with upload hypermedia link for the new object" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - - response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body['objects']).to be_kind_of(Array) - - expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") - expect(response_body['objects'].first['size']).to eq(1575078) - expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") - expect(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth) - - expect(response_body['objects'].last['oid']).to eq(sample_oid) - expect(response_body['objects'].last['size']).to eq(sample_size) - expect(response_body['objects'].last).not_to have_key('actions') - end - end - end - - context 'when user does not have push access' do - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - - context 'when CI is authorized' do - it 'responds with 401' do - expect(lfs_router_ci_auth.try_call.first).to eq(401) - end - end - end - - context 'when user is not authenticated' do - context 'when user has push access' do - before do - project.team << [user, :master] - end - - it "responds with status 401" do - expect(lfs_router_public_noauth.try_call.first).to eq(401) - end - end - - context 'when user does not have push access' do - it "responds with status 401" do - expect(lfs_router_public_noauth.try_call.first).to eq(401) - end - end - end - - context 'when CI is authorized' do - let(:auth) { 'gitlab-ci-token:password' } - - before do - env["HTTP_AUTHORIZATION"] = auth - end - - it "responds with status 403" do - expect(lfs_router_public_ci_auth.try_call.first).to eq(401) - end - end - end - - describe 'unsupported' do - before do - body = { 'operation' => 'other', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - it 'responds with status 404' do - expect(lfs_router_public_noauth.try_call.first).to eq(404) - end - end - end - - describe 'when pushing a lfs object' do - before do - enable_lfs - env['REQUEST_METHOD'] = 'PUT' - end - - shared_examples 'unauthorized' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(router.project) - end - - it 'responds with status 401' do - expect(router.try_call.first).to eq(401) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(router.project) - end - - it 'responds with status 401' do - expect(router.try_call.first).to eq(401) - end - end - - context 'and request is sent with a malformed headers' do - before do - env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" - env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" - end - - it 'does not recognize it as a valid lfs command' do - expect(router.try_call).to eq(nil) - end - end - end - - shared_examples 'forbidden' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(router.project) - end - - it 'responds with 403' do - expect(router.try_call.first).to eq(403) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(router.project) - end - - it 'responds with 403' do - expect(router.try_call.first).to eq(403) - end - end - end - - describe 'to one project' do - describe 'when user is authenticated' do - describe 'when user has push access to the project' do - before do - project.team << [user, :developer] - end - - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(project) - end - - it 'responds with status 200, location of lfs store and object details' do - json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) - - expect(lfs_router_auth.try_call.first).to eq(200) - expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") - expect(json_response['LfsOid']).to eq(sample_oid) - expect(json_response['LfsSize']).to eq(sample_size) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(project) - end - - it 'responds with status 200 and lfs object is linked to the project' do - expect(lfs_router_auth.try_call.first).to eq(200) - expect(lfs_object.projects.pluck(:id)).to include(project.id) - end - end - end - - describe 'and user does not have push access' do - let(:router) { lfs_router_auth } - - it_behaves_like 'forbidden' - end - end - - context 'when CI is authenticated' do - let(:router) { lfs_router_ci_auth } - - it_behaves_like 'unauthorized' - end - - context 'for unauthenticated' do - let(:router) { new_lfs_router(project) } - - it_behaves_like 'unauthorized' - end - end - - describe 'to a forked project' do - let(:forked_project) { fork_project(public_project, user) } - - describe 'when user is authenticated' do - describe 'when user has push access to the project' do - before do - forked_project.team << [user_two, :developer] - end - - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(forked_project) - end - - it 'responds with status 200, location of lfs store and object details' do - json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) - - expect(lfs_router_forked_auth.try_call.first).to eq(200) - expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") - expect(json_response['LfsOid']).to eq(sample_oid) - expect(json_response['LfsSize']).to eq(sample_size) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(forked_project) - end - - it 'responds with status 200 and lfs object is linked to the source project' do - expect(lfs_router_forked_auth.try_call.first).to eq(200) - expect(lfs_object.projects.pluck(:id)).to include(public_project.id) - end - end - end - - describe 'and user does not have push access' do - let(:router) { lfs_router_forked_auth } - - it_behaves_like 'forbidden' - end - end - - context 'when CI is authenticated' do - let(:router) { lfs_router_forked_ci_auth } - - it_behaves_like 'unauthorized' - end - - context 'for unauthenticated' do - let(:router) { lfs_router_forked_noauth } - - it_behaves_like 'unauthorized' - end - - describe 'and second project not related to fork or a source project' do - let(:second_project) { create(:project) } - let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) } - - before do - public_project.lfs_objects << lfs_object - headers_for_upload_finalize(second_project) - end - - context 'when pushing the same lfs object to the second project' do - before do - second_project.team << [user, :master] - end - - it 'responds with 200 and links the lfs object to the project' do - expect(lfs_router_second_project.try_call.first).to eq(200) - expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id) - end - end - end - end - end - - def enable_lfs - allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) - end - - def authorize(user) - ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) - end - - def new_lfs_router(project, user: nil, ci: false) - Gitlab::Lfs::Router.new(project, user, ci, request) - end - - def header_for_upload_authorize(project) - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize" - end - - def headers_for_upload_finalize(project) - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" - env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4" - end - - def fork_project(project, user, object = nil) - allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) - Projects::ForkService.new(project, user, {}).execute - end -end diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb new file mode 100644 index 00000000000..aa9ec243498 --- /dev/null +++ b/spec/lib/gitlab/user_access_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe Gitlab::UserAccess, lib: true do + let(:access) { Gitlab::UserAccess.new(user, project: project) } + let(:project) { create(:project) } + let(:user) { create(:user) } + + describe 'can_push_to_branch?' do + describe 'push to none protected branch' do + it 'returns true if user is a master' do + project.team << [user, :master] + expect(access.can_push_to_branch?('random_branch')).to be_truthy + end + + it 'returns true if user is a developer' do + project.team << [user, :developer] + expect(access.can_push_to_branch?('random_branch')).to be_truthy + end + + it 'returns false if user is a reporter' do + project.team << [user, :reporter] + expect(access.can_push_to_branch?('random_branch')).to be_falsey + end + end + + describe 'push to protected branch' do + let(:branch) { create :protected_branch, project: project } + + it 'returns true if user is a master' do + project.team << [user, :master] + expect(access.can_push_to_branch?(branch.name)).to be_truthy + end + + it 'returns false if user is a developer' do + project.team << [user, :developer] + expect(access.can_push_to_branch?(branch.name)).to be_falsey + end + + it 'returns false if user is a reporter' do + project.team << [user, :reporter] + expect(access.can_push_to_branch?(branch.name)).to be_falsey + end + end + + describe 'push to protected branch if allowed for developers' do + before do + @branch = create :protected_branch, project: project, developers_can_push: true + end + + it 'returns true if user is a master' do + project.team << [user, :master] + expect(access.can_push_to_branch?(@branch.name)).to be_truthy + end + + it 'returns true if user is a developer' do + project.team << [user, :developer] + expect(access.can_push_to_branch?(@branch.name)).to be_truthy + end + + it 'returns false if user is a reporter' do + project.team << [user, :reporter] + expect(access.can_push_to_branch?(@branch.name)).to be_falsey + end + end + + describe 'merge to protected branch if allowed for developers' do + before do + @branch = create :protected_branch, project: project, developers_can_merge: true + end + + it 'returns true if user is a master' do + project.team << [user, :master] + expect(access.can_merge_to_branch?(@branch.name)).to be_truthy + end + + it 'returns true if user is a developer' do + project.team << [user, :developer] + expect(access.can_merge_to_branch?(@branch.name)).to be_truthy + end + + it 'returns false if user is a reporter' do + project.team << [user, :reporter] + expect(access.can_merge_to_branch?(@branch.name)).to be_falsey + end + end + + end +end |