Commit 08266ba0 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Use `Redactor` to hide cross project information

Since the redactor can be run on multiple documents at once and
query results are stored in the request store.
parent 148816cd
module Banzai
module Filter
# HTML filter that removes sensitive information from cross project
# issue references.
#
# The link to the issue or merge request is preserved only the IID is shown,
# but all other info is removed.
class CrossProjectIssuableInformationFilter < HTML::Pipeline::Filter
def call
return doc if can_read_cross_project?
extractor = Banzai::IssuableExtractor.new(project, current_user)
issuables = extractor.extract([doc])
issuables.each do |node, issuable|
next if issuable.project == project
node['class'] = node['class'].gsub('has-tooltip', '')
node['title'] = nil
end
doc
end
private
def project
context[:project]
end
def can_read_cross_project?
Ability.allowed?(current_user, :read_cross_project)
end
def current_user
context[:current_user]
end
end
end
end
......@@ -6,7 +6,6 @@ module Banzai
Filter::RedactorFilter,
Filter::RelativeLinkFilter,
Filter::IssuableStateFilter,
Filter::CrossProjectIssuableInformationFilter,
Filter::AbsoluteLinkFilter
]
end
......
......@@ -19,8 +19,9 @@ module Banzai
#
# Returns the documents passed as the first argument.
def redact(documents)
all_document_nodes = document_nodes(documents)
redact_cross_project_references(documents) unless can_read_cross_project?
all_document_nodes = document_nodes(documents)
redact_document_nodes(all_document_nodes)
end
......@@ -51,6 +52,18 @@ module Banzai
metadata
end
def redact_cross_project_references(documents)
extractor = Banzai::IssuableExtractor.new(project, user)
issuables = extractor.extract(documents)
issuables.each do |node, issuable|
next if issuable.project == project
node['class'] = node['class'].gsub('has-tooltip', '')
node['title'] = nil
end
end
# Returns the nodes visible to the current user.
#
# nodes - The input nodes to check.
......@@ -78,5 +91,11 @@ module Banzai
{ document: document, nodes: Querying.css(document, 'a.gfm[data-reference-type]') }
end
end
private
def can_read_cross_project?
Ability.allowed?(user, :read_cross_project)
end
end
end
require 'spec_helper'
describe Banzai::Filter::CrossProjectIssuableInformationFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:context) { { project: project, current_user: user } }
let(:other_project) { create(:project, :public) }
def create_link(issuable)
type = issuable.class.name.underscore.downcase
link_to(issuable.to_reference, '',
class: 'gfm has-tooltip',
title: issuable.title,
data: {
reference_type: type,
"#{type}": issuable.id
})
end
context 'when the user cannot read cross project' do
before do
allow(Ability).to receive(:allowed?) { false }
end
it 'skips links to issues within the same project' do
issue = create(:issue, project: project)
link = create_link(issue)
doc = filter(link, context)
result = doc.css('a').last
expect(result['class']).to include('has-tooltip')
expect(result['title']).to eq(issue.title)
end
it 'removes info from a cross project reference' do
issue = create(:issue, project: other_project)
link = create_link(issue)
doc = filter(link, context)
result = doc.css('a').last
expect(result['class']).not_to include('has-tooltip')
expect(result['title']).to be_empty
end
end
end
......@@ -6,7 +6,7 @@ describe Banzai::Filter::RedactorFilter do
it 'ignores non-GFM links' do
html = %(See <a href="https://google.com/">Google</a>)
doc = filter(html, current_user: double)
doc = filter(html, current_user: build(:user))
expect(doc.css('a').length).to eq 1
end
......
......@@ -88,6 +88,55 @@ describe Banzai::Redactor do
end
end
context 'when the user cannot read cross project' do
include ActionView::Helpers::UrlHelper
let(:project) { create(:project) }
let(:other_project) { create(:project, :public) }
def create_link(issuable)
type = issuable.class.name.underscore.downcase
link_to(issuable.to_reference, '',
class: 'gfm has-tooltip',
title: issuable.title,
data: {
reference_type: type,
"#{type}": issuable.id
})
end
before do
project.add_developer(user)
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global) { false }
allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
end
it 'skips links to issues within the same project' do
issue = create(:issue, project: project)
link = create_link(issue)
doc = Nokogiri::HTML.fragment(link)
redactor.redact([doc])
result = doc.css('a').last
expect(result['class']).to include('has-tooltip')
expect(result['title']).to eq(issue.title)
end
it 'removes info from a cross project reference' do
issue = create(:issue, project: other_project)
link = create_link(issue)
doc = Nokogiri::HTML.fragment(link)
redactor.redact([doc])
result = doc.css('a').last
expect(result['class']).not_to include('has-tooltip')
expect(result['title']).to be_empty
end
end
describe '#redact_nodes' do
it 'redacts an Array of nodes' do
doc = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
......
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