Commit a3ae2537 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-reference-check' into 'master'

Fix reference visibility check

Closes #23

See merge request gitlab-org/security/gitlab!66
parents 9aff4f43 e852629d
...@@ -545,7 +545,8 @@ class Note < ApplicationRecord ...@@ -545,7 +545,8 @@ class Note < ApplicationRecord
# if they are not equal, then there are private/confidential references as well # if they are not equal, then there are private/confidential references as well
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
referenced_mentionables(user).any? refs = all_references(user)
refs.all.any? && refs.stateful_not_visible_counter == 0
end end
end end
......
---
title: Make sure that only system notes where all references are visible to user are exposed in GraphQL API.
merge_request:
author:
type: security
...@@ -201,12 +201,14 @@ module Banzai ...@@ -201,12 +201,14 @@ module Banzai
gather_references(nodes) gather_references(nodes)
end end
# Gathers the references for the given HTML nodes. # Gathers the references for the given HTML nodes. Returns visible
# references and a list of nodes which are not visible to the user
def gather_references(nodes) def gather_references(nodes)
nodes = nodes_user_can_reference(current_user, nodes) nodes = nodes_user_can_reference(current_user, nodes)
nodes = nodes_visible_to_user(current_user, nodes) visible = nodes_visible_to_user(current_user, nodes)
not_visible = nodes - visible
referenced_by(nodes) { visible: referenced_by(visible), not_visible: not_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,11 +6,16 @@ module Gitlab ...@@ -6,11 +6,16 @@ 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).freeze merge_request snippet commit commit_range directly_addressed_user epic).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
...@@ -20,11 +25,15 @@ module Gitlab ...@@ -20,11 +25,15 @@ module Gitlab
end end
def references(type) def references(type)
super(type, project, current_user) refs = super(type, project, current_user)
@stateful_not_visible_counter += refs[:not_visible].count
refs[:visible]
end end
def reset_memoized_values def reset_memoized_values
@references = {} @references = {}
@stateful_not_visible_counter = 0
super() super()
end end
......
...@@ -19,7 +19,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do ...@@ -19,7 +19,7 @@ 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(subject.gather_references([link])).to eq([]) expect_gathered_references(subject.gather_references([link]), [], 1)
end end
end end
...@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do ...@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do
end end
it 'returns groups' do it 'returns groups' do
expect(subject.gather_references([link])).to eq([group]) expect_gathered_references(subject.gather_references([link]), [group], 0)
end end
end end
...@@ -38,7 +38,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do ...@@ -38,7 +38,7 @@ 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(subject.gather_references([link])).to eq([]) expect_gathered_references(subject.gather_references([link]), [], 1)
end end
end end
end end
......
...@@ -19,7 +19,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do ...@@ -19,7 +19,7 @@ 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(subject.gather_references([link])).to eq([]) expect_gathered_references(subject.gather_references([link]), [], 1)
end end
end end
...@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do ...@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do
end end
it 'returns an Array of referenced projects' do it 'returns an Array of referenced projects' do
expect(subject.gather_references([link])).to eq([project]) expect_gathered_references(subject.gather_references([link]), [project], 0)
end end
end end
...@@ -38,7 +38,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do ...@@ -38,7 +38,7 @@ 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(subject.gather_references([link])).to eq([]) expect_gathered_references(subject.gather_references([link]), [], 1)
end end
end end
end end
......
...@@ -22,7 +22,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do ...@@ -22,7 +22,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do
end end
it 'returns empty list of users' do it 'returns empty list of users' do
expect(subject.gather_references([link])).to eq([]) expect_gathered_references(subject.gather_references([link]), [], 0)
end end
end end
end end
...@@ -35,7 +35,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do ...@@ -35,7 +35,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do
end end
it 'returns empty list of users' do it 'returns empty list of users' do
expect(subject.gather_references([link])).to eq([]) expect_gathered_references(subject.gather_references([link]), [], 0)
end end
end end
end end
...@@ -44,7 +44,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do ...@@ -44,7 +44,7 @@ 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(subject.referenced_by([link])).to eq([user]) expect_gathered_references(subject.gather_references([link]), [user], 0)
end end
end end
end end
......
...@@ -17,7 +17,7 @@ describe Banzai::ReferenceParser::ProjectParser do ...@@ -17,7 +17,7 @@ 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(subject.gather_references([link])).to eq([project]) expect_gathered_references(subject.gather_references([link]), [project], 0)
end end
end end
...@@ -25,7 +25,7 @@ describe Banzai::ReferenceParser::ProjectParser do ...@@ -25,7 +25,7 @@ describe Banzai::ReferenceParser::ProjectParser do
it 'returns an empty Array' do it 'returns an empty Array' do
link['data-project'] = '' link['data-project'] = ''
expect(subject.gather_references([link])).to eq([]) expect_gathered_references(subject.gather_references([link]), [], 1)
end end
end end
...@@ -35,7 +35,7 @@ describe Banzai::ReferenceParser::ProjectParser do ...@@ -35,7 +35,7 @@ describe Banzai::ReferenceParser::ProjectParser do
link['data-project'] = private_project.id.to_s link['data-project'] = private_project.id.to_s
expect(subject.gather_references([link])).to eq([]) expect_gathered_references(subject.gather_references([link]), [], 1)
end end
it 'returns an Array when authorized' do it 'returns an Array when authorized' do
...@@ -43,7 +43,7 @@ describe Banzai::ReferenceParser::ProjectParser do ...@@ -43,7 +43,7 @@ describe Banzai::ReferenceParser::ProjectParser do
link['data-project'] = private_project.id.to_s link['data-project'] = private_project.id.to_s
expect(subject.gather_references([link])).to eq([private_project]) expect_gathered_references(subject.gather_references([link]), [private_project], 0)
end end
end end
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::ReferenceExtractor do describe Gitlab::ReferenceExtractor do
let(:project) { create(:project) } let_it_be(:project) { create(:project) }
before do before do
project.add_developer(project.creator) project.add_developer(project.creator)
...@@ -293,4 +293,34 @@ describe Gitlab::ReferenceExtractor do ...@@ -293,4 +293,34 @@ describe Gitlab::ReferenceExtractor do
end end
end end
end end
describe '#references' do
let_it_be(:user) { create(:user) }
let_it_be(:issue) { create(:issue, project: project) }
let(:text) { "Ref. #{issue.to_reference}" }
subject { described_class.new(project, user) }
before do
subject.analyze(text)
end
context 'when references are visible' do
before do
project.add_developer(user)
end
it 'returns visible references of given type' do
expect(subject.references(:issue)).to eq([issue])
end
it 'does not increase stateful_not_visible_counter' do
expect { subject.references(:issue) }.not_to change { subject.stateful_not_visible_counter }
end
end
it 'increases stateful_not_visible_counter' do
expect { subject.references(:issue) }.to change { subject.stateful_not_visible_counter }.by(1)
end
end
end end
...@@ -350,12 +350,12 @@ describe Note do ...@@ -350,12 +350,12 @@ describe Note do
end end
describe "cross_reference_not_visible_for?" do describe "cross_reference_not_visible_for?" do
let(:private_user) { create(:user) } let_it_be(:private_user) { create(:user) }
let(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_maintainer(private_user) } } let_it_be(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_maintainer(private_user) } }
let(:private_issue) { create(:issue, project: private_project) } let_it_be(:private_issue) { create(:issue, project: private_project) }
let(:ext_proj) { create(:project, :public) } let_it_be(:ext_proj) { create(:project, :public) }
let(: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 true" do
...@@ -393,10 +393,24 @@ describe Note do ...@@ -393,10 +393,24 @@ describe Note do
it_behaves_like "checks references" it_behaves_like "checks references"
end end
context "when there are two references in note" do context "when there is a reference to a label" do
let_it_be(:private_label) { create(:label, project: private_project) }
let(:note) do let(:note) do
create :note, create :note,
noteable: ext_issue, project: ext_proj, noteable: ext_issue, project: ext_proj,
note: "added label #{private_label.to_reference(ext_proj)}",
system: true
end
let!(:system_note_metadata) { create(:system_note_metadata, note: note, action: :label) }
it_behaves_like "checks references"
end
context "when there are two references in note" do
let_it_be(:ext_issue2) { create(:issue, project: ext_proj) }
let(:note) do
create :note,
noteable: ext_issue2, project: ext_proj,
note: "mentioned in issue #{private_issue.to_reference(ext_proj)} and " \ note: "mentioned in issue #{private_issue.to_reference(ext_proj)} and " \
"public issue #{ext_issue.to_reference(ext_proj)}", "public issue #{ext_issue.to_reference(ext_proj)}",
system: true system: true
......
...@@ -5,6 +5,11 @@ module ReferenceParserHelpers ...@@ -5,6 +5,11 @@ 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)
expect(result[:visible]).to eq(visible)
expect(result[:not_visible].count).to eq(not_visible_count)
end
shared_examples 'no project N+1 queries' do shared_examples 'no project N+1 queries' do
it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
context = Banzai::RenderContext.new(project, user) context = Banzai::RenderContext.new(project, user)
......
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