Use hash-based flash objects to render complex flash messages

Since flash objects can only be String, Hash or Array (notably excluding SafeBuffers), then this approach is necessary to render complex html in a safe manner.

Each local can be treated as an (unsafe) string, and therefore escaped normally when rendered into the template. The template (and translation strings) can
contain html since they are no longer stored in the flash as a plain string.

Fixes #3215
This commit is contained in:
Andy Allan 2021-06-23 16:26:50 +01:00
parent f6818bb2ed
commit 24f6aeda6a
6 changed files with 43 additions and 5 deletions

View file

@ -66,7 +66,7 @@ class ConfirmationsController < ApplicationController
flash[:error] = t "confirmations.confirm_resend.failure", :name => params[:display_name]
else
UserMailer.signup_confirm(user, user.tokens.create).deliver_later
flash[:notice] = t "confirmations.confirm_resend.success_html", :email => user.email, :sender => Settings.email_from
flash[:notice] = { :partial => "confirmations/resend_success_flash", :locals => { :email => user.email, :sender => Settings.email_from } }
end
redirect_to login_path

View file

@ -68,4 +68,14 @@ module ApplicationHelper
data
end
# If the flash is a hash, then it will be a partial with a hash of locals, so we can call `render` on that
# This allows us to render html into a flash message in a safe manner.
def render_flash(flash)
if flash.is_a?(Hash)
render flash.with_indifferent_access
else
flash
end
end
end

View file

@ -0,0 +1 @@
<%= t "confirmations.confirm_resend.success_html", :email => email, :sender => sender %>

View file

@ -4,7 +4,7 @@
<source srcset="<%= image_path "notice.svg" %>" type="image/svg+xml" />
<%= image_tag("notice.png", :srcset => image_path("notice.svg"), :class => "small_icon", :border => 0) %>
</picture>
<div class="message"><%= flash[:error] %></div>
<div class="message"><%= render_flash(flash[:error]) %></div>
</div>
<% end %>
@ -14,7 +14,7 @@
<source srcset="<%= image_path "notice.svg" %>" type="image/svg+xml"></source>
<%= image_tag("notice.png", :srcset => image_path("notice.svg"), :class => "small_icon", :border => 0) %>
</picture>
<div class="message"><%= flash[:warning] %></div>
<div class="message"><%= render_flash(flash[:warning]) %></div>
</div>
<% end %>
@ -24,6 +24,6 @@
<source srcset="<%= image_path "notice.svg" %>" type="image/svg+xml"></source>
<%= image_tag("notice.png", :srcset => image_path("notice.svg"), :class => "small_icon", :border => 0) %>
</picture>
<div class="message"><%= flash[:notice] %></div>
<div class="message"><%= render_flash(flash[:notice]) %></div>
</div>
<% end %>

View file

@ -211,7 +211,8 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
assert_response :redirect
assert_redirected_to login_path
assert_match(/sent a new confirmation/, flash[:notice])
assert_equal("confirmations/resend_success_flash", flash[:notice][:partial])
assert_equal({ :email => user.email, :sender => Settings.email_from }, flash[:notice][:locals])
email = ActionMailer::Base.deliveries.last

View file

@ -0,0 +1,26 @@
require "application_system_test_case"
class ConfirmationResendSystemTest < ApplicationSystemTestCase
def setup
@user = build(:user)
visit user_new_path
fill_in "Email", :with => @user.email
fill_in "Email Confirmation", :with => @user.email
fill_in "Display Name", :with => @user.display_name
fill_in "Password", :with => "testtest"
fill_in "Confirm Password", :with => "testtest"
click_button "Sign Up"
check "I have read and agree to the above contributor terms"
check "I have read and agree to the Terms of Use"
click_button "Continue"
end
test "flash message should not contain raw html" do
visit user_confirm_resend_path(@user)
assert page.has_content?("sent a new confirmation")
assert_not page.has_content?("<br />")
end
end