gitlab-org--gitlab-foss/spec/lib/gitlab/project_search_results_spec.rb
Sean McGivern 82f4564fb7 Fix project search results for digits surrounded by colons
A file containing /:\d+:/ in its contents would break the search results if
those contents were part of the results, because we were splitting on colons,
which can't work with untrusted input.

Changing to use the null byte as a separator is much safer.
2018-01-16 11:56:07 +00:00

395 lines
13 KiB
Ruby

require 'spec_helper'
describe Gitlab::ProjectSearchResults do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:query) { 'hello world' }
describe 'initialize with empty ref' do
let(:results) { described_class.new(user, project, query, '') }
it { expect(results.project).to eq(project) }
it { expect(results.query).to eq('hello world') }
end
describe 'initialize with ref' do
let(:ref) { 'refs/heads/test' }
let(:results) { described_class.new(user, project, query, ref) }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to eq(ref) }
it { expect(results.query).to eq('hello world') }
end
describe 'blob search' do
let(:project) { create(:project, :public, :repository) }
subject(:results) { described_class.new(user, project, 'files').objects('blobs') }
context 'when repository is disabled' do
let(:project) { create(:project, :public, :repository, :repository_disabled) }
it 'hides blobs from members' do
project.add_reporter(user)
is_expected.to be_empty
end
it 'hides blobs from non-members' do
is_expected.to be_empty
end
end
context 'when repository is internal' do
let(:project) { create(:project, :public, :repository, :repository_private) }
it 'finds blobs for members' do
project.add_reporter(user)
is_expected.not_to be_empty
end
it 'hides blobs from non-members' do
is_expected.to be_empty
end
end
it 'finds by name' do
expect(results.map(&:first)).to include('files/images/wm.svg')
end
it 'finds by content' do
blob = results.select { |result| result.first == "CHANGELOG" }.flatten.last
expect(blob.filename).to eq("CHANGELOG")
end
describe 'parsing results' do
let(:results) { project.repository.search_files_by_content('feature', 'master') }
let(:search_result) { results.first }
subject { described_class.parse_search_result(search_result) }
it "returns a valid FoundBlob" do
is_expected.to be_an Gitlab::SearchResults::FoundBlob
expect(subject.id).to be_nil
expect(subject.path).to eq('CHANGELOG')
expect(subject.filename).to eq('CHANGELOG')
expect(subject.basename).to eq('CHANGELOG')
expect(subject.ref).to eq('master')
expect(subject.startline).to eq(188)
expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n")
end
context 'when the matching filename contains a colon' do
let(:search_result) { "\nmaster:testdata/project::function1.yaml\x001\x00---\n" }
it 'returns a valid FoundBlob' do
expect(subject.filename).to eq('testdata/project::function1.yaml')
expect(subject.basename).to eq('testdata/project::function1')
expect(subject.ref).to eq('master')
expect(subject.startline).to eq(1)
expect(subject.data).to eq('---')
end
end
context 'when the matching content contains a number surrounded by colons' do
let(:search_result) { "\nmaster:testdata/foo.txt\x001\x00blah:9:blah" }
it 'returns a valid FoundBlob' do
expect(subject.filename).to eq('testdata/foo.txt')
expect(subject.basename).to eq('testdata/foo')
expect(subject.ref).to eq('master')
expect(subject.startline).to eq(1)
expect(subject.data).to eq('blah:9:blah')
end
end
context "when filename has extension" do
let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" }
it { expect(subject.path).to eq('CONTRIBUTE.md') }
it { expect(subject.filename).to eq('CONTRIBUTE.md') }
it { expect(subject.basename).to eq('CONTRIBUTE') }
end
context "when file under directory" do
let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" }
it { expect(subject.path).to eq('a/b/c.md') }
it { expect(subject.filename).to eq('a/b/c.md') }
it { expect(subject.basename).to eq('a/b/c') }
end
end
end
describe 'wiki search' do
let(:project) { create(:project, :public) }
let(:wiki) { build(:project_wiki, project: project) }
let!(:wiki_page) { wiki.create_page('Title', 'Content') }
subject(:results) { described_class.new(user, project, 'Content').objects('wiki_blobs') }
context 'when wiki is disabled' do
let(:project) { create(:project, :public, :wiki_disabled) }
it 'hides wiki blobs from members' do
project.add_reporter(user)
is_expected.to be_empty
end
it 'hides wiki blobs from non-members' do
is_expected.to be_empty
end
end
context 'when wiki is internal' do
let(:project) { create(:project, :public, :wiki_private) }
it 'finds wiki blobs for guest' do
project.add_guest(user)
is_expected.not_to be_empty
end
it 'hides wiki blobs from non-members' do
is_expected.to be_empty
end
end
it 'finds by content' do
expect(results).to include("master:Title.md\x001\x00Content\n")
end
end
it 'does not list issues on private projects' do
issue = create(:issue, project: project)
results = described_class.new(user, project, issue.title)
expect(results.objects('issues')).not_to include issue
end
describe 'confidential issues' do
let(:query) { 'issue' }
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
let(:member) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, :internal) }
let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignees: [assignee]) }
it 'does not list project confidential issues for non project members' do
results = described_class.new(non_member, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).not_to include security_issue_2
expect(results.issues_count).to eq 1
end
it 'does not list project confidential issues for project members with guest role' do
project.add_guest(member)
results = described_class.new(member, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).not_to include security_issue_2
expect(results.issues_count).to eq 1
end
it 'lists project confidential issues for author' do
results = described_class.new(author, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).not_to include security_issue_2
expect(results.issues_count).to eq 2
end
it 'lists project confidential issues for assignee' do
results = described_class.new(assignee, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).to include security_issue_2
expect(results.issues_count).to eq 2
end
it 'lists project confidential issues for project members' do
project.add_developer(member)
results = described_class.new(member, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).to include security_issue_2
expect(results.issues_count).to eq 3
end
it 'lists all project issues for admin' do
results = described_class.new(admin, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).to include security_issue_2
expect(results.issues_count).to eq 3
end
end
describe 'notes search' do
it 'lists notes' do
project = create(:project, :public)
note = create(:note, project: project)
results = described_class.new(user, project, note.note)
expect(results.objects('notes')).to include note
end
it "doesn't list issue notes when access is restricted" do
project = create(:project, :public, :issues_private)
note = create(:note_on_issue, project: project)
results = described_class.new(user, project, note.note)
expect(results.objects('notes')).not_to include note
end
it "doesn't list merge_request notes when access is restricted" do
project = create(:project, :public, :merge_requests_private)
note = create(:note_on_merge_request, project: project)
results = described_class.new(user, project, note.note)
expect(results.objects('notes')).not_to include note
end
end
# Examples for commit access level test
#
# params:
# * search_phrase
# * commit
#
shared_examples 'access restricted commits' do
context 'when project is internal' do
let(:project) { create(:project, :internal, :repository) }
it 'does not search if user is not authenticated' do
commits = described_class.new(nil, project, search_phrase).objects('commits')
expect(commits).to be_empty
end
it 'searches if user is authenticated' do
commits = described_class.new(user, project, search_phrase).objects('commits')
expect(commits).to contain_exactly commit
end
end
context 'when project is private' do
let!(:creator) { create(:user, username: 'private-project-author') }
let!(:private_project) { create(:project, :private, :repository, creator: creator, namespace: creator.namespace) }
let(:team_master) do
user = create(:user, username: 'private-project-master')
private_project.add_master(user)
user
end
let(:team_reporter) do
user = create(:user, username: 'private-project-reporter')
private_project.add_reporter(user)
user
end
it 'does not show commit to stranger' do
commits = described_class.new(nil, private_project, search_phrase).objects('commits')
expect(commits).to be_empty
end
context 'team access' do
it 'shows commit to creator' do
commits = described_class.new(creator, private_project, search_phrase).objects('commits')
expect(commits).to contain_exactly commit
end
it 'shows commit to master' do
commits = described_class.new(team_master, private_project, search_phrase).objects('commits')
expect(commits).to contain_exactly commit
end
it 'shows commit to reporter' do
commits = described_class.new(team_reporter, private_project, search_phrase).objects('commits')
expect(commits).to contain_exactly commit
end
end
end
end
describe 'commit search' do
context 'by commit message' do
let(:project) { create(:project, :public, :repository) }
let(:commit) { project.repository.commit('59e29889be61e6e0e5e223bfa9ac2721d31605b8') }
let(:message) { 'Sorry, I did a mistake' }
it 'finds commit by message' do
commits = described_class.new(user, project, message).objects('commits')
expect(commits).to contain_exactly commit
end
it 'handles when no commit match' do
commits = described_class.new(user, project, 'not really an existing description').objects('commits')
expect(commits).to be_empty
end
it_behaves_like 'access restricted commits' do
let(:search_phrase) { message }
let(:commit) { project.repository.commit('59e29889be61e6e0e5e223bfa9ac2721d31605b8') }
end
end
context 'by commit hash' do
let(:project) { create(:project, :public, :repository) }
let(:commit) { project.repository.commit('0b4bc9a') }
commit_hashes = { short: '0b4bc9a', full: '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
commit_hashes.each do |type, commit_hash|
it "shows commit by #{type} hash id" do
commits = described_class.new(user, project, commit_hash).objects('commits')
expect(commits).to contain_exactly commit
end
end
it 'handles not existing commit hash correctly' do
commits = described_class.new(user, project, 'deadbeef').objects('commits')
expect(commits).to be_empty
end
it_behaves_like 'access restricted commits' do
let(:search_phrase) { '0b4bc9a49' }
let(:commit) { project.repository.commit('0b4bc9a') }
end
end
end
end