2018-10-06 19:10:08 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-06-21 07:35:09 -04:00
|
|
|
module Banzai
|
|
|
|
# Class for removing Markdown references a certain user is not allowed to
|
|
|
|
# view.
|
2019-07-16 16:19:08 -04:00
|
|
|
class ReferenceRedactor
|
2018-04-03 09:45:17 -04:00
|
|
|
attr_reader :context
|
2016-06-21 07:35:09 -04:00
|
|
|
|
2018-04-03 09:45:17 -04:00
|
|
|
# context - An instance of `Banzai::RenderContext`.
|
|
|
|
def initialize(context)
|
|
|
|
@context = context
|
|
|
|
end
|
|
|
|
|
|
|
|
def user
|
|
|
|
context.current_user
|
2016-06-21 07:35:09 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Redacts the references in the given Array of documents.
|
|
|
|
#
|
|
|
|
# This method modifies the given documents in-place.
|
|
|
|
#
|
|
|
|
# documents - A list of HTML documents containing references to redact.
|
|
|
|
#
|
|
|
|
# Returns the documents passed as the first argument.
|
|
|
|
def redact(documents)
|
2018-02-21 06:50:13 -05:00
|
|
|
redact_cross_project_references(documents) unless can_read_cross_project?
|
2016-06-21 07:35:09 -04:00
|
|
|
|
2018-02-21 06:50:13 -05:00
|
|
|
all_document_nodes = document_nodes(documents)
|
2016-07-04 01:31:43 -04:00
|
|
|
redact_document_nodes(all_document_nodes)
|
2016-06-21 07:35:09 -04:00
|
|
|
end
|
|
|
|
|
2016-07-04 01:31:43 -04:00
|
|
|
# Redacts the given node documents
|
2016-06-21 07:35:09 -04:00
|
|
|
#
|
2016-07-04 01:31:43 -04:00
|
|
|
# data - An Array of a Hashes mapping an HTML document to nodes to redact.
|
|
|
|
def redact_document_nodes(all_document_nodes)
|
2019-07-26 06:44:13 -04:00
|
|
|
all_nodes = all_document_nodes.flat_map { |x| x[:nodes] }
|
2016-07-04 01:31:43 -04:00
|
|
|
visible = nodes_visible_to_user(all_nodes)
|
|
|
|
metadata = []
|
2016-06-21 07:35:09 -04:00
|
|
|
|
2016-07-04 01:31:43 -04:00
|
|
|
all_document_nodes.each do |entry|
|
|
|
|
nodes_for_document = entry[:nodes]
|
2018-09-27 14:58:23 -04:00
|
|
|
|
|
|
|
doc_data = {
|
2022-08-12 11:11:42 -04:00
|
|
|
document: entry[:document],
|
|
|
|
total_reference_count: nodes_for_document.count,
|
2018-09-27 14:58:23 -04:00
|
|
|
visible_reference_count: nodes_for_document.count
|
|
|
|
}
|
|
|
|
|
2016-07-04 01:31:43 -04:00
|
|
|
metadata << doc_data
|
|
|
|
|
|
|
|
nodes_for_document.each do |node|
|
|
|
|
next if visible.include?(node)
|
|
|
|
|
|
|
|
doc_data[:visible_reference_count] -= 1
|
2018-02-26 10:28:49 -05:00
|
|
|
redacted_content = redacted_node_content(node)
|
|
|
|
node.replace(redacted_content)
|
2016-06-21 07:35:09 -04:00
|
|
|
end
|
|
|
|
end
|
2016-07-04 01:31:43 -04:00
|
|
|
|
|
|
|
metadata
|
2016-06-21 07:35:09 -04:00
|
|
|
end
|
|
|
|
|
2018-02-26 10:28:49 -05:00
|
|
|
# Return redacted content of given node as either the original link (<a> tag),
|
|
|
|
# the original content (text), or the inner HTML of the node.
|
|
|
|
#
|
|
|
|
def redacted_node_content(node)
|
|
|
|
original_content = node.attr('data-original')
|
2022-03-31 17:08:16 -04:00
|
|
|
original_content = CGI.escape_html(original_content) if original_content
|
2018-02-26 10:28:49 -05:00
|
|
|
|
|
|
|
# Build the raw <a> tag just with a link as href and content if
|
|
|
|
# it's originally a link pattern. We shouldn't return a plain text href.
|
|
|
|
original_link =
|
2022-03-31 17:08:16 -04:00
|
|
|
if node.attr('data-link-reference') == 'true'
|
2019-05-03 09:09:20 -04:00
|
|
|
href = node.attr('href')
|
|
|
|
|
2022-03-31 17:08:16 -04:00
|
|
|
%(<a href="#{href}">#{original_content}</a>)
|
2018-02-26 10:28:49 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# The reference should be replaced by the original link's content,
|
|
|
|
# which is not always the same as the rendered one.
|
|
|
|
original_link || original_content || node.inner_html
|
|
|
|
end
|
|
|
|
|
2018-02-21 06:50:13 -05:00
|
|
|
def redact_cross_project_references(documents)
|
2018-04-03 09:45:17 -04:00
|
|
|
extractor = Banzai::IssuableExtractor.new(context)
|
2018-02-21 06:50:13 -05:00
|
|
|
issuables = extractor.extract(documents)
|
|
|
|
|
|
|
|
issuables.each do |node, issuable|
|
2018-04-03 09:45:17 -04:00
|
|
|
next if issuable.project == context.project_for_node(node)
|
2018-02-21 06:50:13 -05:00
|
|
|
|
|
|
|
node['class'] = node['class'].gsub('has-tooltip', '')
|
|
|
|
node['title'] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-21 07:35:09 -04:00
|
|
|
# Returns the nodes visible to the current user.
|
|
|
|
#
|
|
|
|
# nodes - The input nodes to check.
|
|
|
|
#
|
|
|
|
# Returns a new Array containing the visible nodes.
|
|
|
|
def nodes_visible_to_user(nodes)
|
|
|
|
per_type = Hash.new { |h, k| h[k] = [] }
|
|
|
|
visible = Set.new
|
|
|
|
|
|
|
|
nodes.each do |node|
|
|
|
|
per_type[node.attr('data-reference-type')] << node
|
|
|
|
end
|
|
|
|
|
|
|
|
per_type.each do |type, nodes|
|
2018-04-03 09:45:17 -04:00
|
|
|
parser = Banzai::ReferenceParser[type].new(context)
|
2016-06-21 07:35:09 -04:00
|
|
|
|
|
|
|
visible.merge(parser.nodes_visible_to_user(user, nodes))
|
2020-10-16 14:09:04 -04:00
|
|
|
rescue Banzai::ReferenceParser::InvalidReferenceType
|
2016-06-21 07:35:09 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
visible
|
|
|
|
end
|
2016-07-04 01:31:43 -04:00
|
|
|
|
|
|
|
def document_nodes(documents)
|
|
|
|
documents.map do |document|
|
|
|
|
{ document: document, nodes: Querying.css(document, 'a.gfm[data-reference-type]') }
|
|
|
|
end
|
|
|
|
end
|
2018-02-21 06:50:13 -05:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def can_read_cross_project?
|
|
|
|
Ability.allowed?(user, :read_cross_project)
|
|
|
|
end
|
2016-06-21 07:35:09 -04:00
|
|
|
end
|
|
|
|
end
|