mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 12:42:16 +08:00
FEATURE: Treat site settings as plain text and add a new HTML type. (#12618)
To add an extra layer of security, we sanitize settings before shipping them to the client. We don't sanitize those that have the "html" type. The CookedPostProcessor already uses Loofah for sanitization, so I chose to also use it for this. I added it to our gemfile since we installed it as a transitive dependency.
This commit is contained in:
parent
11e611f845
commit
5e4c0e2caa
1
Gemfile
1
Gemfile
|
@ -96,6 +96,7 @@ gem 'discourse_image_optim', require: 'image_optim'
|
||||||
gem 'multi_json'
|
gem 'multi_json'
|
||||||
gem 'mustache'
|
gem 'mustache'
|
||||||
gem 'nokogiri'
|
gem 'nokogiri'
|
||||||
|
gem 'loofah'
|
||||||
gem 'css_parser', require: false
|
gem 'css_parser', require: false
|
||||||
|
|
||||||
gem 'omniauth'
|
gem 'omniauth'
|
||||||
|
|
|
@ -525,6 +525,7 @@ DEPENDENCIES
|
||||||
logstash-event
|
logstash-event
|
||||||
logstash-logger
|
logstash-logger
|
||||||
logster
|
logster
|
||||||
|
loofah
|
||||||
lru_redux
|
lru_redux
|
||||||
lz4-ruby
|
lz4-ruby
|
||||||
mail!
|
mail!
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
{{text-field value=(html-safe value) classNames="input-setting-string"}}
|
||||||
|
<div class="desc">{{html-safe setting.description}}</div>
|
|
@ -6,7 +6,10 @@ class Admin::SiteSettingsController < Admin::AdminController
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render_json_dump(site_settings: SiteSetting.all_settings, diags: SiteSetting.diags)
|
render_json_dump(
|
||||||
|
site_settings: SiteSetting.all_settings(sanitize_plain_text_settings: true),
|
||||||
|
diags: SiteSetting.diags
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class SiteSettingsTask
|
class SiteSettingsTask
|
||||||
def self.export_to_hash(include_defaults: false, include_hidden: false)
|
def self.export_to_hash(include_defaults: false, include_hidden: false)
|
||||||
site_settings = SiteSetting.all_settings(include_hidden)
|
site_settings = SiteSetting.all_settings(include_hidden: include_hidden)
|
||||||
h = {}
|
h = {}
|
||||||
site_settings.each do |site_setting|
|
site_settings.each do |site_setting|
|
||||||
next if site_setting[:default] == site_setting[:value] if !include_defaults
|
next if site_setting[:default] == site_setting[:value] if !include_defaults
|
||||||
|
|
|
@ -148,7 +148,7 @@ class UserMerger
|
||||||
def update_site_settings
|
def update_site_settings
|
||||||
::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.updating_site_settings") }, user_ids: [@acting_user.id] if @acting_user
|
::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.updating_site_settings") }, user_ids: [@acting_user.id] if @acting_user
|
||||||
|
|
||||||
SiteSetting.all_settings(true).each do |setting|
|
SiteSetting.all_settings(include_hidden: true).each do |setting|
|
||||||
if setting[:type] == "username" && setting[:value] == @source_user.username
|
if setting[:type] == "username" && setting[:value] == @source_user.username
|
||||||
SiteSetting.set_and_log(setting[:setting], @target_user.username)
|
SiteSetting.set_and_log(setting[:setting], @target_user.username)
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
# type: username - Must match the username of an existing user.
|
# type: username - Must match the username of an existing user.
|
||||||
# type: list - A list of values, chosen from a set of valid values defined in the choices option.
|
# type: list - A list of values, chosen from a set of valid values defined in the choices option.
|
||||||
# type: enum - A single value, chosen from a set of valid values in the choices option.
|
# type: enum - A single value, chosen from a set of valid values in the choices option.
|
||||||
|
# type: html - A single value. It could contain HTML.
|
||||||
#
|
#
|
||||||
# A type:list setting with the word 'colors' in its name will make color values have a bold line of the corresponding color
|
# A type:list setting with the word 'colors' in its name will make color values have a bold line of the corresponding color
|
||||||
#
|
#
|
||||||
|
|
|
@ -208,14 +208,20 @@ module SiteSettingExtension
|
||||||
def client_settings_json_uncached
|
def client_settings_json_uncached
|
||||||
MultiJson.dump(Hash[*@client_settings.map do |name|
|
MultiJson.dump(Hash[*@client_settings.map do |name|
|
||||||
value = self.public_send(name)
|
value = self.public_send(name)
|
||||||
value = value.to_s if type_supervisor.get_type(name) == :upload
|
type = type_supervisor.get_type(name)
|
||||||
value = value.map(&:to_s).join("|") if type_supervisor.get_type(name) == :uploaded_image_list
|
value = value.to_s if type == :upload
|
||||||
|
value = value.map(&:to_s).join("|") if type == :uploaded_image_list
|
||||||
|
|
||||||
|
if should_sanitize?(value, type)
|
||||||
|
value = sanitize(value)
|
||||||
|
end
|
||||||
|
|
||||||
[name, value]
|
[name, value]
|
||||||
end.flatten])
|
end.flatten])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Retrieve all settings
|
# Retrieve all settings
|
||||||
def all_settings(include_hidden = false)
|
def all_settings(include_hidden: false, sanitize_plain_text_settings: false)
|
||||||
|
|
||||||
locale_setting_hash =
|
locale_setting_hash =
|
||||||
{
|
{
|
||||||
|
@ -244,6 +250,8 @@ module SiteSettingExtension
|
||||||
default.to_i < Upload::SEEDED_ID_THRESHOLD
|
default.to_i < Upload::SEEDED_ID_THRESHOLD
|
||||||
|
|
||||||
default = default_uploads[default.to_i]
|
default = default_uploads[default.to_i]
|
||||||
|
elsif sanitize_plain_text_settings && should_sanitize?(value, type_hash[:type].to_s)
|
||||||
|
value = sanitize(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
opts = {
|
opts = {
|
||||||
|
@ -574,6 +582,14 @@ module SiteSettingExtension
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def should_sanitize?(value, type)
|
||||||
|
value.is_a?(String) && type.to_s != 'html'
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitize(value)
|
||||||
|
CGI.unescapeHTML(Loofah.scrub_fragment(value, :strip).to_s)
|
||||||
|
end
|
||||||
|
|
||||||
def logger
|
def logger
|
||||||
Rails.logger
|
Rails.logger
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,7 +36,8 @@ class SiteSettings::TypeSupervisor
|
||||||
tag_list: 21,
|
tag_list: 21,
|
||||||
color: 22,
|
color: 22,
|
||||||
simple_list: 23,
|
simple_list: 23,
|
||||||
emoji_list: 24
|
emoji_list: 24,
|
||||||
|
html: 25
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -631,7 +631,7 @@ describe SiteSettingExtension do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is present in all_settings when we ask for hidden" do
|
it "is present in all_settings when we ask for hidden" do
|
||||||
expect(settings.all_settings(true).find { |s| s[:setting] == :superman_identity }).to be_present
|
expect(settings.all_settings(include_hidden: true).find { |s| s[:setting] == :superman_identity }).to be_present
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -842,6 +842,23 @@ describe SiteSettingExtension do
|
||||||
expect(setting[:default]).to eq(system_upload.url)
|
expect(setting[:default]).to eq(system_upload.url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should sanitize html in the site settings' do
|
||||||
|
settings.setting(:with_html, '<script></script>rest')
|
||||||
|
settings.refresh!
|
||||||
|
|
||||||
|
setting = settings.all_settings(sanitize_plain_text_settings: true).last
|
||||||
|
|
||||||
|
expect(setting[:value]).to eq('rest')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'settings with html type are not sanitized' do
|
||||||
|
settings.setting(:with_html, '<script></script>rest', type: :html)
|
||||||
|
|
||||||
|
setting = settings.all_settings(sanitize_plain_text_settings: true).last
|
||||||
|
|
||||||
|
expect(setting[:value]).to eq('<script></script>rest')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.client_settings_json_uncached' do
|
describe '.client_settings_json_uncached' do
|
||||||
|
@ -855,6 +872,27 @@ describe SiteSettingExtension do
|
||||||
%Q|{"default_locale":"#{SiteSetting.default_locale}","upload_type":"#{upload.url}","string_type":"haha"}|
|
%Q|{"default_locale":"#{SiteSetting.default_locale}","upload_type":"#{upload.url}","string_type":"haha"}|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should sanitize html in the site settings' do
|
||||||
|
settings.setting(:with_html, '<script></script>rest', client: true)
|
||||||
|
settings.setting(:with_symbols, '<>rest', client: true)
|
||||||
|
settings.setting(:with_unknown_tag, '<rest>rest', client: true)
|
||||||
|
settings.refresh!
|
||||||
|
|
||||||
|
client_settings = JSON.parse settings.client_settings_json_uncached
|
||||||
|
|
||||||
|
expect(client_settings['with_html']).to eq('rest')
|
||||||
|
expect(client_settings['with_symbols']).to eq('<>rest')
|
||||||
|
expect(client_settings['with_unknown_tag']).to eq('rest')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'settings with html type are not sanitized' do
|
||||||
|
settings.setting(:with_html, '<script></script>rest', type: :html, client: true)
|
||||||
|
|
||||||
|
client_settings = JSON.parse settings.client_settings_json_uncached
|
||||||
|
|
||||||
|
expect(client_settings['with_html']).to eq('<script></script>rest')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.setup_methods' do
|
describe '.setup_methods' do
|
||||||
|
|
|
@ -94,6 +94,9 @@ describe SiteSettings::TypeSupervisor do
|
||||||
it "'emoji_list' should be at the right position" do
|
it "'emoji_list' should be at the right position" do
|
||||||
expect(SiteSettings::TypeSupervisor.types[:emoji_list]).to eq(24)
|
expect(SiteSettings::TypeSupervisor.types[:emoji_list]).to eq(24)
|
||||||
end
|
end
|
||||||
|
it "'html' should be at the right position" do
|
||||||
|
expect(SiteSettings::TypeSupervisor.types[:html]).to eq(25)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user