diff --git a/config/locales/en.yml b/config/locales/en.yml
index 719adf8141d..bb3db89ccb7 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -4359,6 +4359,7 @@ en:
label_total_days_off: "Total days off"
macro_execution_error: "Error executing the macro %{macro_name}"
macro_unavailable: "Macro %{macro_name} cannot be displayed."
+ macro_unknown: "Unknown or unsupported macro."
macros:
placeholder: "[Placeholder] Macro %{macro_name}"
errors:
diff --git a/lib/open_project/text_formatting/filters/macro_filter.rb b/lib/open_project/text_formatting/filters/macro_filter.rb
index 8f2699efd01..b6a30765918 100644
--- a/lib/open_project/text_formatting/filters/macro_filter.rb
+++ b/lib/open_project/text_formatting/filters/macro_filter.rb
@@ -41,9 +41,13 @@ module OpenProject::TextFormatting
def call # rubocop:disable Metrics/AbcSize
doc.search("macro").each do |macro|
+ matched = false
+
registered.each do |macro_class|
next unless macro_applies?(macro_class, macro)
+ matched = true
+
# If requested to skip macro expansion, do that
if context[:disable_macro_expansion]
macro.replace macro_placeholder(macro_class)
@@ -60,6 +64,8 @@ module OpenProject::TextFormatting
break
end
end
+
+ macro.replace unknown_macro_placeholder unless matched
end
doc
@@ -67,6 +73,13 @@ module OpenProject::TextFormatting
private
+ def unknown_macro_placeholder
+ ApplicationController.helpers.content_tag :macro,
+ I18n.t(:macro_unknown),
+ class: "macro-unavailable",
+ data: { macro_name: "unknown" }
+ end
+
def macro_error_placeholder(macro_class, message)
ApplicationController.helpers.content_tag :macro,
"#{I18n.t(:macro_execution_error,
diff --git a/lib/open_project/text_formatting/filters/sanitization_filter.rb b/lib/open_project/text_formatting/filters/sanitization_filter.rb
index c891ebf9ad6..8e9b775920b 100644
--- a/lib/open_project/text_formatting/filters/sanitization_filter.rb
+++ b/lib/open_project/text_formatting/filters/sanitization_filter.rb
@@ -52,8 +52,8 @@ module OpenProject::TextFormatting
remove_contents: Array(base[:remove_contents]) | %w[svg style],
attributes: base_attrs.deep_merge(
- # Whitelist class and data-* attributes on all macros
- "macro" => ["class", :data],
+ # Explicit allowlist of data-* attributes used by registered macros.
+ "macro" => %w[class data-type data-classes data-page data-include-parent data-macro-name data-query-props data-pull-request-id data-pull-request-state],
# mentions
"mention" => %w[data-type data-text data-id class],
# add styles to tables
diff --git a/spec/lib/open_project/text_formatting/filters/macro_attribute_handling_spec.rb b/spec/lib/open_project/text_formatting/filters/macro_attribute_handling_spec.rb
new file mode 100644
index 00000000000..ef846b2b775
--- /dev/null
+++ b/spec/lib/open_project/text_formatting/filters/macro_attribute_handling_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+#-- copyright
+# OpenProject is an open source project management software.
+# Copyright (C) the OpenProject GmbH
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# See COPYRIGHT and LICENSE files for more details.
+#++
+
+require "spec_helper"
+
+RSpec.describe "macro element attribute handling" do # rubocop:disable RSpec/DescribeClass
+ def sanitize(html)
+ filter = OpenProject::TextFormatting::Filters::SanitizationFilter.new(html, {})
+ result = filter.call
+ result.respond_to?(:to_html) ? result.to_html : result.to_s
+ end
+
+ def apply_macro_filter(html)
+ filter = OpenProject::TextFormatting::Filters::MacroFilter.new(html, {})
+ result = filter.call
+ result.respond_to?(:to_html) ? result.to_html : result.to_s
+ end
+
+ describe OpenProject::TextFormatting::Filters::SanitizationFilter do
+ describe "macro element data attribute restrictions" do
+ it "strips data-controller from macro elements" do
+ html = '
text