Commit 98bc2786 authored by Terri Chu's avatar Terri Chu Committed by Dylan Griffith

Remove project joins from notes ES query

Use new denormalized project visibility and project
feature permissions in the notes documents if the
ES migration has finished. The ES query supports
both before and after migration.
parent 02e3a87f
......@@ -13,6 +13,7 @@ module Elastic
options[:in] = ['note']
query_hash = basic_query_hash(%w[note], query, count_only: options[:count_only])
options[:no_join_project] = Elastic::DataMigrationService.migration_has_finished?(:add_permissions_data_to_notes_documents)
context.name(:note) do
query_hash = context.name(:authorized) { project_ids_filter(query_hash, options) }
query_hash = context.name(:confidentiality) { confidentiality_filter(query_hash, options) }
......@@ -100,6 +101,9 @@ module Elastic
override :project_ids_filter
def project_ids_filter(query_hash, options)
# support for not using project joins in the query
no_join_project = options[:no_join_project]
query_hash[:query][:bool][:filter] ||= []
project_query = context.name(:project) do
......@@ -107,7 +111,8 @@ module Elastic
options[:current_user],
options[:project_ids],
options[:public_and_internal_projects],
options[:features]
options[:features],
no_join_project
)
end
......@@ -124,23 +129,30 @@ module Elastic
project_query[:should].flatten.each do |condition|
noteable_type = condition.delete(:noteable_type).to_s
filters[:bool][:should] << {
should_filter = {
bool: {
must: [
{
has_parent: {
parent_type: "project",
query: {
bool: {
should: condition
}
}
}
},
{ term: { noteable_type: { _name: context.name(:noteable, :is_a, noteable_type), value: noteable_type } } }
]
}
}
should_filter[:bool][:must] << if no_join_project
condition
else
{
has_parent: {
parent_type: "project",
query: {
bool: {
should: condition
}
}
}
}
end
filters[:bool][:should] << should_filter
end
query_hash[:query][:bool][:filter] << filters
......@@ -150,18 +162,18 @@ module Elastic
# Query notes based on the various feature permission of the noteable_type.
# Appends `noteable_type` (which will be removed in project_ids_filter)
# for base model filtering.
# We do not implement `no_join_project` argument for notes class yet
# as this is not supported. This will need to be fixed when we move
# notes to a new index.
override :pick_projects_by_membership
def pick_projects_by_membership(project_ids, user, _, _ = nil)
def pick_projects_by_membership(project_ids, user, no_join_project, _ = nil)
# support for not using project joins in the query
project_id_key = no_join_project ? :project_id : :id
noteable_type_to_feature.map do |noteable_type, feature|
context.name(feature) do
condition =
if project_ids == :any
{ term: { visibility_level: { _name: context.name(:any), value: Project::PRIVATE } } }
else
{ terms: { _name: context.name(:membership, :id), id: filter_ids_by_feature(project_ids, user, feature) } }
{ terms: { _name: context.name(:membership, :id), project_id_key => filter_ids_by_feature(project_ids, user, feature) } }
end
limit =
......
......@@ -18,248 +18,259 @@ RSpec.describe Search::GlobalService do
let(:service) { described_class.new(user, params) }
end
context 'issue search' do
let(:results) { described_class.new(nil, search: '*').execute.objects('issues') }
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:migrate_issues_to_separate_index)
.and_return(false)
end
it_behaves_like 'search query applies joins based on migrations shared examples', :add_new_data_to_issues_documents
end
context 'notes search' do
let(:results) { described_class.new(nil, search: '*').execute.objects('notes') }
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
it_behaves_like 'search query applies joins based on migrations shared examples', :add_permissions_data_to_notes_documents
end
context 'visibility', :elastic, :sidekiq_inline do
include_context 'ProjectPolicyTable context'
shared_examples 'search respects visibility' do
it 'respects visibility' do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
ensure_elasticsearch_index!
expect_search_results(user, scope, expected_count: expected_count) do |user|
described_class.new(user, search: search).execute
end
end
end
let_it_be(:group) { create(:group) }
let(:project) { create(:project, project_level, namespace: group) }
let(:user) { create_user_from_membership(project, membership) }
context 'merge request' do
let!(:merge_request) { create :merge_request, target_project: project, source_project: project }
let!(:note) { create :note, project: project, noteable: merge_request }
let(:scope) { 'merge_requests' }
let(:search) { merge_request.title }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_reporter_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
ensure_elasticsearch_index!
expect_search_results(user, 'merge_requests', expected_count: expected_count) do |user|
described_class.new(user, search: merge_request.title).execute
end
expect_search_results(user, 'notes', expected_count: expected_count) do |user|
described_class.new(user, search: note.note).execute
end
end
it_behaves_like 'search respects visibility'
end
end
context 'code' do
context 'blob and commit' do
let!(:project) { create(:project, project_level, :repository, namespace: group ) }
let!(:note) { create :note_on_commit, project: project }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
ElasticCommitIndexerWorker.new.perform(project.id)
ensure_elasticsearch_index!
expect_search_results(user, 'commits', expected_count: expected_count) do |user|
described_class.new(user, search: 'initial').execute
end
before do
project.repository.index_commits_and_blobs
end
expect_search_results(user, 'blobs', expected_count: expected_count) do |user|
described_class.new(user, search: '.gitmodules').execute
end
it_behaves_like 'search respects visibility' do
let(:scope) { 'commits' }
let(:search) { 'initial' }
end
expect_search_results(user, 'notes', expected_count: expected_count) do |user|
described_class.new(user, search: note.note).execute
end
it_behaves_like 'search respects visibility' do
let(:scope) { 'blobs' }
let(:search) { '.gitmodules' }
end
end
end
context 'issue' do
let(:scope) { 'issues' }
context 'note' do
let(:scope) { 'notes' }
let(:search) { note.note }
context 'visibility' do
let!(:issue) { create :issue, project: project }
let!(:note) { create :note, project: project, noteable: issue }
context 'on issues' do
let!(:note) { create :note_on_issue, project: project }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
ensure_elasticsearch_index!
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
expect_search_results(user, 'issues', expected_count: expected_count) do |user|
described_class.new(user, search: issue.title).execute
end
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
expect_search_results(user, 'notes', expected_count: expected_count) do |user|
described_class.new(user, search: note.note).execute
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
it_behaves_like 'search respects visibility'
end
end
end
# Since newly created indices automatically have all migrations as
# finished we need a test to verify the old style searches work for
# instances which haven't finished the migration yet
context 'when add_new_data_to_issues_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_new_data_to_issues_documents)
.and_return(false)
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:migrate_issues_to_separate_index)
.and_return(false)
end
# issue cannot be defined prior to the migration mocks because it
# will cause the incorrect value to be passed to `use_separate_indices` when creating
# the proxy
let!(:issue) { create(:issue, project: project) }
context 'on merge requests' do
let!(:note) { create :note_on_merge_request, project: project }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
permission_table_for_reporter_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
ensure_elasticsearch_index!
expect_search_results(user, 'issues', expected_count: expected_count) do |user|
described_class.new(user, search: issue.title).execute
end
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
end
end
context 'ordering' do
let_it_be(:project) { create(:project, :public) }
let!(:old_result) { create(:issue, project: project, title: 'sorted old', created_at: 1.month.ago) }
let!(:new_result) { create(:issue, project: project, title: 'sorted recent', created_at: 1.day.ago) }
let!(:very_old_result) { create(:issue, project: project, title: 'sorted very old', created_at: 1.year.ago) }
let!(:old_updated) { create(:issue, project: project, title: 'updated old', updated_at: 1.month.ago) }
let!(:new_updated) { create(:issue, project: project, title: 'updated recent', updated_at: 1.day.ago) }
let!(:very_old_updated) { create(:issue, project: project, title: 'updated very old', updated_at: 1.year.ago) }
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
before do
ensure_elasticsearch_index!
end
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
include_examples 'search results sorted' do
let(:results_created) { described_class.new(nil, search: 'sorted', sort: sort).execute }
let(:results_updated) { described_class.new(nil, search: 'updated', sort: sort).execute }
it_behaves_like 'search respects visibility'
end
end
end
context 'using joins for global permission checks' do
let(:results) { described_class.new(nil, search: '*').execute.objects('issues') }
let(:es_host) { Gitlab::CurrentSettings.elasticsearch_url[0] }
let(:search_url) { Addressable::Template.new("#{es_host}/{index}/doc/_search{?params*}") }
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:migrate_issues_to_separate_index)
.and_return(false)
context 'on commits' do
let!(:project) { create(:project, project_level, :repository, namespace: group ) }
let!(:note) { create :note_on_commit, project: project }
ensure_elasticsearch_index!
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
end
context 'when add_new_data_to_issues_documents migration is finished' do
with_them do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_new_data_to_issues_documents)
.and_return(true)
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
project.repository.index_commits_and_blobs
end
it 'does not use joins to apply permissions' do
request = a_request(:get, search_url).with do |req|
expect(req.body).not_to include("has_parent")
end
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
results
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
expect(request).to have_been_made
it_behaves_like 'search respects visibility'
end
end
end
context 'on snippets' do
let!(:note) { create :note_on_project_snippet, project: project }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
context 'when add_new_data_to_issues_documents migration is not finished' do
with_them do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_new_data_to_issues_documents)
.and_return(false)
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
it 'uses joins to apply permissions' do
request = a_request(:get, search_url).with do |req|
expect(req.body).to include("has_parent")
end
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
results
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
expect(request).to have_been_made
it_behaves_like 'search respects visibility'
end
end
end
end
context 'merge_request' do
let(:scope) { 'merge_requests' }
context 'issue' do
let(:scope) { 'issues' }
let(:search) { issue.title }
context 'sorting' do
let!(:project) { create(:project, :public) }
context 'when add_new_data_to_issues_documents migration is finished' do
let!(:issue) { create :issue, project: project }
let!(:old_result) { create(:merge_request, :opened, source_project: project, source_branch: 'old-1', title: 'sorted old', created_at: 1.month.ago) }
let!(:new_result) { create(:merge_request, :opened, source_project: project, source_branch: 'new-1', title: 'sorted recent', created_at: 1.day.ago) }
let!(:very_old_result) { create(:merge_request, :opened, source_project: project, source_branch: 'very-old-1', title: 'sorted very old', created_at: 1.year.ago) }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
let!(:old_updated) { create(:merge_request, :opened, source_project: project, source_branch: 'updated-old-1', title: 'updated old', updated_at: 1.month.ago) }
let!(:new_updated) { create(:merge_request, :opened, source_project: project, source_branch: 'updated-new-1', title: 'updated recent', updated_at: 1.day.ago) }
let!(:very_old_updated) { create(:merge_request, :opened, source_project: project, source_branch: 'updated-very-old-1', title: 'updated very old', updated_at: 1.year.ago) }
with_them do
it_behaves_like 'search respects visibility'
end
end
# Since newly created indices automatically have all migrations as
# finished we need a test to verify the old style searches work for
# instances which haven't finished the migration yet
context 'when add_new_data_to_issues_documents migration is not finished' do
before do
ensure_elasticsearch_index!
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_new_data_to_issues_documents)
.and_return(false)
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:migrate_issues_to_separate_index)
.and_return(false)
end
# issue cannot be defined prior to the migration mocks because it
# will cause the incorrect value to be passed to `use_separate_indices` when creating
# the proxy
let!(:issue) { create(:issue, project: project) }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
include_examples 'search results sorted' do
let(:results_created) { described_class.new(nil, search: 'sorted', sort: sort).execute }
let(:results_updated) { described_class.new(nil, search: 'updated', sort: sort).execute }
with_them do
it_behaves_like 'search respects visibility'
end
end
end
context 'wiki' do
let!(:project) { create(:project, project_level, :wiki_repo) }
let(:scope) { 'wiki_blobs' }
let(:search) { 'term' }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
project.wiki.create_page('test.md', '# term')
before do
project.wiki.create_page('test.md', "# #{search}")
project.wiki.index_wiki_blobs
update_feature_access_level(project, feature_access_level)
ensure_elasticsearch_index!
end
expect_search_results(user, 'wiki_blobs', expected_count: expected_count) do |user|
described_class.new(user, search: 'term').execute
end
it_behaves_like 'search respects visibility' do
let(:projects) { [project] }
end
end
end
......@@ -312,6 +323,52 @@ RSpec.describe Search::GlobalService do
end
end
context 'sorting', :elastic, :sidekiq_inline do
context 'issue' do
let(:scope) { 'issues' }
let_it_be(:project) { create(:project, :public) }
let!(:old_result) { create(:issue, project: project, title: 'sorted old', created_at: 1.month.ago) }
let!(:new_result) { create(:issue, project: project, title: 'sorted recent', created_at: 1.day.ago) }
let!(:very_old_result) { create(:issue, project: project, title: 'sorted very old', created_at: 1.year.ago) }
let!(:old_updated) { create(:issue, project: project, title: 'updated old', updated_at: 1.month.ago) }
let!(:new_updated) { create(:issue, project: project, title: 'updated recent', updated_at: 1.day.ago) }
let!(:very_old_updated) { create(:issue, project: project, title: 'updated very old', updated_at: 1.year.ago) }
before do
ensure_elasticsearch_index!
end
include_examples 'search results sorted' do
let(:results_created) { described_class.new(nil, search: 'sorted', sort: sort).execute }
let(:results_updated) { described_class.new(nil, search: 'updated', sort: sort).execute }
end
end
context 'merge request' do
let(:scope) { 'merge_requests' }
let(:project) { create(:project, :public) }
let!(:old_result) { create(:merge_request, :opened, source_project: project, source_branch: 'old-1', title: 'sorted old', created_at: 1.month.ago) }
let!(:new_result) { create(:merge_request, :opened, source_project: project, source_branch: 'new-1', title: 'sorted recent', created_at: 1.day.ago) }
let!(:very_old_result) { create(:merge_request, :opened, source_project: project, source_branch: 'very-old-1', title: 'sorted very old', created_at: 1.year.ago) }
let!(:old_updated) { create(:merge_request, :opened, source_project: project, source_branch: 'updated-old-1', title: 'updated old', updated_at: 1.month.ago) }
let!(:new_updated) { create(:merge_request, :opened, source_project: project, source_branch: 'updated-new-1', title: 'updated recent', updated_at: 1.day.ago) }
let!(:very_old_updated) { create(:merge_request, :opened, source_project: project, source_branch: 'updated-very-old-1', title: 'updated very old', updated_at: 1.year.ago) }
before do
ensure_elasticsearch_index!
end
include_examples 'search results sorted' do
let(:results_created) { described_class.new(nil, search: 'sorted', sort: sort).execute }
let(:results_updated) { described_class.new(nil, search: 'updated', sort: sort).execute }
end
end
end
describe '#allowed_scopes' do
context 'when ES is used' do
it 'includes ES-specific scopes' do
......@@ -396,23 +453,81 @@ RSpec.describe Search::GlobalService do
end
context 'confidential notes' do
let(:project) { create(:project, :public) }
let(:project) { create(:project, :public, :repository) }
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
context 'with notes on issues' do
it_behaves_like 'search notes shared examples' do
let(:noteable) { create :issue, project: project }
let(:noteable) { create :issue, project: project }
context 'when add_permissions_data_to_notes_documents migration has not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_permissions_data_to_notes_documents)
.and_return(false)
end
it_behaves_like 'search notes shared examples', :note_on_issue
end
context 'when add_permissions_data_to_notes_documents migration has finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_permissions_data_to_notes_documents)
.and_return(true)
end
it_behaves_like 'search notes shared examples', :note_on_issue
end
end
context 'with notes on merge requests' do
it_behaves_like 'search notes shared examples' do
let(:noteable) { create :merge_request, target_project: project, source_project: project }
let(:noteable) { create :merge_request, target_project: project, source_project: project }
context 'when add_permissions_data_to_notes_documents migration has not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_permissions_data_to_notes_documents)
.and_return(false)
end
it_behaves_like 'search notes shared examples', :note_on_merge_request
end
context 'when add_permissions_data_to_notes_documents migration has finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_permissions_data_to_notes_documents)
.and_return(true)
end
it_behaves_like 'search notes shared examples', :note_on_merge_request
end
end
context 'with notes on commits' do
it_behaves_like 'search notes shared examples' do
let(:noteable) { create(:commit, project: project) }
let(:noteable) { create(:commit, project: project) }
context 'when add_permissions_data_to_notes_documents migration has not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_permissions_data_to_notes_documents)
.and_return(false)
end
it_behaves_like 'search notes shared examples', :note_on_commit
end
context 'when add_permissions_data_to_notes_documents migration has finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:add_permissions_data_to_notes_documents)
.and_return(true)
end
it_behaves_like 'search notes shared examples', :note_on_commit
end
end
end
......
......@@ -67,71 +67,195 @@ RSpec.describe Search::GroupService, :elastic do
end
end
context 'notes search' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, namespace: group) }
let(:results) { described_class.new(nil, group, search: 'test').execute.objects('notes') }
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
it_behaves_like 'search query applies joins based on migrations shared examples', :add_permissions_data_to_notes_documents
end
context 'visibility', :sidekiq_inline do
include_context 'ProjectPolicyTable context'
shared_examples 'search respects visibility' do
it 'respects visibility' do
enable_admin_mode!(user) if admin_mode
projects.each do |project|
update_feature_access_level(project, feature_access_level)
end
ensure_elasticsearch_index!
expect_search_results(user, scope, expected_count: expected_count) do |user|
described_class.new(user, group, search: search).execute
end
end
end
let_it_be(:group) { create(:group) }
let!(:project) { create(:project, project_level, namespace: group) }
let!(:project2) { create(:project, project_level) }
let(:user) { create_user_from_membership(project, membership) }
let(:projects) { [project, project2] }
context 'merge request' do
let!(:merge_request) { create :merge_request, target_project: project, source_project: project }
let!(:merge_request2) { create :merge_request, target_project: project2, source_project: project2, title: merge_request.title }
let!(:note) { create :note, project: project, noteable: merge_request }
let!(:note2) { create :note, project: project2, noteable: merge_request2, note: note.note }
let(:scope) { 'merge_requests' }
let(:search) { merge_request.title }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_reporter_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
[project, project2].each do |project|
update_feature_access_level(project, feature_access_level)
it_behaves_like 'search respects visibility'
end
end
context 'blob and commit' do
let!(:project) { create(:project, project_level, :repository, namespace: group ) }
let!(:project2) { create(:project, project_level, :repository) }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
end
with_them do
before do
project.repository.index_commits_and_blobs
project2.repository.index_commits_and_blobs
end
it_behaves_like 'search respects visibility' do
let(:scope) { 'commits' }
let(:search) { 'initial' }
end
it_behaves_like 'search respects visibility' do
let(:scope) { 'blobs' }
let(:search) { '.gitmodules' }
end
end
end
context 'note' do
let(:scope) { 'notes' }
let(:search) { note.note }
context 'on issues' do
let!(:note) { create :note_on_issue, project: project }
let!(:note2) { create :note_on_issue, project: project2, note: note.note }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
ensure_elasticsearch_index!
expect_search_results(user, 'merge_requests', expected_count: expected_count) do |user|
described_class.new(user, group, search: merge_request.title).execute
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
expect_search_results(user, 'notes', expected_count: expected_count) do |user|
described_class.new(user, group, search: note.note).execute
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
it_behaves_like 'search respects visibility'
end
end
end
end
context 'code' do
let!(:project) { create(:project, project_level, :repository, namespace: group ) }
let!(:note) { create :note_on_commit, project: project }
context 'on merge requests' do
let!(:note) { create :note_on_merge_request, project: project }
let!(:note2) { create :note_on_merge_request, project: project2, note: note.note }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_reporter_feature_access
end
with_them do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
it_behaves_like 'search respects visibility'
end
end
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
[project, project2].each do |project|
update_feature_access_level(project, feature_access_level)
ElasticCommitIndexerWorker.new.perform(project.id)
context 'on commits' do
let!(:project) { create(:project, project_level, :repository, namespace: group ) }
let!(:project2) { create(:project, project_level, :repository) }
let!(:note) { create :note_on_commit, project: project }
let!(:note2) { create :note_on_commit, project: project2, note: note.note }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
end
with_them do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
project.repository.index_commits_and_blobs
project2.repository.index_commits_and_blobs
end
ElasticCommitIndexerWorker.new.perform(project.id)
ensure_elasticsearch_index!
expect_search_results(user, 'commits', expected_count: expected_count) do |user|
described_class.new(user, group, search: 'initial').execute
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
it_behaves_like 'search respects visibility'
end
end
end
context 'on snippets' do
let!(:note) { create :note_on_project_snippet, project: project }
let!(:note2) { create :note_on_project_snippet, project: project2, note: note.note }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
expect_search_results(user, 'blobs', expected_count: expected_count) do |user|
described_class.new(user, group, search: '.gitmodules').execute
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
expect_search_results(user, 'notes', expected_count: expected_count) do |user|
described_class.new(user, group, search: note.note).execute
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
it_behaves_like 'search respects visibility'
end
end
end
......@@ -140,51 +264,36 @@ RSpec.describe Search::GroupService, :elastic do
context 'issue' do
let!(:issue) { create :issue, project: project }
let!(:issue2) { create :issue, project: project2, title: issue.title }
let!(:note) { create :note, project: project, noteable: issue }
let!(:note2) { create :note, project: project2, noteable: issue2, note: note.note }
let(:scope) { 'issues' }
let(:search) { issue.title }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
[project, project2].each do |project|
update_feature_access_level(project, feature_access_level)
end
ensure_elasticsearch_index!
expect_search_results(user, 'issues', expected_count: expected_count) do |user|
described_class.new(user, group, search: issue.title).execute
end
expect_search_results(user, 'notes', expected_count: expected_count) do |user|
described_class.new(user, group, search: note.note).execute
end
end
it_behaves_like 'search respects visibility'
end
end
context 'wiki' do
let!(:project) { create(:project, project_level, :wiki_repo) }
let(:group) { project.namespace }
let(:projects) { [project] }
let(:scope) { 'wiki_blobs' }
let(:search) { 'term' }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
project.wiki.create_page('test.md', '# term')
before do
project.wiki.create_page('test.md', "# term")
project.wiki.index_wiki_blobs
update_feature_access_level(project, feature_access_level)
ensure_elasticsearch_index!
expect_search_results(user, 'wiki_blobs', expected_count: expected_count) do |user|
described_class.new(user, project.namespace, search: 'term').execute
end
end
it_behaves_like 'search respects visibility'
end
end
......@@ -211,7 +320,7 @@ RSpec.describe Search::GroupService, :elastic do
end
end
context 'project search' do
context 'project' do
let(:project) { create(:project, project_level, namespace: group) }
where(:project_level, :membership, :expected_count) do
......@@ -238,10 +347,9 @@ RSpec.describe Search::GroupService, :elastic do
end
end
context 'issues' do
let(:scope) { 'issues' }
context 'sorting' do
context 'sorting' do
context 'issues' do
let(:scope) { 'issues' }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :public, group: group) }
......@@ -262,12 +370,9 @@ RSpec.describe Search::GroupService, :elastic do
let(:results_updated) { described_class.new(nil, group, search: 'updated', sort: sort).execute }
end
end
end
context 'merge requests' do
let(:scope) { 'merge_requests' }
context 'sorting' do
context 'merge requests' do
let(:scope) { 'merge_requests' }
let!(:project) { create(:project, :public, group: group) }
let!(:old_result) { create(:merge_request, :opened, source_project: project, source_branch: 'old-1', title: 'sorted old', created_at: 1.month.ago) }
......
......@@ -34,72 +34,194 @@ RSpec.describe Search::ProjectService do
end
end
context 'notes search' do
let_it_be(:project) { create(:project) }
let(:results) { described_class.new(project, nil, search: 'test').execute.objects('notes') }
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
it_behaves_like 'search query applies joins based on migrations shared examples', :add_permissions_data_to_notes_documents
end
context 'visibility', :elastic, :sidekiq_inline do
include_context 'ProjectPolicyTable context'
shared_examples 'search respects visibility' do
it 'respects visibility' do
enable_admin_mode!(user) if admin_mode
projects.each do |project|
update_feature_access_level(project, feature_access_level)
end
ensure_elasticsearch_index!
expect_search_results(user, scope, expected_count: expected_count) do |user|
described_class.new(project, user, search: search).execute
end
end
end
let_it_be(:group) { create(:group) }
let!(:project) { create(:project, project_level, namespace: group) }
let!(:project2) { create(:project, project_level) }
let(:user) { create_user_from_membership(project, membership) }
let(:projects) { [project, project2] }
context 'merge request' do
let!(:merge_request) { create :merge_request, target_project: project, source_project: project }
let!(:merge_request2) { create :merge_request, target_project: project2, source_project: project2, title: merge_request.title }
let!(:note) { create :note, project: project, noteable: merge_request }
let!(:note2) { create :note, project: project2, noteable: merge_request2, note: note.note }
let(:scope) { 'merge_requests' }
let(:search) { merge_request.title }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_reporter_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
[project, project2].each do |project|
update_feature_access_level(project, feature_access_level)
end
ensure_elasticsearch_index!
expect_search_results(user, 'merge_requests', expected_count: expected_count) do |user|
described_class.new(project, user, search: merge_request.title).execute
end
expect_search_results(user, 'notes', expected_count: expected_count) do |user|
described_class.new(project, user, search: note.note).execute
end
end
it_behaves_like 'search respects visibility'
end
end
context 'code' do
context 'blob and commit' do
let!(:project) { create(:project, project_level, :repository, namespace: group ) }
let!(:project2) { create(:project, project_level, :repository) }
let!(:note) { create :note_on_commit, project: project }
let!(:note2) { create :note_on_commit, project: project2, note: note.note }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
[project, project2].each do |project|
update_feature_access_level(project, feature_access_level)
ElasticCommitIndexerWorker.new.perform(project.id)
before do
project.repository.index_commits_and_blobs
project2.repository.index_commits_and_blobs
end
it_behaves_like 'search respects visibility' do
let(:scope) { 'commits' }
let(:search) { 'initial' }
end
it_behaves_like 'search respects visibility' do
let(:scope) { 'blobs' }
let(:search) { '.gitmodules' }
end
end
end
context 'note' do
let(:scope) { 'notes' }
let(:search) { note.note }
context 'on issues' do
let!(:note) { create :note_on_issue, project: project }
let!(:note2) { create :note_on_issue, project: project2, note: note.note }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
ensure_elasticsearch_index!
expect_search_results(user, 'commits', expected_count: expected_count) do |user|
described_class.new(project, user, search: 'initial').execute
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
it_behaves_like 'search respects visibility'
end
end
end
context 'on merge requests' do
let!(:note) { create :note_on_merge_request, project: project }
let!(:note2) { create :note_on_merge_request, project: project2, note: note.note }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_reporter_feature_access
end
with_them do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
expect_search_results(user, 'blobs', expected_count: expected_count) do |user|
described_class.new(project, user, search: '.gitmodules').execute
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
expect_search_results(user, 'notes', expected_count: expected_count) do |user|
described_class.new(project, user, search: note.note).execute
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
it_behaves_like 'search respects visibility'
end
end
end
context 'on commits' do
let!(:project) { create(:project, project_level, :repository, namespace: group ) }
let!(:project2) { create(:project, project_level, :repository) }
let!(:note) { create :note_on_commit, project: project }
let!(:note2) { create :note_on_commit, project: project2, note: note.note }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
end
with_them do
before do
project.repository.index_commits_and_blobs
project2.repository.index_commits_and_blobs
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
it_behaves_like 'search respects visibility'
end
end
end
context 'on snippets' do
let!(:note) { create :note_on_project_snippet, project: project }
let!(:note2) { create :note_on_project_snippet, project: project2, note: note.note }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
end
context 'when add_permissions_data_to_notes_documents migration is finished' do
it_behaves_like 'search respects visibility'
end
context 'when add_permissions_data_to_notes_documents migration is not finished' do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).with(:add_permissions_data_to_notes_documents).and_return(false)
end
it_behaves_like 'search respects visibility'
end
end
end
......@@ -108,51 +230,35 @@ RSpec.describe Search::ProjectService do
context 'issue' do
let!(:issue) { create :issue, project: project }
let!(:issue2) { create :issue, project: project2, title: issue.title }
let!(:note) { create :note, project: project, noteable: issue }
let!(:note2) { create :note, project: project2, noteable: issue2, note: note.note }
let(:scope) { 'issues' }
let(:search) { issue.title }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
[project, project2].each do |project|
update_feature_access_level(project, feature_access_level)
end
ensure_elasticsearch_index!
expect_search_results(user, 'issues', expected_count: expected_count) do |user|
described_class.new(project, user, search: issue.title).execute
end
expect_search_results(user, 'notes', expected_count: expected_count) do |user|
described_class.new(project, user, search: note.note).execute
end
end
it_behaves_like 'search respects visibility'
end
end
context 'wiki' do
let!(:project) { create(:project, project_level, :wiki_repo) }
let(:projects) { [project] }
let(:scope) { 'wiki_blobs' }
let(:search) { 'term' }
before do
project.wiki.create_page('test.md', "# term")
project.wiki.index_wiki_blobs
end
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
project.wiki.create_page('test.md', '# term')
project.wiki.index_wiki_blobs
update_feature_access_level(project, feature_access_level)
ensure_elasticsearch_index!
expect_search_results(user, 'wiki_blobs', expected_count: expected_count) do |user|
described_class.new(project, user, search: 'term').execute
end
end
it_behaves_like 'search respects visibility'
end
end
......@@ -180,10 +286,9 @@ RSpec.describe Search::ProjectService do
end
end
context 'issues' do
let(:scope) { 'issues' }
context 'sorting', :elastic do
context 'sorting', :elastic, :sidekiq_inline do
context 'issues' do
let(:scope) { 'issues' }
let_it_be(:project) { create(:project, :public) }
let!(:old_result) { create(:issue, project: project, title: 'sorted old', created_at: 1.month.ago) }
......@@ -203,12 +308,9 @@ RSpec.describe Search::ProjectService do
let(:results_updated) { described_class.new(project, nil, search: 'updated', sort: sort).execute }
end
end
end
context 'merge requests' do
let(:scope) { 'merge_requests' }
context 'sorting', :elastic do
context 'merge requests' do
let(:scope) { 'merge_requests' }
let(:project) { create(:project, :public) }
let!(:old_result) { create(:merge_request, :opened, source_project: project, source_branch: 'old-1', title: 'sorted old', created_at: 1.month.ago) }
......
......@@ -32,7 +32,7 @@ RSpec.shared_examples 'search notes shared examples' do
end
context 'when user can read confidential notes' do
it 'does not filter confidental notes' do
it 'does not filter confidential notes' do
noteable.project.add_reporter(user)
expect_search_results(user, 'notes', expected_objects: [not_confidential_note, nil_confidential_note, confidential_note]) do |user|
......
# frozen_string_literal: true
RSpec.shared_examples 'search query applies joins based on migrations shared examples' do |migration_name|
context 'using joins for global permission checks', :elastic do
let(:es_host) { Gitlab::CurrentSettings.elasticsearch_url[0] }
let(:search_url) { Addressable::Template.new("#{es_host}/{index}/doc/_search{?params*}") }
context "when #{migration_name} migration is finished" do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(migration_name)
.and_return(true)
end
it 'does not use joins to apply permissions' do
request = a_request(:get, search_url).with do |req|
expect(req.body).not_to include("has_parent")
end
results
expect(request).to have_been_made
end
end
context "when #{migration_name} migration is not finished" do
before do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(migration_name)
.and_return(false)
end
it 'uses joins to apply permissions' do
request = a_request(:get, search_url).with do |req|
expect(req.body).to include("has_parent")
end
results
expect(request).to have_been_made
end
end
end
end
......@@ -356,7 +356,7 @@ RSpec.shared_context 'ProjectPolicyTable context' do
:private | :anonymous | 0
end
# :snippet_level, :project_level, :feature_access_level, :membership, :expected_count
# :snippet_level, :project_level, :feature_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_project_snippet_access
:public | :public | :enabled | :admin | true | 1
:public | :public | :enabled | :admin | false | 1
......
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