SECURITY: ERB execution in custom Email Style
This commit is contained in:
parent
5a71c51ddd
commit
d11c462104
|
@ -25,12 +25,8 @@ module EmailHelper
|
||||||
raw "<a href='#{Discourse.base_url}#{url}' style='color: ##{@anchor_color}'>#{title}</a>"
|
raw "<a href='#{Discourse.base_url}#{url}' style='color: ##{@anchor_color}'>#{title}</a>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_html_template(binding_arg)
|
def email_html_template
|
||||||
template = EmailStyle.new.html.sub(
|
EmailStyle.new.html.sub('%{email_content}', yield).html_safe
|
||||||
'%{email_content}',
|
|
||||||
'<%= yield %><% if defined?(html_body) %><%= html_body %><% end %>'
|
|
||||||
)
|
|
||||||
ERB.new(template).result(binding_arg)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
|
@ -2,5 +2,8 @@
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
<% if defined?(html_body) %><%= html_body %><% end %>
|
<% if defined?(html_body) %><%= html_body %><% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= email_html_template(binding).html_safe %>
|
<%= email_html_template do %>
|
||||||
|
<%= yield %>
|
||||||
|
<% if defined?(html_body) %><%= html_body %><% end %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -3,128 +3,139 @@
|
||||||
require "rails_helper"
|
require "rails_helper"
|
||||||
|
|
||||||
describe EmailStyle do
|
describe EmailStyle do
|
||||||
before do
|
|
||||||
SiteSetting.email_custom_template = "<body><h1>FOR YOU</h1><div>%{email_content}</div></body>"
|
|
||||||
SiteSetting.email_custom_css = 'h1 { color: red; } div.body { color: #FAB; }'
|
|
||||||
SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
context "ERB evaluation" do
|
||||||
SiteSetting.remove_override!(:email_custom_template)
|
it "does not evaluate ERB outside of the email itself" do
|
||||||
SiteSetting.remove_override!(:email_custom_css)
|
SiteSetting.email_custom_template = "<div>%{email_content}</div><%= (111 * 333) %>"
|
||||||
end
|
html = Email::Renderer.new(UserNotifications.signup(Fabricate(:user))).html
|
||||||
|
expect(html).not_to match("36963")
|
||||||
context 'invite' do
|
|
||||||
fab!(:invite) { Fabricate(:invite) }
|
|
||||||
let(:invite_mail) { InviteMailer.send_invite(invite) }
|
|
||||||
|
|
||||||
subject(:mail_html) { Email::Renderer.new(invite_mail).html }
|
|
||||||
|
|
||||||
it 'applies customizations' do
|
|
||||||
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
|
||||||
expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}")
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'applies customizations if compiled is missing' do
|
|
||||||
SiteSetting.remove_override!(:email_custom_css_compiled)
|
|
||||||
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
|
||||||
expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}")
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'can apply RTL attrs' do
|
|
||||||
SiteSetting.default_locale = 'he'
|
|
||||||
body_attrs = mail_html.match(/<body ([^>])+/)
|
|
||||||
expect(body_attrs[0]&.downcase).to match(/text-align:\s*right/)
|
|
||||||
expect(body_attrs[0]&.downcase).to include('dir="rtl"')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'user_replied' do
|
context "with a custom template" do
|
||||||
let(:response_by_user) { Fabricate(:user, name: "John Doe") }
|
before do
|
||||||
let(:category) { Fabricate(:category, name: 'India') }
|
SiteSetting.email_custom_template = "<body><h1>FOR YOU</h1><div>%{email_content}</div></body>"
|
||||||
let(:topic) { Fabricate(:topic, category: category, title: "Super cool topic") }
|
SiteSetting.email_custom_css = 'h1 { color: red; } div.body { color: #FAB; }'
|
||||||
let(:post) { Fabricate(:post, topic: topic, raw: 'This is My super duper cool topic') }
|
SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css
|
||||||
let(:response) { Fabricate(:basic_reply, topic: post.topic, user: response_by_user) }
|
|
||||||
let(:user) { Fabricate(:user) }
|
|
||||||
let(:notification) { Fabricate(:replied_notification, user: user, post: response) }
|
|
||||||
|
|
||||||
let(:mail) do
|
|
||||||
UserNotifications.user_replied(
|
|
||||||
user,
|
|
||||||
post: response,
|
|
||||||
notification_type: notification.notification_type,
|
|
||||||
notification_data_hash: notification.data_hash
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subject(:mail_html) { Email::Renderer.new(mail).html }
|
after do
|
||||||
|
SiteSetting.remove_override!(:email_custom_template)
|
||||||
it "customizations are applied to html part of emails" do
|
SiteSetting.remove_override!(:email_custom_css)
|
||||||
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
|
||||||
matches = mail_html.match(/<div style="([^"]+)">#{post.raw}/)
|
|
||||||
expect(matches[1]).to include('color: #FAB;') # custom
|
|
||||||
expect(matches[1]).to include('padding-top:5px;') # div.body
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: translation override
|
context 'invite' do
|
||||||
end
|
fab!(:invite) { Fabricate(:invite) }
|
||||||
|
let(:invite_mail) { InviteMailer.send_invite(invite) }
|
||||||
|
|
||||||
context 'signup' do
|
subject(:mail_html) { Email::Renderer.new(invite_mail).html }
|
||||||
let(:signup_mail) { UserNotifications.signup(Fabricate(:user)) }
|
|
||||||
subject(:mail_html) { Email::Renderer.new(signup_mail).html }
|
|
||||||
|
|
||||||
it "customizations are applied to html part of emails" do
|
it 'applies customizations' do
|
||||||
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
||||||
expect(mail_html).to include('activate-account')
|
expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'applies customizations if compiled is missing' do
|
||||||
|
SiteSetting.remove_override!(:email_custom_css_compiled)
|
||||||
|
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
||||||
|
expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can apply RTL attrs' do
|
||||||
|
SiteSetting.default_locale = 'he'
|
||||||
|
body_attrs = mail_html.match(/<body ([^>])+/)
|
||||||
|
expect(body_attrs[0]&.downcase).to match(/text-align:\s*right/)
|
||||||
|
expect(body_attrs[0]&.downcase).to include('dir="rtl"')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'translation override' do
|
context 'user_replied' do
|
||||||
before do
|
let(:response_by_user) { Fabricate(:user, name: "John Doe") }
|
||||||
TranslationOverride.upsert!(
|
let(:category) { Fabricate(:category, name: 'India') }
|
||||||
'en',
|
let(:topic) { Fabricate(:topic, category: category, title: "Super cool topic") }
|
||||||
'user_notifications.signup.text_body_template',
|
let(:post) { Fabricate(:post, topic: topic, raw: 'This is My super duper cool topic') }
|
||||||
"CLICK THAT LINK: %{base_url}/u/activate-account/%{email_token}"
|
let(:response) { Fabricate(:basic_reply, topic: post.topic, user: response_by_user) }
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
let(:notification) { Fabricate(:replied_notification, user: user, post: response) }
|
||||||
|
|
||||||
|
let(:mail) do
|
||||||
|
UserNotifications.user_replied(
|
||||||
|
user,
|
||||||
|
post: response,
|
||||||
|
notification_type: notification.notification_type,
|
||||||
|
notification_data_hash: notification.data_hash
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
after do
|
subject(:mail_html) { Email::Renderer.new(mail).html }
|
||||||
TranslationOverride.revert!('en', ['user_notifications.signup.text_body_template'])
|
|
||||||
|
it "customizations are applied to html part of emails" do
|
||||||
|
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
||||||
|
matches = mail_html.match(/<div style="([^"]+)">#{post.raw}/)
|
||||||
|
expect(matches[1]).to include('color: #FAB;') # custom
|
||||||
|
expect(matches[1]).to include('padding-top:5px;') # div.body
|
||||||
end
|
end
|
||||||
|
|
||||||
it "applies customizations when translation override exists" do
|
# TODO: translation override
|
||||||
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
|
||||||
expect(mail_html.scan('CLICK THAT LINK').count).to eq(1)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with some bad css' do
|
context 'signup' do
|
||||||
before do
|
let(:signup_mail) { UserNotifications.signup(Fabricate(:user)) }
|
||||||
SiteSetting.email_custom_css = '@import "nope.css"; h1 {{{ size: really big; '
|
subject(:mail_html) { Email::Renderer.new(signup_mail).html }
|
||||||
SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can render the html" do
|
it "customizations are applied to html part of emails" do
|
||||||
expect(mail_html.scan(/<h1\s*(?:style=""){0,1}>FOR YOU<\/h1>/).count).to eq(1)
|
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
||||||
expect(mail_html).to include('activate-account')
|
expect(mail_html).to include('activate-account')
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'digest' do
|
context 'translation override' do
|
||||||
fab!(:popular_topic) { Fabricate(:topic, user: Fabricate(:coding_horror), created_at: 1.hour.ago) }
|
before do
|
||||||
let(:summary_email) { UserNotifications.digest(Fabricate(:user)) }
|
TranslationOverride.upsert!(
|
||||||
subject(:mail_html) { Email::Renderer.new(summary_email).html }
|
'en',
|
||||||
|
'user_notifications.signup.text_body_template',
|
||||||
|
"CLICK THAT LINK: %{base_url}/u/activate-account/%{email_token}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it "customizations are applied to html part of emails" do
|
after do
|
||||||
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
TranslationOverride.revert!('en', ['user_notifications.signup.text_body_template'])
|
||||||
expect(mail_html).to include(popular_topic.title)
|
end
|
||||||
|
|
||||||
|
it "applies customizations when translation override exists" do
|
||||||
|
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
||||||
|
expect(mail_html.scan('CLICK THAT LINK').count).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with some bad css' do
|
||||||
|
before do
|
||||||
|
SiteSetting.email_custom_css = '@import "nope.css"; h1 {{{ size: really big; '
|
||||||
|
SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can render the html" do
|
||||||
|
expect(mail_html.scan(/<h1\s*(?:style=""){0,1}>FOR YOU<\/h1>/).count).to eq(1)
|
||||||
|
expect(mail_html).to include('activate-account')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't apply customizations if apply_custom_styles_to_digest is disabled" do
|
context 'digest' do
|
||||||
SiteSetting.apply_custom_styles_to_digest = false
|
fab!(:popular_topic) { Fabricate(:topic, user: Fabricate(:coding_horror), created_at: 1.hour.ago) }
|
||||||
expect(mail_html).to_not include('<h1 style="color: red;">FOR YOU</h1>')
|
let(:summary_email) { UserNotifications.digest(Fabricate(:user)) }
|
||||||
expect(mail_html).to_not include('FOR YOU')
|
subject(:mail_html) { Email::Renderer.new(summary_email).html }
|
||||||
expect(mail_html).to include(popular_topic.title)
|
|
||||||
|
it "customizations are applied to html part of emails" do
|
||||||
|
expect(mail_html.scan('<h1 style="color: red;">FOR YOU</h1>').count).to eq(1)
|
||||||
|
expect(mail_html).to include(popular_topic.title)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't apply customizations if apply_custom_styles_to_digest is disabled" do
|
||||||
|
SiteSetting.apply_custom_styles_to_digest = false
|
||||||
|
expect(mail_html).to_not include('<h1 style="color: red;">FOR YOU</h1>')
|
||||||
|
expect(mail_html).to_not include('FOR YOU')
|
||||||
|
expect(mail_html).to include(popular_topic.title)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue