discourse/lib/validators/form_template_yaml_validato...

85 lines
2.4 KiB
Ruby

# frozen_string_literal: true
class FormTemplateYamlValidator < ActiveModel::Validator
RESERVED_KEYWORDS = %w[title body category category_id tags]
ALLOWED_TYPES = %w[checkbox dropdown input multi-select textarea upload]
HTML_SANITIZATION_OPTIONS = {
elements: ["a"],
attributes: {
"a" => %w[href target],
},
protocols: {
"a" => {
"href" => %w[http https mailto],
},
},
}
def validate(record)
begin
yaml = Psych.safe_load(record.template)
unless yaml.is_a?(Array)
record.errors.add(:template, I18n.t("form_templates.errors.invalid_yaml"))
return
end
existing_ids = []
yaml.each do |field|
check_missing_fields(record, field)
check_allowed_types(record, field)
check_ids(record, field, existing_ids)
check_descriptions_html(record, field)
end
rescue Psych::SyntaxError
record.errors.add(:template, I18n.t("form_templates.errors.invalid_yaml"))
end
end
def check_allowed_types(record, field)
if !ALLOWED_TYPES.include?(field["type"])
record.errors.add(
:template,
I18n.t(
"form_templates.errors.invalid_type",
type: field["type"],
valid_types: ALLOWED_TYPES.join(", "),
),
)
end
end
def check_missing_fields(record, field)
if field["type"].blank?
record.errors.add(:template, I18n.t("form_templates.errors.missing_type"))
end
record.errors.add(:template, I18n.t("form_templates.errors.missing_id")) if field["id"].blank?
end
def check_descriptions_html(record, field)
description = field.dig("attributes", "description")
return if description.blank?
sanitized_html = Sanitize.fragment(description, HTML_SANITIZATION_OPTIONS)
is_safe_html = sanitized_html == Loofah.html5_fragment(description).to_s
unless is_safe_html
record.errors.add(:template, I18n.t("form_templates.errors.unsafe_description"))
end
end
def check_ids(record, field, existing_ids)
if RESERVED_KEYWORDS.include?(field["id"])
record.errors.add(:template, I18n.t("form_templates.errors.reserved_id", id: field["id"]))
end
if existing_ids.include?(field["id"])
record.errors.add(:template, I18n.t("form_templates.errors.duplicate_ids"))
end
existing_ids << field["id"] if field["id"].present?
end
end