2018-07-05 06:18:17 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-03-31 09:03:55 -04:00
|
|
|
class SearchService
|
|
|
|
include Gitlab::Allowable
|
|
|
|
|
2020-01-14 13:08:31 -05:00
|
|
|
SEARCH_TERM_LIMIT = 64
|
|
|
|
SEARCH_CHAR_LIMIT = 4096
|
2020-05-07 17:09:26 -04:00
|
|
|
DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE
|
|
|
|
MAX_PER_PAGE = 200
|
|
|
|
|
2017-03-31 09:03:55 -04:00
|
|
|
def initialize(current_user, params = {})
|
|
|
|
@current_user = current_user
|
|
|
|
@params = params.dup
|
|
|
|
end
|
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2017-03-31 09:03:55 -04:00
|
|
|
def project
|
|
|
|
return @project if defined?(@project)
|
|
|
|
|
|
|
|
@project =
|
|
|
|
if params[:project_id].present?
|
|
|
|
the_project = Project.find_by(id: params[:project_id])
|
2017-05-22 12:51:09 -04:00
|
|
|
can?(current_user, :read_project, the_project) ? the_project : nil
|
2017-03-31 09:03:55 -04:00
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2017-03-31 09:03:55 -04:00
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2017-03-31 09:03:55 -04:00
|
|
|
def group
|
|
|
|
return @group if defined?(@group)
|
|
|
|
|
|
|
|
@group =
|
|
|
|
if params[:group_id].present?
|
|
|
|
the_group = Group.find_by(id: params[:group_id])
|
|
|
|
can?(current_user, :read_group, the_group) ? the_group : nil
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2017-03-31 09:03:55 -04:00
|
|
|
|
2021-06-15 08:10:11 -04:00
|
|
|
def projects
|
|
|
|
# overridden in EE
|
|
|
|
end
|
|
|
|
|
2017-03-31 09:03:55 -04:00
|
|
|
def show_snippets?
|
|
|
|
return @show_snippets if defined?(@show_snippets)
|
|
|
|
|
|
|
|
@show_snippets = params[:snippets] == 'true'
|
|
|
|
end
|
|
|
|
|
2020-01-14 13:08:31 -05:00
|
|
|
def valid_query_length?
|
|
|
|
params[:search].length <= SEARCH_CHAR_LIMIT
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid_terms_count?
|
|
|
|
params[:search].split.count { |word| word.length >= 3 } <= SEARCH_TERM_LIMIT
|
|
|
|
end
|
|
|
|
|
2017-03-31 09:03:55 -04:00
|
|
|
delegate :scope, to: :search_service
|
|
|
|
|
|
|
|
def search_results
|
|
|
|
@search_results ||= search_service.execute
|
|
|
|
end
|
|
|
|
|
2020-05-20 02:08:06 -04:00
|
|
|
def search_objects(preload_method = nil)
|
2020-10-21 17:09:00 -04:00
|
|
|
@search_objects ||= redact_unauthorized_results(
|
|
|
|
search_results.objects(scope, page: page, per_page: per_page, preload_method: preload_method)
|
|
|
|
)
|
2020-03-17 17:09:16 -04:00
|
|
|
end
|
2020-10-08 11:08:17 -04:00
|
|
|
|
|
|
|
def search_highlight
|
|
|
|
search_results.highlight_map(scope)
|
|
|
|
end
|
2020-03-17 17:09:16 -04:00
|
|
|
|
2017-03-31 09:03:55 -04:00
|
|
|
private
|
|
|
|
|
2020-10-21 17:09:00 -04:00
|
|
|
def page
|
|
|
|
[1, params[:page].to_i].max
|
|
|
|
end
|
|
|
|
|
2020-05-07 17:09:26 -04:00
|
|
|
def per_page
|
|
|
|
per_page_param = params[:per_page].to_i
|
|
|
|
|
2020-08-12 02:09:53 -04:00
|
|
|
return DEFAULT_PER_PAGE unless per_page_param > 0
|
2020-05-07 17:09:26 -04:00
|
|
|
|
|
|
|
[MAX_PER_PAGE, per_page_param].min
|
|
|
|
end
|
|
|
|
|
2020-03-17 17:09:16 -04:00
|
|
|
def visible_result?(object)
|
|
|
|
return true unless object.respond_to?(:to_ability_name) && DeclarativePolicy.has_policy?(object)
|
|
|
|
|
|
|
|
Ability.allowed?(current_user, :"read_#{object.to_ability_name}", object)
|
|
|
|
end
|
|
|
|
|
2020-04-09 17:09:19 -04:00
|
|
|
def redact_unauthorized_results(results_collection)
|
2020-06-15 23:08:24 -04:00
|
|
|
redacted_results = results_collection.reject { |object| visible_result?(object) }
|
2020-03-17 17:09:16 -04:00
|
|
|
|
2020-06-15 23:08:24 -04:00
|
|
|
if redacted_results.any?
|
|
|
|
redacted_log = redacted_results.each_with_object({}) do |object, memo|
|
|
|
|
memo[object.id] = { ability: :"read_#{object.to_ability_name}", id: object.id, class_name: object.class.name }
|
|
|
|
end
|
|
|
|
|
|
|
|
log_redacted_search_results(redacted_log.values)
|
|
|
|
|
|
|
|
return results_collection.id_not_in(redacted_log.keys) if results_collection.is_a?(ActiveRecord::Relation)
|
2020-03-17 17:09:16 -04:00
|
|
|
end
|
|
|
|
|
2020-06-15 23:08:24 -04:00
|
|
|
return results_collection if results_collection.is_a?(ActiveRecord::Relation)
|
2020-03-17 17:09:16 -04:00
|
|
|
|
2020-06-15 23:08:24 -04:00
|
|
|
permitted_results = results_collection - redacted_results
|
2020-03-17 17:09:16 -04:00
|
|
|
|
|
|
|
Kaminari.paginate_array(
|
|
|
|
permitted_results,
|
2020-04-09 17:09:19 -04:00
|
|
|
total_count: results_collection.total_count,
|
|
|
|
limit: results_collection.limit_value,
|
|
|
|
offset: results_collection.offset_value
|
2020-03-17 17:09:16 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def log_redacted_search_results(filtered_results)
|
|
|
|
logger.error(message: "redacted_search_results", filtered: filtered_results, current_user_id: current_user&.id, query: params[:search])
|
|
|
|
end
|
|
|
|
|
|
|
|
def logger
|
|
|
|
@logger ||= ::Gitlab::RedactedSearchResultsLogger.build
|
|
|
|
end
|
|
|
|
|
2017-03-31 09:03:55 -04:00
|
|
|
def search_service
|
|
|
|
@search_service ||=
|
|
|
|
if project
|
|
|
|
Search::ProjectService.new(project, current_user, params)
|
|
|
|
elsif show_snippets?
|
|
|
|
Search::SnippetService.new(current_user, params)
|
2017-04-13 11:20:04 -04:00
|
|
|
elsif group
|
|
|
|
Search::GroupService.new(current_user, group, params)
|
2017-03-31 09:03:55 -04:00
|
|
|
else
|
|
|
|
Search::GlobalService.new(current_user, params)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
attr_reader :current_user, :params
|
|
|
|
end
|
2019-09-13 09:26:31 -04:00
|
|
|
|
2021-05-11 17:10:21 -04:00
|
|
|
SearchService.prepend_mod_with('SearchService')
|