1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
# frozen_string_literal: true
module Banzai
module Filter
# HTML filter that removes embeded elements that the current user does
# not have permission to view.
class InlineMetricsRedactorFilter < HTML::Pipeline::Filter
include Gitlab::Utils::StrongMemoize
METRICS_CSS_CLASS = '.js-render-metrics'
XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(METRICS_CSS_CLASS).freeze
EMBED_LIMIT = 100
Route = Struct.new(:regex, :permission)
Embed = Struct.new(:project_path, :permission)
# Finds all embeds based on the css class the FE
# uses to identify the embedded content, removing
# only unnecessary nodes.
def call
nodes.each do |node|
embed = embeds_by_node[node]
user_has_access = user_access_by_embed[embed]
node.remove unless user_has_access
end
doc
end
private
def user
context[:current_user]
end
# Returns all nodes which the FE will identify as
# a metrics embed placeholder element
#
# Removes any nodes beyond the first 100
#
# @return [Nokogiri::XML::NodeSet]
def nodes
strong_memoize(:nodes) do
nodes = doc.xpath(XPATH)
nodes.drop(EMBED_LIMIT).each(&:remove)
nodes
end
end
# Maps a node to key properties of an embed.
# Memoized so we only need to run the regex to get
# the project full path from the url once per node.
#
# @return [Hash<Nokogiri::XML::Node, Embed>]
def embeds_by_node
strong_memoize(:embeds_by_node) do
nodes.each_with_object({}) do |node, embeds|
embed = Embed.new
url = node.attribute('data-dashboard-url').to_s
permissions_by_route.each do |route|
set_path_and_permission(embed, url, route.regex, route.permission) unless embed.permission
end
embeds[node] = embed if embed.permission
end
end
end
def permissions_by_route
[
Route.new(
::Gitlab::Metrics::Dashboard::Url.metrics_regex,
:read_environment
),
Route.new(
::Gitlab::Metrics::Dashboard::Url.grafana_regex,
:read_project
),
Route.new(
::Gitlab::Metrics::Dashboard::Url.clusters_regex,
:read_cluster
),
Route.new(
::Gitlab::Metrics::Dashboard::Url.alert_regex,
:read_prometheus_alerts
)
]
end
# Attempts to determine the path and permission attributes
# of a url based on expected dashboard url formats and
# sets the attributes on an Embed object
#
# @param embed [Embed]
# @param url [String]
# @param regex [RegExp]
# @param permission [Symbol]
def set_path_and_permission(embed, url, regex, permission)
return unless path = regex.match(url) do |m|
"#{$~[:namespace]}/#{$~[:project]}"
end
embed.project_path = path
embed.permission = permission
end
# Returns a mapping representing whether the current user
# has permission to view the embed for the project.
# Determined in a batch
#
# @return [Hash<Embed, Boolean>]
def user_access_by_embed
strong_memoize(:user_access_by_embed) do
unique_embeds.each_with_object({}) do |embed, access|
project = projects_by_path[embed.project_path]
access[embed] = Ability.allowed?(user, embed.permission, project)
end
end
end
# Returns a unique list of embeds
#
# @return [Array<Embed>]
def unique_embeds
embeds_by_node.values.uniq
end
# Maps a project's full path to a Project object.
# Contains all of the Projects referenced in the
# metrics placeholder elements of the current document
#
# @return [Hash<String, Project>]
def projects_by_path
strong_memoize(:projects_by_path) do
Project.eager_load(:route, namespace: [:route])
.where_full_path_in(unique_project_paths)
.index_by(&:full_path)
end
end
# Returns a list of the full_paths of every project which
# has an embed in the doc
#
# @return [Array<String>]
def unique_project_paths
embeds_by_node.values.map(&:project_path).uniq
end
end
end
end
|