Commit 7fb9af87 authored by Jan Provaznik's avatar Jan Provaznik

Fix visibility reference check

When all references in a system note are checked, it's not reliable to
check if there was any not-visible reference returned by an individual
parser because that reference could be satisfied by another parser.

Instead we check if in the end all nodes were visible.

Changelog: fixed
parent 7a99c1e7
...@@ -643,7 +643,7 @@ class Note < ApplicationRecord ...@@ -643,7 +643,7 @@ class Note < ApplicationRecord
user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count
else else
refs = all_references(user) refs = all_references(user)
refs.all.any? && refs.stateful_not_visible_counter == 0 refs.all.any? && refs.all_visible?
end end
end end
......
...@@ -212,9 +212,8 @@ module Banzai ...@@ -212,9 +212,8 @@ module Banzai
def gather_references(nodes, ids_only: false) def gather_references(nodes, ids_only: false)
nodes = nodes_user_can_reference(current_user, nodes) nodes = nodes_user_can_reference(current_user, nodes)
visible = nodes_visible_to_user(current_user, nodes) visible = nodes_visible_to_user(current_user, nodes)
not_visible = nodes - visible
{ visible: referenced_by(visible, ids_only: ids_only), not_visible: not_visible } { visible: referenced_by(visible, ids_only: ids_only), nodes: nodes, visible_nodes: visible }
end end
# Returns a Hash containing the projects for a given list of HTML nodes. # Returns a Hash containing the projects for a given list of HTML nodes.
......
...@@ -6,16 +6,11 @@ module Gitlab ...@@ -6,16 +6,11 @@ module Gitlab
REFERABLES = %i(user issue label milestone mentioned_user mentioned_group mentioned_project REFERABLES = %i(user issue label milestone mentioned_user mentioned_group mentioned_project
merge_request snippet commit commit_range directly_addressed_user epic iteration vulnerability).freeze merge_request snippet commit commit_range directly_addressed_user epic iteration vulnerability).freeze
attr_accessor :project, :current_user, :author attr_accessor :project, :current_user, :author
# This counter is increased by a number of references filtered out by
# banzai reference exctractor. Note that this counter is stateful and
# not idempotent and is increased whenever you call `references`.
attr_reader :stateful_not_visible_counter
def initialize(project, current_user = nil) def initialize(project, current_user = nil)
@project = project @project = project
@current_user = current_user @current_user = current_user
@references = {} @references = {}
@stateful_not_visible_counter = 0
super() super()
end end
...@@ -26,14 +21,19 @@ module Gitlab ...@@ -26,14 +21,19 @@ module Gitlab
def references(type, ids_only: false) def references(type, ids_only: false)
refs = super(type, project, current_user, ids_only: ids_only) refs = super(type, project, current_user, ids_only: ids_only)
@stateful_not_visible_counter += refs[:not_visible].count update_visible_nodes_set(refs[:nodes], refs[:visible_nodes])
refs[:visible] refs[:visible]
end end
# this method is stateful, it tracks if all nodes from `references`
# calls are visible or not
def all_visible?
not_visible_nodes.empty?
end
def reset_memoized_values def reset_memoized_values
@references = {} @references = {}
@stateful_not_visible_counter = 0
super() super()
end end
...@@ -76,5 +76,16 @@ module Gitlab ...@@ -76,5 +76,16 @@ module Gitlab
@pattern = Regexp.union(patterns.compact) @pattern = Regexp.union(patterns.compact)
end end
private
def update_visible_nodes_set(all, visible)
not_visible_nodes.merge(all)
not_visible_nodes.subtract(visible)
end
def not_visible_nodes
@not_visible_nodes ||= Set.new
end
end end
end end
...@@ -247,15 +247,15 @@ RSpec.describe Banzai::ReferenceParser::BaseParser do ...@@ -247,15 +247,15 @@ RSpec.describe Banzai::ReferenceParser::BaseParser do
end end
end end
it 'returns referenceable and visible objects, alongside nodes that are referenceable but not visible' do it 'returns referenceable and visible objects, alongside all and visible nodes' do
expect(subject.gather_references(nodes)).to match( referenceable = nodes.select { |n| n.id.even? }
visible: contain_exactly(6, 8, 10), visible = nodes.select { |n| [6, 8, 10].include?(n.id) }
not_visible: match_array(nodes.select { |n| n.id.even? && n.id <= 5 })
) expect_gathered_references(subject.gather_references(nodes), [6, 8, 10], referenceable, visible)
end end
it 'is always empty if the input is empty' do it 'is always empty if the input is empty' do
expect(subject.gather_references([])) .to match(visible: be_empty, not_visible: be_empty) expect_gathered_references(subject.gather_references([]), [], [], [])
end end
end end
......
...@@ -19,7 +19,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do ...@@ -19,7 +19,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do
it 'returns empty array' do it 'returns empty array' do
link['data-group'] = project.group.id.to_s link['data-group'] = project.group.id.to_s
expect_gathered_references(subject.gather_references([link]), [], 1) expect_gathered_references(subject.gather_references([link]), [], [link], [])
end end
end end
...@@ -30,7 +30,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do ...@@ -30,7 +30,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do
end end
it 'returns groups' do it 'returns groups' do
expect_gathered_references(subject.gather_references([link]), [group], 0) expect_gathered_references(subject.gather_references([link]), [group], [link], [link])
end end
end end
...@@ -38,7 +38,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do ...@@ -38,7 +38,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do
it 'returns an empty Array' do it 'returns an empty Array' do
link['data-group'] = 'test-non-existing' link['data-group'] = 'test-non-existing'
expect_gathered_references(subject.gather_references([link]), [], 1) expect_gathered_references(subject.gather_references([link]), [], [link], [])
end end
end end
end end
......
...@@ -19,7 +19,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do ...@@ -19,7 +19,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do
it 'returns empty Array' do it 'returns empty Array' do
link['data-project'] = project.id.to_s link['data-project'] = project.id.to_s
expect_gathered_references(subject.gather_references([link]), [], 1) expect_gathered_references(subject.gather_references([link]), [], [link], [])
end end
end end
...@@ -30,7 +30,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do ...@@ -30,7 +30,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do
end end
it 'returns an Array of referenced projects' do it 'returns an Array of referenced projects' do
expect_gathered_references(subject.gather_references([link]), [project], 0) expect_gathered_references(subject.gather_references([link]), [project], [link], [link])
end end
end end
...@@ -38,7 +38,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do ...@@ -38,7 +38,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do
it 'returns an empty Array' do it 'returns an empty Array' do
link['data-project'] = 'inexisting-project-id' link['data-project'] = 'inexisting-project-id'
expect_gathered_references(subject.gather_references([link]), [], 1) expect_gathered_references(subject.gather_references([link]), [], [link], [])
end end
end end
end end
......
...@@ -22,7 +22,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedUserParser do ...@@ -22,7 +22,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedUserParser do
end end
it 'returns empty list of users' do it 'returns empty list of users' do
expect_gathered_references(subject.gather_references([link]), [], 0) expect_gathered_references(subject.gather_references([link]), [], [link], [link])
end end
end end
end end
...@@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedUserParser do ...@@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedUserParser do
end end
it 'returns empty list of users' do it 'returns empty list of users' do
expect_gathered_references(subject.gather_references([link]), [], 0) expect_gathered_references(subject.gather_references([link]), [], [link], [link])
end end
end end
end end
...@@ -44,7 +44,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedUserParser do ...@@ -44,7 +44,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedUserParser do
it 'returns an Array of users' do it 'returns an Array of users' do
link['data-user'] = user.id.to_s link['data-user'] = user.id.to_s
expect_gathered_references(subject.gather_references([link]), [user], 0) expect_gathered_references(subject.gather_references([link]), [user], [link], [link])
end end
end end
end end
......
...@@ -17,7 +17,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do ...@@ -17,7 +17,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do
it 'returns an Array of projects' do it 'returns an Array of projects' do
link['data-project'] = project.id.to_s link['data-project'] = project.id.to_s
expect_gathered_references(subject.gather_references([link]), [project], 0) expect_gathered_references(subject.gather_references([link]), [project], [link], [link])
end end
end end
...@@ -25,7 +25,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do ...@@ -25,7 +25,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do
it 'returns an empty Array' do it 'returns an empty Array' do
link['data-project'] = '' link['data-project'] = ''
expect_gathered_references(subject.gather_references([link]), [], 1) expect_gathered_references(subject.gather_references([link]), [], [link], [])
end end
end end
...@@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do ...@@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do
link['data-project'] = private_project.id.to_s link['data-project'] = private_project.id.to_s
expect_gathered_references(subject.gather_references([link]), [], 1) expect_gathered_references(subject.gather_references([link]), [], [link], [])
end end
it 'returns an Array when authorized' do it 'returns an Array when authorized' do
...@@ -43,7 +43,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do ...@@ -43,7 +43,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do
link['data-project'] = private_project.id.to_s link['data-project'] = private_project.id.to_s
expect_gathered_references(subject.gather_references([link]), [private_project], 0) expect_gathered_references(subject.gather_references([link]), [private_project], [link], [link])
end end
end end
end end
......
...@@ -332,14 +332,59 @@ RSpec.describe Gitlab::ReferenceExtractor do ...@@ -332,14 +332,59 @@ RSpec.describe Gitlab::ReferenceExtractor do
it 'returns visible references of given type' do it 'returns visible references of given type' do
expect(subject.references(:issue)).to eq([issue]) expect(subject.references(:issue)).to eq([issue])
end end
end
it 'does not increase stateful_not_visible_counter' do it 'does not return any references' do
expect { subject.references(:issue) }.not_to change { subject.stateful_not_visible_counter } expect(subject.references(:issue)).to be_empty
end end
end
describe '#all_visible?' do
let_it_be(:user) { create(:user) }
let_it_be(:project2) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:issue2) { create(:issue, project: project2) }
let(:text) { "Ref. #{issue.to_reference} and #{issue2.to_reference(project)}" }
subject { described_class.new(project, user) }
before do
subject.analyze(text)
end end
it 'increases stateful_not_visible_counter' do it 'returns true if no references were parsed yet' do
expect { subject.references(:issue) }.to change { subject.stateful_not_visible_counter }.by(1) expect(subject.all_visible?).to be_truthy
end
context 'when references was already called' do
let(:membership) { [] }
before do
membership.each { |p| p.add_developer(user) }
subject.references(:issue)
end
it 'returns false' do
expect(subject.all_visible?).to be_falsey
end
context 'when user can access only some references' do
let(:membership) { [project] }
it 'returns false' do
expect(subject.all_visible?).to be_falsey
end
end
context 'when user can access all references' do
let(:membership) { [project, project2] }
it 'returns true' do
expect(subject.all_visible?).to be_truthy
end
end
end end
end end
end end
...@@ -500,15 +500,15 @@ RSpec.describe Note do ...@@ -500,15 +500,15 @@ RSpec.describe Note do
let_it_be(:ext_issue) { create(:issue, project: ext_proj) } let_it_be(:ext_issue) { create(:issue, project: ext_proj) }
shared_examples "checks references" do shared_examples "checks references" do
it "returns true" do it "returns false" do
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy
end end
it "returns false" do it "returns true" do
expect(note.system_note_with_references_visible_for?(private_user)).to be_truthy expect(note.system_note_with_references_visible_for?(private_user)).to be_truthy
end end
it "returns false if user visible reference count set" do it "returns true if user visible reference count set" do
note.user_visible_reference_count = 1 note.user_visible_reference_count = 1
note.total_reference_count = 1 note.total_reference_count = 1
...@@ -516,7 +516,15 @@ RSpec.describe Note do ...@@ -516,7 +516,15 @@ RSpec.describe Note do
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_truthy expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_truthy
end end
it "returns true if ref count is 0" do it "returns false if user visible reference count set but does not match total reference count" do
note.user_visible_reference_count = 1
note.total_reference_count = 2
expect(note).not_to receive(:reference_mentionables)
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy
end
it "returns false if ref count is 0" do
note.user_visible_reference_count = 0 note.user_visible_reference_count = 0
expect(note).not_to receive(:reference_mentionables) expect(note).not_to receive(:reference_mentionables)
...@@ -562,13 +570,35 @@ RSpec.describe Note do ...@@ -562,13 +570,35 @@ RSpec.describe Note do
end end
it_behaves_like "checks references" it_behaves_like "checks references"
end
it "returns true if user visible reference count set and there is a private reference" do context "when there is a private issue and user reference" do
note.user_visible_reference_count = 1 let_it_be(:ext_issue2) { create(:issue, project: ext_proj) }
note.total_reference_count = 2
expect(note).not_to receive(:reference_mentionables) let(:note) do
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy create :note,
noteable: ext_issue2, project: ext_proj,
note: "mentioned in #{private_issue.to_reference(ext_proj)} and pinged user #{private_user.to_reference}",
system: true
end
it_behaves_like "checks references"
end
context "when there is a publicly visible user reference" do
let(:note) do
create :note,
noteable: ext_issue, project: ext_proj,
note: "mentioned in #{ext_proj.owner.to_reference}",
system: true
end
it "returns true for other users" do
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_truthy
end
it "returns true for anonymous users" do
expect(note.system_note_with_references_visible_for?(nil)).to be_truthy
end end
end end
end end
......
...@@ -5,9 +5,10 @@ module ReferenceParserHelpers ...@@ -5,9 +5,10 @@ module ReferenceParserHelpers
Nokogiri::HTML.fragment('<a></a>').children[0] Nokogiri::HTML.fragment('<a></a>').children[0]
end end
def expect_gathered_references(result, visible, not_visible_count) def expect_gathered_references(result, visible, nodes, visible_nodes)
expect(result[:visible]).to eq(visible) expect(result[:visible]).to eq(visible)
expect(result[:not_visible].count).to eq(not_visible_count) expect(result[:nodes]).to eq(nodes)
expect(result[:visible_nodes]).to eq(visible_nodes)
end end
RSpec.shared_examples 'no project N+1 queries' do RSpec.shared_examples 'no project N+1 queries' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment