From f7d2fc05244d20c05e8c747c1e6ea66331d60d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= <regis@hanol.fr> Date: Fri, 6 Feb 2015 12:08:37 +0100 Subject: [PATCH] FEATURE: 'reply by email address' validator Prevent infinite email loophole when the 'reply_by_email_address' site setting is the same as the 'notification_email'. --- config/locales/server.en.yml | 1 + config/site_settings.yml | 21 ++++++------ lib/email/message_builder.rb | 5 +-- lib/site_setting_extension.rb | 11 +++++-- .../reply_by_email_address_validator.rb | 17 ++++++++++ .../reply_by_email_address_validator_spec.rb | 33 +++++++++++++++++++ 6 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 lib/validators/reply_by_email_address_validator.rb create mode 100644 spec/components/validators/reply_by_email_address_validator_spec.rb diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 611dd62af..51e279c77 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1096,6 +1096,7 @@ en: invalid_string_min_max: "Must be between %{min} and %{max} characters." invalid_string_min: "Must be at least %{min} characters." invalid_string_max: "Must be no more than %{max} characters." + invalid_reply_by_email_address: "Value must contain '%{reply_key}' and be different from the notification email." notification_types: mentioned: "%{display_username} mentioned you in %{link}" diff --git a/config/site_settings.yml b/config/site_settings.yml index 6967d21ab..733164d60 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1,13 +1,14 @@ # Available options: # -# default - The default value of the setting. -# client - Set to true if the javascript should have access to this setting's value. -# refresh - Set to true if clients should refresh when the setting is changed. -# min - For a string setting, the minimum length. For an integer setting, the minimum value. -# max - For a string setting, the maximum length. For an integer setting, the maximum value. -# regex - A regex that the value must match. -# enum - The setting has a fixed set of allowed values, and only one can be chosen. -# Set to the class name that defines the set. +# default - The default value of the setting. +# client - Set to true if the javascript should have access to this setting's value. +# refresh - Set to true if clients should refresh when the setting is changed. +# min - For a string setting, the minimum length. For an integer setting, the minimum value. +# max - For a string setting, the maximum length. For an integer setting, the maximum value. +# regex - A regex that the value must match. +# validator - The name of the class that will be use to validate the value of the setting. +# enum - The setting has a fixed set of allowed values, and only one can be chosen. +# Set to the class name that defines the set. # type: email - Must be a valid email address. # 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. @@ -423,7 +424,7 @@ email: reply_by_email_enabled: false reply_by_email_address: default: '' - regex: "%{reply_key}" + validator: "ReplyByEmailAddressValidator" pop3_polling_enabled: false pop3_polling_ssl: true pop3_polling_period_mins: 5 @@ -491,7 +492,7 @@ files: enum: 'S3RegionSiteSetting' s3_upload_bucket: default: '' - regex: "^[^\\.]*$" + regex: "^[^A-Z._]+$" allow_profile_backgrounds: client: true default: true diff --git a/lib/email/message_builder.rb b/lib/email/message_builder.rb index cff0e2b64..5e1428d96 100644 --- a/lib/email/message_builder.rb +++ b/lib/email/message_builder.rb @@ -146,10 +146,7 @@ module Email end def private_reply? - SiteSetting.reply_by_email_enabled? && - reply_by_email_address.present? && - @opts[:allow_reply_by_email] && - @opts[:private_reply] + allow_reply_by_email? && @opts[:private_reply] end def from_value diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index c9efa2ca3..9b87a8e18 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -72,18 +72,22 @@ module SiteSettingExtension self.defaults[name] = default categories[name] = opts[:category] || :uncategorized current_value = current.has_key?(name) ? current[name] : default + if opts[:enum] enum = opts[:enum] enums[name] = enum.is_a?(String) ? enum.constantize : enum end + if opts[:choices] choices.has_key?(name) ? choices[name].concat(opts[:choices]) : choices[name] = opts[:choices] end + if opts[:type] == 'list' lists << name end + if opts[:hidden] hidden_settings << name end @@ -105,8 +109,11 @@ module SiteSettingExtension previews[name] = opts[:preview] end - if validator_type = validator_for(opts[:type] || get_data_type(name, defaults[name])) - validators[name] = {class: validator_type, opts: opts} + opts[:validator] = opts[:validator].try(:constantize) + type = opts[:type] || get_data_type(name, defaults[name]) + + if validator_type = opts[:validator] || validator_for(type) + validators[name] = { class: validator_type, opts: opts } end current[name] = current_value diff --git a/lib/validators/reply_by_email_address_validator.rb b/lib/validators/reply_by_email_address_validator.rb new file mode 100644 index 000000000..f5c19d2bd --- /dev/null +++ b/lib/validators/reply_by_email_address_validator.rb @@ -0,0 +1,17 @@ +class ReplyByEmailAddressValidator + def initialize(opts={}) + @opts = opts + end + + def valid_value?(val) + return true if val.blank? + + !!(val =~ /@/i) && + !!(val =~ /%{reply_key}/i) && + val.gsub(/\+?%{reply_key}/i, "") != SiteSetting.notification_email + end + + def error_message + I18n.t('site_settings.errors.invalid_reply_by_email_address') + end +end diff --git a/spec/components/validators/reply_by_email_address_validator_spec.rb b/spec/components/validators/reply_by_email_address_validator_spec.rb new file mode 100644 index 000000000..81f6f24a0 --- /dev/null +++ b/spec/components/validators/reply_by_email_address_validator_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe ReplyByEmailAddressValidator do + + describe '#valid_value?' do + subject(:validator) { described_class.new } + + it "returns true for blank values" do + expect(validator.valid_value?('')).to eq(true) + expect(validator.valid_value?(nil)).to eq(true) + end + + it "returns false if value is not an email address" do + expect(validator.valid_value?('WAT%{reply_key}.com')).to eq(false) + end + + it "returns false if value does not contain '%{reply_key}'" do + expect(validator.valid_value?('foo@bar.com')).to eq(false) + end + + it "returns false if value is the same as SiteSetting.notification_email" do + SiteSetting.expects(:notification_email).returns("foo@bar.com") + expect(validator.valid_value?('foo+%{reply_key}@bar.com')).to eq(false) + end + + it "returns true when value is OK" do + SiteSetting.expects(:notification_email).returns("foo@bar.com") + expect(validator.valid_value?('bar%{reply_key}@foo.com')).to eq(true) + end + + end + +end