DEV: Sanitize HTML admin inputs (#14681)

* DEV: Sanitize HTML admin inputs

This PR adds on-save HTML sanitization for:

Client site settings
translation overrides
badges descriptions
user fields descriptions

I used Rails's SafeListSanitizer, which [accepts the following HTML tags and attributes](018cf54073/lib/rails/html/sanitizer.rb (L108))

* Make sure that the sanitization logic doesn't corrupt settings with special characters
This commit is contained in:
Roman Rizzi
2021-10-27 11:33:07 -03:00
committed by GitHub
parent 184ccf4490
commit df3eb93973
10 changed files with 104 additions and 10 deletions

View File

@@ -5,6 +5,7 @@ class Badge < ActiveRecord::Base
self.ignored_columns = %w{image}
include GlobalPath
include HasSanitizableFields
# NOTE: These badge ids are not in order! They are grouped logically.
# When picking an id, *search* for it.
@@ -116,6 +117,7 @@ class Badge < ActiveRecord::Base
scope :enabled, -> { where(enabled: true) }
before_create :ensure_not_system
before_save :sanitize_description
after_commit do
SvgSprite.expire_cache
@@ -314,6 +316,12 @@ class Badge < ActiveRecord::Base
def ensure_not_system
self.id = [Badge.maximum(:id) + 1, 100].max unless id
end
def sanitize_description
if description_changed?
self.description = sanitize_field(self.description)
end
end
end
# == Schema Information

View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true
module HasSanitizableFields
extend ActiveSupport::Concern
def sanitize_field(field, additional_attributes: [])
if field
sanitizer = Rails::Html::SafeListSanitizer.new
allowed_attributes = Rails::Html::SafeListSanitizer.allowed_attributes
if additional_attributes.present?
allowed_attributes = allowed_attributes.merge(additional_attributes)
end
field = CGI.unescape_html(sanitizer.sanitize(field, attributes: allowed_attributes))
# Just replace the characters that our translations use for interpolation.
# Calling CGI.unescape removes characters like '+', which will corrupt the original value.
field = field.gsub('%7B', '{').gsub('%7D', '}')
end
field
end
end

View File

@@ -39,6 +39,7 @@ class TranslationOverride < ActiveRecord::Base
}
}
include HasSanitizableFields
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
deprecate_constant 'CUSTOM_INTERPOLATION_KEYS_WHITELIST', 'TranslationOverride::ALLOWED_CUSTOM_INTERPOLATION_KEYS'
@@ -50,13 +51,15 @@ class TranslationOverride < ActiveRecord::Base
def self.upsert!(locale, key, value)
params = { locale: locale, translation_key: key }
data = { value: value }
translation_override = find_or_initialize_by(params)
sanitized_value = translation_override.sanitize_field(value, additional_attributes: ['data-auto-route'])
data = { value: sanitized_value }
if key.end_with?('_MF')
_, filename = JsLocaleHelper.find_message_format_locale([locale], fallback_to_english: false)
data[:compiled_js] = JsLocaleHelper.compile_message_format(filename, locale, value)
data[:compiled_js] = JsLocaleHelper.compile_message_format(filename, locale, sanitized_value)
end
translation_override = find_or_initialize_by(params)
params.merge!(data) if translation_override.new_record?
i18n_changed(locale, [key]) if translation_override.update(data)
translation_override
@@ -125,7 +128,6 @@ class TranslationOverride < ActiveRecord::Base
if original_text
original_interpolation_keys = I18nInterpolationKeysFinder.find(original_text)
new_interpolation_keys = I18nInterpolationKeysFinder.find(value)
custom_interpolation_keys = []
ALLOWED_CUSTOM_INTERPOLATION_KEYS.select do |keys, value|

View File

@@ -3,6 +3,7 @@
class UserField < ActiveRecord::Base
include AnonCacheInvalidator
include HasSanitizableFields
validates_presence_of :description, :field_type
validates_presence_of :name, unless: -> { field_type == "confirm" }
@@ -10,6 +11,7 @@ class UserField < ActiveRecord::Base
has_one :directory_column, dependent: :destroy
accepts_nested_attributes_for :user_field_options
before_save :sanitize_description
after_save :queue_index_search
def self.max_length
@@ -19,6 +21,14 @@ class UserField < ActiveRecord::Base
def queue_index_search
SearchIndexer.queue_users_reindex(UserCustomField.where(name: "user_field_#{self.id}").pluck(:user_id))
end
private
def sanitize_description
if description_changed?
self.description = sanitize_field(self.description)
end
end
end
# == Schema Information