Commit 3a11c2fc authored by Rubén Dávila's avatar Rubén Dávila Committed by Ruben Davila

Merge branch 'es_fix_global_search' into 'security'

[ES] Fix internal data exposure

We need also specs for this

Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/1046

See merge request !504
parent 8a575a57
...@@ -147,9 +147,13 @@ module Elastic ...@@ -147,9 +147,13 @@ module Elastic
} }
end end
def project_ids_filter(query_hash, project_ids, public_and_internal_projects = true) def project_ids_filter(query_hash, options)
if project_ids if options[:project_ids]
condition = project_ids_condition(project_ids, public_and_internal_projects) condition = project_ids_condition(
options[:current_user],
options[:project_ids],
options[:public_and_internal_projects]
)
query_hash[:query][:bool][:filter] = { query_hash[:query][:bool][:filter] = {
has_parent: { has_parent: {
...@@ -166,19 +170,17 @@ module Elastic ...@@ -166,19 +170,17 @@ module Elastic
query_hash query_hash
end end
def project_ids_condition(project_ids, public_and_internal_projects) def project_ids_condition(current_user, project_ids, public_and_internal_projects)
conditions = [{ conditions = [{
terms: { id: project_ids } terms: { id: project_ids }
}] }]
if public_and_internal_projects if public_and_internal_projects
conditions << { conditions << { term: { visibility_level: Project::PUBLIC } }
term: { visibility_level: Project::PUBLIC }
}
conditions << { if current_user
term: { visibility_level: Project::INTERNAL } conditions << { term: { visibility_level: Project::INTERNAL } }
} end
end end
conditions conditions
......
...@@ -44,7 +44,7 @@ module Elastic ...@@ -44,7 +44,7 @@ module Elastic
query_hash = basic_query_hash(%w(title^2 description), query) query_hash = basic_query_hash(%w(title^2 description), query)
end end
query_hash = project_ids_filter(query_hash, options[:project_ids], options[:public_and_internal_projects]) query_hash = project_ids_filter(query_hash, options)
query_hash = confidentiality_filter(query_hash, options[:current_user]) query_hash = confidentiality_filter(query_hash, options[:current_user])
self.__elasticsearch__.search(query_hash) self.__elasticsearch__.search(query_hash)
......
...@@ -66,7 +66,7 @@ module Elastic ...@@ -66,7 +66,7 @@ module Elastic
query_hash = basic_query_hash(%w(title^2 description), query) query_hash = basic_query_hash(%w(title^2 description), query)
end end
query_hash = project_ids_filter(query_hash, options[:project_ids], options[:public_and_internal_projects]) query_hash = project_ids_filter(query_hash, options)
self.__elasticsearch__.search(query_hash) self.__elasticsearch__.search(query_hash)
end end
......
...@@ -31,7 +31,7 @@ module Elastic ...@@ -31,7 +31,7 @@ module Elastic
query_hash = basic_query_hash(options[:in], query) query_hash = basic_query_hash(options[:in], query)
query_hash = project_ids_filter(query_hash, options[:project_ids], options[:public_and_internal_projects]) query_hash = project_ids_filter(query_hash, options)
self.__elasticsearch__.search(query_hash) self.__elasticsearch__.search(query_hash)
end end
......
...@@ -60,7 +60,7 @@ module Elastic ...@@ -60,7 +60,7 @@ module Elastic
query_hash[:track_scores] = true query_hash[:track_scores] = true
end end
query_hash = project_ids_filter(query_hash, options[:project_ids], options[:public_and_internal_projects]) query_hash = project_ids_filter(query_hash, options)
query_hash = confidentiality_filter(query_hash, options[:current_user]) query_hash = confidentiality_filter(query_hash, options[:current_user])
query_hash[:sort] = [ query_hash[:sort] = [
......
...@@ -83,10 +83,10 @@ module Elastic ...@@ -83,10 +83,10 @@ module Elastic
} }
end end
if options[:pids] if options[:project_ids]
filters << { filters << {
bool: { bool: {
should: project_ids_condition(options[:pids], options[:public_and_internal_projects]) should: project_ids_condition(options[:current_user], options[:project_ids], options[:public_and_internal_projects])
} }
} }
end end
......
module Gitlab module Gitlab
module Elastic module Elastic
class SearchResults class SearchResults
attr_reader :current_user, :query attr_reader :current_user, :query, :public_and_internal_projects
# Limit search results by passed project ids # Limit search results by passed project ids
# It allows us to search only for projects user has access to # It allows us to search only for projects user has access to
...@@ -59,41 +59,28 @@ module Gitlab ...@@ -59,41 +59,28 @@ module Gitlab
private private
def projects def base_options
opt = { {
pids: limit_project_ids, current_user: current_user,
public_and_internal_projects: @public_and_internal_projects project_ids: limit_project_ids,
public_and_internal_projects: public_and_internal_projects
} }
end
@projects = Project.elastic_search(query, options: opt) def projects
Project.elastic_search(query, options: base_options)
end end
def issues def issues
opt = { Issue.elastic_search(query, options: base_options)
project_ids: limit_project_ids,
current_user: current_user,
public_and_internal_projects: @public_and_internal_projects
}
Issue.elastic_search(query, options: opt)
end end
def milestones def milestones
opt = { Milestone.elastic_search(query, options: base_options)
project_ids: limit_project_ids,
public_and_internal_projects: @public_and_internal_projects
}
Milestone.elastic_search(query, options: opt)
end end
def merge_requests def merge_requests
opt = { MergeRequest.elastic_search(query, options: base_options)
project_ids: limit_project_ids,
public_and_internal_projects: @public_and_internal_projects
}
MergeRequest.elastic_search(query, options: opt)
end end
def blobs def blobs
...@@ -101,7 +88,7 @@ module Gitlab ...@@ -101,7 +88,7 @@ module Gitlab
Kaminari.paginate_array([]) Kaminari.paginate_array([])
else else
opt = { opt = {
additional_filter: build_filter_by_project(limit_project_ids, @public_and_internal_projects) additional_filter: build_filter_by_project
} }
Repository.search( Repository.search(
...@@ -117,7 +104,7 @@ module Gitlab ...@@ -117,7 +104,7 @@ module Gitlab
Kaminari.paginate_array([]) Kaminari.paginate_array([])
else else
options = { options = {
additional_filter: build_filter_by_project(limit_project_ids, @public_and_internal_projects) additional_filter: build_filter_by_project
} }
Repository.find_commits_by_message_with_elastic( Repository.find_commits_by_message_with_elastic(
...@@ -129,17 +116,15 @@ module Gitlab ...@@ -129,17 +116,15 @@ module Gitlab
end end
end end
def build_filter_by_project(project_ids, public_and_internal_projects) def build_filter_by_project
conditions = [{ terms: { id: project_ids } }] conditions = [{ terms: { id: limit_project_ids } }]
if public_and_internal_projects if public_and_internal_projects
conditions << { conditions << { term: { visibility_level: Project::PUBLIC } }
term: { visibility_level: Project::PUBLIC }
}
conditions << { if current_user
term: { visibility_level: Project::INTERNAL } conditions << { term: { visibility_level: Project::INTERNAL } }
} end
end end
{ {
......
...@@ -374,7 +374,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -374,7 +374,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
end end
it 'founds blobs' do it 'finds blobs' do
results = described_class.new(user, 'def', limit_project_ids) results = described_class.new(user, 'def', limit_project_ids)
blobs = results.objects('blobs') blobs = results.objects('blobs')
...@@ -382,7 +382,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -382,7 +382,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
expect(results.blobs_count).to eq 4 expect(results.blobs_count).to eq 4
end end
it 'founds blobs from public projects only' do it 'finds blobs from public projects only' do
project_2 = create :project, :private project_2 = create :project, :private
project_2.repository.index_blobs project_2.repository.index_blobs
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
...@@ -408,7 +408,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -408,7 +408,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
end end
it 'founds commits' do it 'finds commits' do
results = described_class.new(user, 'add', limit_project_ids) results = described_class.new(user, 'add', limit_project_ids)
commits = results.objects('commits') commits = results.objects('commits')
...@@ -416,7 +416,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -416,7 +416,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
expect(results.commits_count).to eq 5 expect(results.commits_count).to eq 5
end end
it 'founds commits from public projects only' do it 'finds commits from public projects only' do
project_2 = create :project, :private project_2 = create :project, :private
project_2.repository.index_commits project_2.repository.index_commits
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
...@@ -434,4 +434,190 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -434,4 +434,190 @@ describe Gitlab::Elastic::SearchResults, lib: true do
expect(results.commits_count).to eq 0 expect(results.commits_count).to eq 0
end end
end end
describe 'Visibility levels' do
let(:internal_project) { create(:project, :internal, description: "Internal project") }
let(:private_project1) { create(:project, :private, description: "Private project") }
let(:private_project2) { create(:project, :private, description: "Private project where I'm a member") }
let(:public_project) { create(:project, :public, description: "Public project") }
let(:limit_project_ids) { [private_project2.id] }
before do
private_project2.project_members.create(user: user, access_level: ProjectMember::DEVELOPER)
end
context 'Issues' do
it 'finds right set of issues' do
issue_1 = create :issue, project: internal_project, title: "Internal project"
create :issue, project: private_project1, title: "Private project"
issue_3 = create :issue, project: private_project2, title: "Private project where I'm a member"
issue_4 = create :issue, project: public_project, title: "Public project"
Gitlab::Elastic::Helper.refresh_index
# Authenticated search
results = described_class.new(user, 'project', limit_project_ids)
issues = results.objects('issues')
expect(issues).to include issue_1
expect(issues).to include issue_3
expect(issues).to include issue_4
expect(results.issues_count).to eq 3
# Unauthenticated search
results = described_class.new(nil, 'project', [])
issues = results.objects('issues')
expect(issues).to include issue_4
expect(results.issues_count).to eq 1
end
end
context 'Milestones' do
it 'finds right set of milestine' do
milestone_1 = create :milestone, project: internal_project, title: "Internal project"
create :milestone, project: private_project1, title: "Private project"
milestone_3 = create :milestone, project: private_project2, title: "Private project where I'm a member"
milestone_4 = create :milestone, project: public_project, title: "Public project"
Gitlab::Elastic::Helper.refresh_index
# Authenticated search
results = described_class.new(user, 'project', limit_project_ids)
milestones = results.objects('milestones')
expect(milestones).to include milestone_1
expect(milestones).to include milestone_3
expect(milestones).to include milestone_4
expect(results.milestones_count).to eq 3
# Unauthenticated search
results = described_class.new(nil, 'project', [])
milestones = results.objects('milestones')
expect(milestones).to include milestone_4
expect(results.milestones_count).to eq 1
end
end
context 'Projects' do
it 'finds right set of projects' do
internal_project
private_project1
private_project2
public_project
Gitlab::Elastic::Helper.refresh_index
# Authenticated search
results = described_class.new(user, 'project', limit_project_ids)
milestones = results.objects('projects')
expect(milestones).to include internal_project
expect(milestones).to include private_project2
expect(milestones).to include public_project
expect(results.projects_count).to eq 3
# Unauthenticated search
results = described_class.new(nil, 'project', [])
projects = results.objects('projects')
expect(projects).to include public_project
expect(results.projects_count).to eq 1
end
end
context 'Merge Requests' do
it 'finds right set of merge requests' do
merge_request_1 = create :merge_request, target_project: internal_project, source_project: internal_project, title: "Internal project"
create :merge_request, target_project: private_project1, source_project: private_project1, title: "Private project"
merge_request_3 = create :merge_request, target_project: private_project2, source_project: private_project2, title: "Private project where I'm a member"
merge_request_4 = create :merge_request, target_project: public_project, source_project: public_project, title: "Public project"
Gitlab::Elastic::Helper.refresh_index
# Authenticated search
results = described_class.new(user, 'project', limit_project_ids)
merge_requests = results.objects('merge_requests')
expect(merge_requests).to include merge_request_1
expect(merge_requests).to include merge_request_3
expect(merge_requests).to include merge_request_4
expect(results.merge_requests_count).to eq 3
# Unauthenticated search
results = described_class.new(nil, 'project', [])
merge_requests = results.objects('merge_requests')
expect(merge_requests).to include merge_request_4
expect(results.merge_requests_count).to eq 1
end
end
context 'Commits' do
it 'finds right set of commits' do
[internal_project, private_project1, private_project2, public_project].each do |project|
project.repository.commit_file(
user,
'test-file',
'search test',
'search test',
'master',
false
)
project.repository.index_commits
end
Gitlab::Elastic::Helper.refresh_index
# Authenticated search
results = described_class.new(user, 'search', limit_project_ids)
commits = results.objects('commits')
expect(commits.map(&:project)).to match_array [internal_project, private_project2, public_project]
expect(results.commits_count).to eq 3
# Unauthenticated search
results = described_class.new(nil, 'search', [])
commits = results.objects('commits')
expect(commits.first.project).to eq public_project
expect(results.commits_count).to eq 1
end
end
context 'Blobs' do
it 'finds right set of blobs' do
[internal_project, private_project1, private_project2, public_project].each do |project|
project.repository.commit_file(
user,
'test-file',
'tesla',
'search test',
'master',
false
)
project.repository.index_blobs
end
Gitlab::Elastic::Helper.refresh_index
# Authenticated search
results = described_class.new(user, 'tesla', limit_project_ids)
blobs = results.objects('blobs')
expect(blobs.map{ |blob| blob._parent.to_i }).to match_array [internal_project.id, private_project2.id, public_project.id]
expect(results.blobs_count).to eq 3
# Unauthenticated search
results = described_class.new(nil, 'tesla', [])
blobs = results.objects('blobs')
expect(blobs.first._parent.to_i).to eq public_project.id.to_i
expect(results.blobs_count).to eq 1
end
end
end
end end
...@@ -24,9 +24,9 @@ describe Project, elastic: true do ...@@ -24,9 +24,9 @@ describe Project, elastic: true do
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
end end
expect(described_class.elastic_search('test', options: { pids: project_ids }).total_count).to eq(1) expect(described_class.elastic_search('test', options: { project_ids: project_ids }).total_count).to eq(1)
expect(described_class.elastic_search('test1', options: { pids: project_ids }).total_count).to eq(1) expect(described_class.elastic_search('test1', options: { project_ids: project_ids }).total_count).to eq(1)
expect(described_class.elastic_search('someone_elses_project', options: { pids: project_ids }).total_count).to eq(0) expect(described_class.elastic_search('someone_elses_project', options: { project_ids: project_ids }).total_count).to eq(0)
end end
it "finds partial matches in project names" do it "finds partial matches in project names" do
...@@ -40,7 +40,7 @@ describe Project, elastic: true do ...@@ -40,7 +40,7 @@ describe Project, elastic: true do
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
end end
expect(described_class.elastic_search('tesla', options: { pids: project_ids }).total_count).to eq(2) expect(described_class.elastic_search('tesla', options: { project_ids: project_ids }).total_count).to eq(2)
end end
it "returns json with all needed elements" do it "returns json with all needed elements" do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment