Commit 95296bae authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '36439-redact-2' into 'master'

Wrap Elasticsearch results as blob

See merge request gitlab-org/gitlab!22676
parents 26265cf1 5da6794d
...@@ -86,19 +86,6 @@ module SearchHelper ...@@ -86,19 +86,6 @@ module SearchHelper
}).html_safe }).html_safe
end end
def find_project_for_result_blob(projects, result)
@project
end
# Used in EE
def blob_projects(results)
nil
end
def parse_search_result(result)
result
end
# Overriden in EE # Overriden in EE
def search_blob_title(project, path) def search_blob_title(project, path)
path path
......
...@@ -32,8 +32,7 @@ ...@@ -32,8 +32,7 @@
.term .term
= render 'shared/projects/list', projects: @search_objects, pipeline_status: false = render 'shared/projects/list', projects: @search_objects, pipeline_status: false
- else - else
- locals = { projects: blob_projects(@search_objects) } if %w[blobs wiki_blobs].include?(@scope) = render partial: "search/results/#{@scope.singularize}", collection: @search_objects
= render partial: "search/results/#{@scope.singularize}", collection: @search_objects, locals: locals
- if @scope != 'projects' - if @scope != 'projects'
= paginate_collection(@search_objects) = paginate_collection(@search_objects)
- project = find_project_for_result_blob(projects, blob) - project = blob.project
- return unless project - return unless project
- blob = parse_search_result(blob)
- blob_link = project_blob_path(project, tree_join(blob.ref, blob.path)) - blob_link = project_blob_path(project, tree_join(blob.ref, blob.path))
= render partial: 'search/results/blob_data', locals: { blob: blob, project: project, path: blob.path, blob_link: blob_link } = render partial: 'search/results/blob_data', locals: { blob: blob, project: project, path: blob.path, blob_link: blob_link }
- project = find_project_for_result_blob(projects, wiki_blob) - project = wiki_blob.project
- wiki_blob = parse_search_result(wiki_blob)
- wiki_blob_link = project_wiki_path(project, wiki_blob.basename) - wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, path: wiki_blob.path, blob_link: wiki_blob_link } = render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, path: wiki_blob.path, blob_link: wiki_blob_link }
...@@ -13,29 +13,6 @@ module EE ...@@ -13,29 +13,6 @@ module EE
options options
end end
override :find_project_for_result_blob
def find_project_for_result_blob(projects, result)
return super if result.is_a?(::Gitlab::Search::FoundBlob)
super || projects&.find { |project| project.id == blob_project_id(result) }
end
override :blob_projects
def blob_projects(results)
return super if results.first.is_a?(::Gitlab::Search::FoundBlob)
project_ids = results.map(&method(:blob_project_id))
::ProjectsFinder.new(current_user: current_user, project_ids_relation: project_ids).execute
end
override :parse_search_result
def parse_search_result(result)
return super if result.is_a?(::Gitlab::Search::FoundBlob)
::Gitlab::Elastic::SearchResults.parse_search_result(result)
end
override :search_blob_title override :search_blob_title
def search_blob_title(project, path) def search_blob_title(project, path)
if @project if @project
...@@ -88,10 +65,6 @@ module EE ...@@ -88,10 +65,6 @@ module EE
context.feature_available?(:multiple_issue_assignees)) context.feature_available?(:multiple_issue_assignees))
end end
def blob_project_id(blob_result)
blob_result.dig('_source', 'join_field', 'parent')&.split('_')&.last.to_i
end
def gitlab_com_snippet_db_search? def gitlab_com_snippet_db_search?
@current_user && @current_user &&
@show_snippets && @show_snippets &&
......
...@@ -7,8 +7,7 @@ module EE ...@@ -7,8 +7,7 @@ module EE
# doesn't seem to be a common ancestor to check. # doesn't seem to be a common ancestor to check.
REDACTABLE_RESULTS = [ REDACTABLE_RESULTS = [
Kaminari::PaginatableArray, Kaminari::PaginatableArray,
Elasticsearch::Model::Response::Records, Elasticsearch::Model::Response::Records
Elasticsearch::Model::Response::Response
].freeze ].freeze
# This is a proper method instead of a `delegate` in order to # This is a proper method instead of a `delegate` in order to
......
...@@ -21,17 +21,6 @@ module EE ...@@ -21,17 +21,6 @@ module EE
def use_elasticsearch?(resource) def use_elasticsearch?(resource)
::Gitlab::CurrentSettings.search_using_elasticsearch?(scope: resource) ::Gitlab::CurrentSettings.search_using_elasticsearch?(scope: resource)
end end
override :process_results
def process_results(results)
return [] if results.empty?
if results.any? { |result| result.is_a?(::Elasticsearch::Model::Response::Result) && result.respond_to?(:blob) }
return paginate(results).map { |blob| ::Gitlab::Elastic::SearchResults.parse_search_result(blob) }
end
super
end
end end
end end
end end
......
...@@ -20,6 +20,16 @@ module Elastic ...@@ -20,6 +20,16 @@ module Elastic
results results
end end
# @return [Kaminari::PaginatableArray]
def elastic_search_as_found_blob(query, page: 1, per: 20, options: {})
# Highlight is required for parse_search_result to locate relevant line
options = options.merge(highlight: true)
elastic_search_and_wrap(query, type: es_type, page: page, per: per, options: options) do |result, project|
::Gitlab::Elastic::SearchResults.parse_search_result(result, project)
end
end
private private
def extract_repository_ids(options) def extract_repository_ids(options)
...@@ -156,7 +166,7 @@ module Elastic ...@@ -156,7 +166,7 @@ module Elastic
} }
end end
options[:project_ids] = repository_ids.map { |id| id.to_s[/\d+/].to_i } if type == :wiki_blob && repository_ids.any? options[:project_ids] = repository_ids.map { |id| id.to_s[/\d+/].to_i } if type.to_sym == :wiki_blob && repository_ids.any?
res = search(query_hash, options) res = search(query_hash, options)
...@@ -165,6 +175,56 @@ module Elastic ...@@ -165,6 +175,56 @@ module Elastic
total_count: res.size total_count: res.size
} }
end end
# Wrap returned results into GitLab model objects and paginate
#
# @return [Kaminari::PaginatableArray]
def elastic_search_and_wrap(query, type:, page: 1, per: 20, options: {}, &blk)
type = type.to_s
response = elastic_search(
query,
type: type,
page: page,
per: per,
options: options
)[type.pluralize.to_sym][:results]
items, total_count = yield_each_search_result(response, type, &blk)
# Before "map" we had a paginated array so we need to recover it
offset = per * ((page || 1) - 1)
Kaminari.paginate_array(items, total_count: total_count, limit: per, offset: offset)
end
def yield_each_search_result(response, type)
# Avoid one SELECT per result by loading all projects into a hash
project_ids = response.map { |result| project_id_for_commit_or_blob(result, type) }.uniq
projects = Project.with_route.id_in(project_ids).index_by(&:id)
total_count = response.total_count
items = response.map do |result|
project_id = project_id_for_commit_or_blob(result, type)
project = projects[project_id]
if project.nil? || project.pending_delete?
total_count -= 1
next
end
yield(result, project)
end
# Remove results for deleted projects
items.compact!
[items, total_count]
end
# Indexed commit does not include project_id
def project_id_for_commit_or_blob(result, type)
result.dig('_source', 'project_id') || result.dig('_source', type, 'rid').to_i
end
end end
end end
end end
...@@ -16,10 +16,18 @@ module Elastic ...@@ -16,10 +16,18 @@ module Elastic
end end
def elastic_search(query, type: :all, page: 1, per: 20, options: {}) def elastic_search(query, type: :all, page: 1, per: 20, options: {})
options[:repository_id] = repository_id if options[:repository_id].nil? options = repository_specific_options(options)
self.class.elastic_search(query, type: type, page: page, per: per, options: options) self.class.elastic_search(query, type: type, page: page, per: per, options: options)
end end
# @return [Kaminari::PaginatableArray]
def elastic_search_as_found_blob(query, page: 1, per: 20, options: {})
options = repository_specific_options(options)
self.class.elastic_search_as_found_blob(query, page: page, per: per, options: options)
end
def delete_index_for_commits_and_blobs(wiki: false) def delete_index_for_commits_and_blobs(wiki: false)
types = types =
if wiki if wiki
...@@ -62,6 +70,14 @@ module Elastic ...@@ -62,6 +70,14 @@ module Elastic
def repository_id def repository_id
raise NotImplementedError raise NotImplementedError
end end
def repository_specific_options(options)
if options[:repository_id].nil?
options = options.merge(repository_id: repository_id)
end
options
end
end end
end end
end end
...@@ -8,6 +8,12 @@ module Elastic ...@@ -8,6 +8,12 @@ module Elastic
def es_type def es_type
'wiki_blob' 'wiki_blob'
end end
def elastic_search_as_wiki_page(*args)
elastic_search_as_found_blob(*args).map! do |blob|
Gitlab::Search::FoundWikiPage.new(blob)
end
end
end end
end end
end end
...@@ -8,6 +8,13 @@ module Elastic ...@@ -8,6 +8,13 @@ module Elastic
delegate :project, to: :target delegate :project, to: :target
delegate :id, to: :project, prefix: true delegate :id, to: :project, prefix: true
# @return [Kaminari::PaginatableArray]
def elastic_search_as_wiki_page(query, page: 1, per: 20, options: {})
options = repository_specific_options(options)
self.class.elastic_search_as_wiki_page(query, page: page, per: per, options: options)
end
private private
def repository_id def repository_id
......
...@@ -9,30 +9,9 @@ module Elastic ...@@ -9,30 +9,9 @@ module Elastic
'blob' 'blob'
end end
# @return [Kaminari::PaginatableArray]
def find_commits_by_message_with_elastic(query, page: 1, per_page: 20, options: {}) def find_commits_by_message_with_elastic(query, page: 1, per_page: 20, options: {})
response = elastic_search( elastic_search_and_wrap(query, type: :commit, page: page, per: per_page, options: options) do |result, project|
query,
type: :commit,
page: page,
per: per_page,
options: options
)[:commits][:results]
response_count = response.total_count
# Avoid one SELECT per result by loading all projects into a hash
project_ids = response.map {|result| result["_source"]["commit"]["rid"] }.uniq
projects = Project.with_route.id_in(project_ids).index_by(&:id)
commits = response.map do |result|
project_id = result["_source"]["commit"]["rid"].to_i
project = projects[project_id]
if project.nil? || project.pending_delete?
response_count -= 1
next
end
raw_commit = Gitlab::Git::Commit.new( raw_commit = Gitlab::Git::Commit.new(
project.repository.raw, project.repository.raw,
prepare_commit(result['_source']['commit']), prepare_commit(result['_source']['commit']),
...@@ -40,13 +19,6 @@ module Elastic ...@@ -40,13 +19,6 @@ module Elastic
) )
Commit.new(raw_commit, project) Commit.new(raw_commit, project)
end end
# Remove results for deleted projects
commits.compact!
# Before "map" we had a paginated array so we need to recover it
offset = per_page * ((page || 1) - 1)
Kaminari.paginate_array(commits, total_count: response_count, limit: per_page, offset: offset)
end end
private private
......
...@@ -24,9 +24,9 @@ module Gitlab ...@@ -24,9 +24,9 @@ module Gitlab
when 'notes' when 'notes'
notes.page(page).per(per_page).records notes.page(page).per(per_page).records
when 'blobs' when 'blobs'
blobs.page(page).per(per_page) blobs(page: page, per_page: per_page)
when 'wiki_blobs' when 'wiki_blobs'
wiki_blobs.page(page).per(per_page) wiki_blobs(page: page, per_page: per_page)
when 'commits' when 'commits'
commits(page: page, per_page: per_page) commits(page: page, per_page: per_page)
when 'users' when 'users'
...@@ -42,7 +42,7 @@ module Gitlab ...@@ -42,7 +42,7 @@ module Gitlab
private private
def blobs def blobs(page: 1, per_page: 20)
return Kaminari.paginate_array([]) unless Ability.allowed?(@current_user, :download_code, project) return Kaminari.paginate_array([]) unless Ability.allowed?(@current_user, :download_code, project)
if project.empty_repo? || query.blank? if project.empty_repo? || query.blank?
...@@ -50,11 +50,11 @@ module Gitlab ...@@ -50,11 +50,11 @@ module Gitlab
else else
# We use elastic for default branch only # We use elastic for default branch only
if root_ref? if root_ref?
project.repository.elastic_search( project.repository.__elasticsearch__.elastic_search_as_found_blob(
query, query,
type: :blob, page: (page || 1).to_i,
options: { highlight: true } per: per_page
)[:blobs][:results].response )
else else
Kaminari.paginate_array( Kaminari.paginate_array(
Gitlab::FileFinder.new(project, repository_ref).find(query) Gitlab::FileFinder.new(project, repository_ref).find(query)
...@@ -63,15 +63,15 @@ module Gitlab ...@@ -63,15 +63,15 @@ module Gitlab
end end
end end
def wiki_blobs def wiki_blobs(page: 1, per_page: 20)
return Kaminari.paginate_array([]) unless Ability.allowed?(@current_user, :read_wiki, project) return Kaminari.paginate_array([]) unless Ability.allowed?(@current_user, :read_wiki, project)
if project.wiki_enabled? && !project.wiki.empty? && query.present? if project.wiki_enabled? && !project.wiki.empty? && query.present?
project.wiki.elastic_search( project.wiki.__elasticsearch__.elastic_search_as_wiki_page(
query, query,
type: :wiki_blob, page: (page || 1).to_i,
options: { highlight: true } per: per_page
)[:wiki_blobs][:results].response )
else else
Kaminari.paginate_array([]) Kaminari.paginate_array([])
end end
......
...@@ -37,9 +37,9 @@ module Gitlab ...@@ -37,9 +37,9 @@ module Gitlab
when 'notes' when 'notes'
eager_load(notes, page, eager: { project: [:route, :namespace] }) eager_load(notes, page, eager: { project: [:route, :namespace] })
when 'blobs' when 'blobs'
blobs.page(page).per(per_page) blobs(page: page, per_page: per_page)
when 'wiki_blobs' when 'wiki_blobs'
wiki_blobs.page(page).per(per_page) wiki_blobs(page: page, per_page: per_page)
when 'commits' when 'commits'
commits(page: page, per_page: per_page) commits(page: page, per_page: per_page)
when 'users' when 'users'
...@@ -112,7 +112,7 @@ module Gitlab ...@@ -112,7 +112,7 @@ module Gitlab
false false
end end
def self.parse_search_result(result) def self.parse_search_result(result, project)
ref = result["_source"]["blob"]["commit_sha"] ref = result["_source"]["blob"]["commit_sha"]
path = result["_source"]["blob"]["path"] path = result["_source"]["blob"]["path"]
extname = File.extname(path) extname = File.extname(path)
...@@ -156,6 +156,7 @@ module Gitlab ...@@ -156,6 +156,7 @@ module Gitlab
ref: ref, ref: ref,
startline: from + 1, startline: from + 1,
data: data.join, data: data.join,
project: project,
project_id: project_id project_id: project_id
) )
end end
...@@ -225,7 +226,7 @@ module Gitlab ...@@ -225,7 +226,7 @@ module Gitlab
end end
end end
def blobs def blobs(page: 1, per_page: 20)
return Kaminari.paginate_array([]) if query.blank? return Kaminari.paginate_array([]) if query.blank?
strong_memoize(:blobs) do strong_memoize(:blobs) do
...@@ -233,15 +234,16 @@ module Gitlab ...@@ -233,15 +234,16 @@ module Gitlab
additional_filter: repository_filter(limit_project_ids) additional_filter: repository_filter(limit_project_ids)
) )
Repository.elastic_search( Repository.__elasticsearch__.elastic_search_as_found_blob(
query, query,
type: :blob, page: (page || 1).to_i,
options: options.merge({ highlight: true }) per: per_page,
)[:blobs][:results].response options: options
)
end end
end end
def wiki_blobs def wiki_blobs(page: 1, per_page: 20)
return Kaminari.paginate_array([]) if query.blank? return Kaminari.paginate_array([]) if query.blank?
strong_memoize(:wiki_blobs) do strong_memoize(:wiki_blobs) do
...@@ -249,11 +251,12 @@ module Gitlab ...@@ -249,11 +251,12 @@ module Gitlab
additional_filter: wiki_filter(limit_project_ids) additional_filter: wiki_filter(limit_project_ids)
) )
ProjectWiki.elastic_search( ProjectWiki.__elasticsearch__.elastic_search_as_wiki_page(
query, query,
type: :wiki_blob, page: (page || 1).to_i,
options: options.merge({ highlight: true }) per: per_page,
)[:wiki_blobs][:results].response options: options
)
end end
end end
......
...@@ -51,67 +51,6 @@ describe SearchHelper do ...@@ -51,67 +51,6 @@ describe SearchHelper do
end end
end end
describe '#parse_search_result with elastic enabled', :elastic do
let(:user) { create(:user) }
before do
allow(self).to receive(:current_user).and_return(user)
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
end
it "returns parsed result", :sidekiq_might_not_need_inline do
project = create :project, :repository
project.repository.index_commits_and_blobs
Gitlab::Elastic::Helper.refresh_index
result = project.repository.elastic_search(
'def popen',
type: :blob,
options: { highlight: true }
)[:blobs][:results][0]
parsed_result = helper.parse_search_result(result)
expect(parsed_result.ref). to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0')
expect(parsed_result.path).to eq('files/ruby/popen.rb')
expect(parsed_result.startline).to eq(2)
expect(parsed_result.data).to include("Popen")
end
end
describe '#blob_projects', :elastic do
let(:user) { create(:user) }
before do
allow(self).to receive(:current_user).and_return(user)
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
end
def es_blob_search
Repository.elastic_search(
'def popen',
type: :blob,
options: { highlight: true }
)[:blobs][:results]
end
it 'returns all projects in the result page without causing an N+1', :sidekiq_might_not_need_inline do
control_count = ActiveRecord::QueryRecorder.new { blob_projects(es_blob_search) }.count
projects = create_list :project, 3, :repository, :public
projects.each { |project| project.repository.index_commits_and_blobs }
Gitlab::Elastic::Helper.refresh_index
# So we can access it outside the following block
result_projects = nil
expect { result_projects = blob_projects(es_blob_search) }.not_to exceed_query_limit(control_count)
expect(result_projects).to match_array(projects)
end
end
describe '#search_entries_info_template' do describe '#search_entries_info_template' do
let(:com_value) { true } let(:com_value) { true }
let(:flag_enabled) { true } let(:flag_enabled) { true }
......
# frozen_string_literal: true
require 'spec_helper'
describe Elastic::Latest::GitClassProxy do
let_it_be(:project) { create(:project, :repository) }
let(:included_class) { Elastic::Latest::RepositoryClassProxy }
subject { included_class.new(project.repository) }
describe '#elastic_search_as_found_blob', :elastic do
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
Gitlab::Elastic::Indexer.new(project).run
Gitlab::Elastic::Helper.refresh_index
end
it 'returns FoundBlob', :sidekiq_inline do
results = subject.elastic_search_as_found_blob('def popen')
expect(results).not_to be_empty
expect(results).to all(be_a(Gitlab::Search::FoundBlob))
result = results.first
expect(result.ref).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0')
expect(result.path).to eq('files/ruby/popen.rb')
expect(result.startline).to eq(2)
expect(result.data).to include('Popen')
expect(result.project).to eq(project)
end
end
end
...@@ -51,6 +51,33 @@ describe Elastic::Latest::GitInstanceProxy do ...@@ -51,6 +51,33 @@ describe Elastic::Latest::GitInstanceProxy do
end end
end end
describe '#elastic_search_as_found_blob' do
let(:params) do
{
page: 2,
per: 30,
options: { foo: :bar }
}
end
it 'provides repository_id if not provided' do
expected_params = params.deep_dup
expected_params[:options][:repository_id] = project.id
expect(subject.class).to receive(:elastic_search_as_found_blob).with('foo', expected_params)
subject.elastic_search_as_found_blob('foo', params)
end
it 'uses provided repository_id' do
params[:options][:repository_id] = 42
expect(subject.class).to receive(:elastic_search_as_found_blob).with('foo', params)
subject.elastic_search_as_found_blob('foo', params)
end
end
describe '#delete_index_for_commits_and_blobs' do describe '#delete_index_for_commits_and_blobs' do
let(:write_targets) { [double(:write_target_1), double(:write_target_2)] } let(:write_targets) { [double(:write_target_1), double(:write_target_2)] }
let(:read_target) { double(:read_target) } let(:read_target) { double(:read_target) }
......
# frozen_string_literal: true
require 'spec_helper'
describe Elastic::Latest::ProjectWikiClassProxy do
let_it_be(:project) { create(:project, :wiki_repo) }
subject { described_class.new(project.wiki.repository) }
describe '#elastic_search_as_wiki_page', :elastic do
let_it_be(:page) { create(:wiki_page, wiki: project.wiki) }
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
Gitlab::Elastic::Indexer.new(project, wiki: true).run
Gitlab::Elastic::Helper.refresh_index
end
it 'returns FoundWikiPage', :sidekiq_inline do
results = subject.elastic_search_as_wiki_page('*')
expect(results.size).to eq(1)
expect(results).to all(be_a(Gitlab::Search::FoundWikiPage))
result = results.first
expect(result.path).to eq(page.path)
expect(result.startline).to eq(1)
expect(result.data).to include(page.content)
expect(result.project).to eq(project)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Elastic::Latest::ProjectWikiInstanceProxy do
let_it_be(:project) { create(:project, :wiki_repo) }
subject { described_class.new(project.wiki) }
describe '#elastic_search_as_wiki_page' do
let(:params) do
{
page: 2,
per: 30,
options: { foo: :bar }
}
end
it 'provides repository_id if not provided' do
expected_params = params.deep_dup
expected_params[:options][:repository_id] = "wiki_#{project.id}"
expect(subject.class).to receive(:elastic_search_as_wiki_page).with('foo', expected_params)
subject.elastic_search_as_wiki_page('foo', params)
end
it 'uses provided repository_id' do
params[:options][:repository_id] = "wiki_63"
expect(subject.class).to receive(:elastic_search_as_wiki_page).with('foo', params)
subject.elastic_search_as_wiki_page('foo', params)
end
end
end
...@@ -67,6 +67,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -67,6 +67,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
end end
describe 'parse_search_result' do describe 'parse_search_result' do
let(:project) { double(:project) }
let(:blob) do let(:blob) do
{ {
'blob' => { 'blob' => {
...@@ -78,11 +79,12 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -78,11 +79,12 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
end end
it 'returns an unhighlighted blob when no highlight data is present' do it 'returns an unhighlighted blob when no highlight data is present' do
parsed = described_class.parse_search_result('_source' => blob) parsed = described_class.parse_search_result({ '_source' => blob }, project)
expect(parsed).to be_kind_of(::Gitlab::Search::FoundBlob) expect(parsed).to be_kind_of(::Gitlab::Search::FoundBlob)
expect(parsed).to have_attributes( expect(parsed).to have_attributes(
startline: 1, startline: 1,
project: project,
data: "foo\n" data: "foo\n"
) )
end end
...@@ -95,7 +97,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -95,7 +97,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
} }
} }
parsed = described_class.parse_search_result(result) parsed = described_class.parse_search_result(result, project)
expect(parsed).to be_kind_of(::Gitlab::Search::FoundBlob) expect(parsed).to be_kind_of(::Gitlab::Search::FoundBlob)
expect(parsed).to have_attributes( expect(parsed).to have_attributes(
...@@ -104,6 +106,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -104,6 +106,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
basename: 'path/file', basename: 'path/file',
ref: 'sha', ref: 'sha',
startline: 2, startline: 2,
project: project,
data: "bar\n" data: "bar\n"
) )
end end
...@@ -519,11 +522,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -519,11 +522,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
end end
def search_for(term) def search_for(term)
blobs = described_class.new(user, term, [project_1.id]).objects('blobs') described_class.new(user, term, [project_1.id]).objects('blobs').map(&:path)
blobs.map do |blob|
blob['_source']['blob']['path']
end
end end
it_behaves_like 'a paginated object', 'blobs' it_behaves_like 'a paginated object', 'blobs'
...@@ -532,7 +531,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -532,7 +531,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
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')
expect(blobs.first['_source']['blob']['content']).to include('def') expect(blobs.first.data).to include('def')
expect(results.blobs_count).to eq 7 expect(results.blobs_count).to eq 7
end end
...@@ -544,7 +543,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -544,7 +543,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
results = described_class.new(user, 'def', [project_1.id]) results = described_class.new(user, 'def', [project_1.id])
expect(results.blobs_count).to eq 7 expect(results.blobs_count).to eq 7
result_project_ids = results.objects('blobs').map { |r| r.dig('_source', 'project_id') } result_project_ids = results.objects('blobs').map(&:project_id)
expect(result_project_ids.uniq).to eq([project_1.id]) expect(result_project_ids.uniq).to eq([project_1.id])
results = described_class.new(user, 'def', [project_1.id, project_2.id]) results = described_class.new(user, 'def', [project_1.id, project_2.id])
...@@ -656,7 +655,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -656,7 +655,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
it 'finds wiki blobs' do it 'finds wiki blobs' do
blobs = results.objects('wiki_blobs') blobs = results.objects('wiki_blobs')
expect(blobs.first['_source']['blob']['content']).to include("term") expect(blobs.first.data).to include('term')
expect(results.wiki_blobs_count).to eq 1 expect(results.wiki_blobs_count).to eq 1
end end
...@@ -664,7 +663,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -664,7 +663,7 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
project_1.add_guest(user) project_1.add_guest(user)
blobs = results.objects('wiki_blobs') blobs = results.objects('wiki_blobs')
expect(blobs.first['_source']['blob']['content']).to include("term") expect(blobs.first.data).to include('term')
expect(results.wiki_blobs_count).to eq 1 expect(results.wiki_blobs_count).to eq 1
end end
...@@ -989,14 +988,14 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -989,14 +988,14 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
results = described_class.new(user, 'term', limit_project_ids) results = described_class.new(user, 'term', limit_project_ids)
blobs = results.objects('wiki_blobs') blobs = results.objects('wiki_blobs')
expect(blobs.map { |blob| blob.join_field.parent }).to match_array [internal_project.es_id, private_project2.es_id, public_project.es_id] expect(blobs.map(&:project)).to match_array [internal_project, private_project2, public_project]
expect(results.wiki_blobs_count).to eq 3 expect(results.wiki_blobs_count).to eq 3
# Unauthenticated search # Unauthenticated search
results = described_class.new(nil, 'term', []) results = described_class.new(nil, 'term', [])
blobs = results.objects('wiki_blobs') blobs = results.objects('wiki_blobs')
expect(blobs.first.join_field.parent).to eq public_project.es_id expect(blobs.first.project).to eq public_project
expect(results.wiki_blobs_count).to eq 1 expect(results.wiki_blobs_count).to eq 1
end end
end end
...@@ -1053,14 +1052,14 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin ...@@ -1053,14 +1052,14 @@ describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need_inlin
results = described_class.new(user, 'tesla', limit_project_ids) results = described_class.new(user, 'tesla', limit_project_ids)
blobs = results.objects('blobs') blobs = results.objects('blobs')
expect(blobs.map { |blob| blob.join_field.parent }).to match_array [internal_project.es_id, private_project2.es_id, public_project.es_id] expect(blobs.map(&:project)).to match_array [internal_project, private_project2, public_project]
expect(results.blobs_count).to eq 3 expect(results.blobs_count).to eq 3
# Unauthenticated search # Unauthenticated search
results = described_class.new(nil, 'tesla', []) results = described_class.new(nil, 'tesla', [])
blobs = results.objects('blobs') blobs = results.objects('blobs')
expect(blobs.first.join_field.parent).to eq public_project.es_id expect(blobs.first.project).to eq public_project
expect(results.blobs_count).to eq 1 expect(results.blobs_count).to eq 1
end end
end end
......
...@@ -4,6 +4,77 @@ require 'spec_helper' ...@@ -4,6 +4,77 @@ require 'spec_helper'
describe SearchService do describe SearchService do
describe '#search_objects' do describe '#search_objects' do
context 'redacting search results (repository)', :elastic, :sidekiq_inline do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
subject(:search_service) { described_class.new(user, search: '*', scope: scope, page: 1) }
shared_examples 'it redacts incorrect results' do
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
Gitlab::Elastic::Indexer.new(project).run
Gitlab::Elastic::Helper.refresh_index
# disable permission to test redaction
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(user, ability, a_kind_of(model_class)).and_return(allowed)
end
context 'when allowed' do
let(:allowed) { true }
it 'does nothing' do
results = subject.search_objects
expect(results).not_to be_empty
expect(results).to all(be_an(model_class))
end
end
context 'when disallowed' do
let(:allowed) { false }
it 'redacts results' do
results = subject.search_objects
expect(results).to be_empty
end
end
end
context 'commits' do
let(:scope) { 'commits' }
let(:model_class) { Commit }
let(:ability) { :read_commit }
it_behaves_like 'it redacts incorrect results'
end
context 'blobs' do
let(:scope) { 'blobs' }
let(:model_class) { Gitlab::Search::FoundBlob }
let(:ability) { :read_blob }
it_behaves_like 'it redacts incorrect results'
end
context 'wiki blobs' do
let(:project) { create(:project, :wiki_repo) }
let(:scope) { 'wiki_blobs' }
let(:model_class) { Gitlab::Search::FoundWikiPage }
let(:ability) { :read_wiki_page }
it_behaves_like 'it redacts incorrect results' do
before do
create(:wiki_page, wiki: project.wiki)
Gitlab::Elastic::Indexer.new(project, wiki: true).run
Gitlab::Elastic::Helper.refresh_index
end
end
end
end
context 'redacting search results' do context 'redacting search results' do
let(:user) { create(:user) } let(:user) { create(:user) }
...@@ -21,8 +92,6 @@ describe SearchService do ...@@ -21,8 +92,6 @@ describe SearchService do
let(:note_on_unauthorized_issue) { create(:note, project: unauthorized_project, noteable: issue1_in_unauthorized_project) } let(:note_on_unauthorized_issue) { create(:note, project: unauthorized_project, noteable: issue1_in_unauthorized_project) }
let(:merge_request_in_unauthorized_project) { create(:merge_request_with_diffs, target_project: unauthorized_project, source_project: unauthorized_project) } let(:merge_request_in_unauthorized_project) { create(:merge_request_with_diffs, target_project: unauthorized_project, source_project: unauthorized_project) }
let(:milestone_in_unauthorized_project) { create(:milestone, project: unauthorized_project) } let(:milestone_in_unauthorized_project) { create(:milestone, project: unauthorized_project) }
let(:wiki_page) { WikiPages::CreateService.new(unauthorized_project, user, { title: "foo", content: "wiki_blobs" }).execute }
let(:commit) { unauthorized_project.repository.commit(SeedRepo::FirstCommit::ID) }
let(:search_service) { described_class.new(user, search: 'some-search-string', page: 1) } let(:search_service) { described_class.new(user, search: 'some-search-string', page: 1) }
let(:mock_global_service) { instance_double(Search::GlobalService, scope: 'some-scope') } let(:mock_global_service) { instance_double(Search::GlobalService, scope: 'some-scope') }
...@@ -147,55 +216,6 @@ describe SearchService do ...@@ -147,55 +216,6 @@ describe SearchService do
expect(subject).to be_kind_of(Kaminari::PaginatableArray) expect(subject).to be_kind_of(Kaminari::PaginatableArray)
expect(subject).to contain_exactly(note_on_issue_in_project) expect(subject).to contain_exactly(note_on_issue_in_project)
end end
it 'redacts commits the user does not have access to' do
allow(mock_results).to receive(:objects)
.and_return(
Kaminari.paginate_array(
[
commit
],
total_count: 1,
limit: 1,
offset: 0
)
)
expect(subject).to be_kind_of(Kaminari::PaginatableArray)
expect(subject).to be_empty
end
it 'redacts blobs the user does not have access to' do
blob = unauthorized_project.repository.blob_at(SeedRepo::FirstCommit::ID, 'README.md')
response = Elasticsearch::Model::Response::Response.new Blob, double(:search)
allow(response).to receive_messages(
results: [blob],
total_count: 1,
limit_value: 10,
offset_value: 0
)
allow(mock_results).to receive(:objects).and_return(response)
expect(subject).to be_kind_of(Kaminari::PaginatableArray)
expect(subject).to be_empty
end
it 'redacts wikis the user does not have access to' do
wiki_page = create(:wiki_page, wiki: unauthorized_project.wiki)
response = Elasticsearch::Model::Response::Response.new WikiPage, double(:search)
allow(response).to receive_messages(
results: [wiki_page],
total_count: 1,
limit_value: 10,
offset_value: 0
)
allow(mock_results).to receive(:objects).and_return(response)
expect(subject).to be_kind_of(Kaminari::PaginatableArray)
expect(subject).to be_empty
end
end end
end end
end end
...@@ -32,10 +32,6 @@ module API ...@@ -32,10 +32,6 @@ module API
results = SearchService.new(current_user, search_params).search_objects results = SearchService.new(current_user, search_params).search_objects
process_results(results)
end
def process_results(results)
paginate(results) paginate(results)
end end
......
...@@ -7,6 +7,7 @@ module Gitlab ...@@ -7,6 +7,7 @@ module Gitlab
include Presentable include Presentable
include BlobLanguageFromGitAttributes include BlobLanguageFromGitAttributes
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include BlobActiveModel
attr_reader :project, :content_match, :blob_path attr_reader :project, :content_match, :blob_path
......
# frozen_string_literal: true
# - rendering by using data purely from Elasticsearch and does not trigger Gitaly calls.
# - allows policy check
module Gitlab
module Search
class FoundWikiPage < SimpleDelegator
attr_reader :wiki
def self.declarative_policy_class
'WikiPagePolicy'
end
# @param found_blob [Gitlab::Search::FoundBlob]
def initialize(found_blob)
super
@wiki = found_blob.project.wiki
end
def to_ability_name
'wiki_page'
end
end
end
end
...@@ -156,4 +156,14 @@ describe Gitlab::Search::FoundBlob do ...@@ -156,4 +156,14 @@ describe Gitlab::Search::FoundBlob do
end end
end end
end end
describe 'policy' do
let(:project) { build(:project, :repository) }
subject { described_class.new(project: project) }
it 'works with policy' do
expect(Ability.allowed?(project.creator, :read_blob, subject)).to be_truthy
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Search::FoundWikiPage do
let(:project) { create(:project, :public, :repository) }
describe 'policy' do
let(:project) { build(:project, :repository) }
let(:found_blob) { Gitlab::Search::FoundBlob.new(project: project) }
subject { described_class.new(found_blob) }
it 'works with policy' do
expect(Ability.allowed?(project.creator, :read_wiki_page, subject)).to be_truthy
end
end
end
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