Commit 62aaaa1b authored by Micael Bergeron's avatar Micael Bergeron

Optimize the ES Query for confidentiality check

Previously, we would do separate checks for project membership and
confidentiality checks.

This commit adds an optimization that will limit the confidentiality
check to be within the limited projects, if such a limit exists.
parent 1e5cc59b
---
title: Optimize the Advanced Search query for Issues and Notes.
merge_request: 38095
author:
type: performance
......@@ -171,7 +171,7 @@ module Elastic
limit =
{ terms: { "#{feature}_access_level" => [::ProjectFeature::ENABLED, ::ProjectFeature::PRIVATE] } }
{ bool: { filter: [condition, limit].compact } }
{ bool: { filter: [condition, limit] } }
end
end
......@@ -226,11 +226,24 @@ module Elastic
# When reading cross project is not allowed, only allow searching a
# a single project, so the `:read_*` ability is only checked once.
unless Ability.allowed?(current_user, :read_cross_project)
project_ids = [] if project_ids.size > 1
return [] if project_ids.size > 1
end
project_ids
end
def authorized_project_ids(current_user, options = {})
return [] unless current_user
scoped_project_ids = scoped_project_ids(current_user, options[:project_ids])
authorized_project_ids = current_user.authorized_projects(Gitlab::Access::REPORTER).pluck_primary_key.to_set
# if the current search is limited to a subset of projects, we should do
# confidentiality check for these projects.
authorized_project_ids &= scoped_project_ids.to_set unless scoped_project_ids == :any
authorized_project_ids.to_a
end
end
end
end
......@@ -13,29 +13,32 @@ module Elastic
options[:features] = 'issues'
query_hash = project_ids_filter(query_hash, options)
query_hash = confidentiality_filter(query_hash, options[:current_user], options[:project_ids])
query_hash = confidentiality_filter(query_hash, options)
search(query_hash, options)
end
private
def user_has_access_to_confidential_issues?(authorized_project_ids, project_ids)
# is_a?(Array) is needed because we might receive project_ids: :any
return false unless authorized_project_ids && project_ids.is_a?(Array)
def confidentiality_filter(query_hash, options)
current_user = options[:current_user]
project_ids = options[:project_ids]
(project_ids - authorized_project_ids).empty?
end
def confidentiality_filter(query_hash, current_user, project_ids)
return query_hash if current_user&.can_read_all_resources?
authorized_project_ids = current_user&.authorized_projects(Gitlab::Access::REPORTER)&.pluck_primary_key
return query_hash if user_has_access_to_confidential_issues?(authorized_project_ids, project_ids)
scoped_project_ids = scoped_project_ids(current_user, project_ids)
authorized_project_ids = authorized_project_ids(current_user, options)
# we can shortcut the filter if the user is authorized to see
# all the projects for which this query is scoped on
unless scoped_project_ids == :any || scoped_project_ids.empty?
return query_hash if authorized_project_ids.to_set == scoped_project_ids.to_set
end
filter = { term: { confidential: false } }
filter =
if current_user
{
filter = {
bool: {
should: [
{ term: { confidential: false } },
......@@ -58,8 +61,6 @@ module Elastic
]
}
}
else
{ term: { confidential: false } }
end
query_hash[:query][:bool][:filter] << filter
......
......@@ -14,7 +14,7 @@ module Elastic
query_hash = basic_query_hash(%w[note], query)
query_hash = project_ids_filter(query_hash, options)
query_hash = confidentiality_filter(query_hash, options[:current_user])
query_hash = confidentiality_filter(query_hash, options)
query_hash[:highlight] = highlight_options(options[:in])
......@@ -23,7 +23,9 @@ module Elastic
private
def confidentiality_filter(query_hash, current_user)
def confidentiality_filter(query_hash, options)
current_user = options[:current_user]
return query_hash if current_user&.can_read_all_resources?
filter = {
......@@ -43,7 +45,7 @@ module Elastic
bool: {
should: [
{ bool: { must_not: [{ exists: { field: :confidential } }] } },
{ term: { "confidential" => false } }
{ term: { confidential: false } }
]
}
}
......@@ -61,7 +63,7 @@ module Elastic
bool: {
should: [
{ term: { "issue.confidential" => true } },
{ term: { "confidential" => true } }
{ term: { confidential: true } }
]
}
},
......@@ -70,7 +72,7 @@ module Elastic
should: [
{ term: { "issue.author_id" => current_user.id } },
{ term: { "issue.assignee_id" => current_user.id } },
{ terms: { "project_id" => current_user.authorized_projects(Gitlab::Access::REPORTER).pluck_primary_key } }
{ terms: { project_id: authorized_project_ids(current_user, options) } }
]
}
}
......
......@@ -261,6 +261,7 @@ RSpec.configure do |config|
./spec/support/protected_tags
./spec/support/shared_examples/features
./spec/support/shared_examples/requests
./spec/support/shared_examples/lib/gitlab
./spec/views
./spec/workers
)
......
......@@ -10,10 +10,6 @@ RSpec.shared_examples 'access restricted confidential issues' do
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]) }
before do
stub_feature_flags(user_mode_in_session: false)
end
subject(:objects) do
described_class.new(user, query, project: project).objects('issues')
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