FIX: Make set_locale an around_action to avoid leaking between requests (#10282)

This commit is contained in:
David Taylor 2020-07-22 17:30:26 +01:00 committed by GitHub
parent 723d7e3a61
commit bcb0e62363
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 136 additions and 111 deletions

View File

@ -32,7 +32,7 @@ class ApplicationController < ActionController::Base
before_action :handle_theme before_action :handle_theme
before_action :set_current_user_for_logs before_action :set_current_user_for_logs
before_action :clear_notifications before_action :clear_notifications
before_action :set_locale around_action :with_resolved_locale
before_action :set_mobile_view before_action :set_mobile_view
before_action :block_if_readonly_mode before_action :block_if_readonly_mode
before_action :authorize_mini_profiler before_action :authorize_mini_profiler
@ -245,16 +245,19 @@ class ApplicationController < ActionController::Base
end end
end end
if opts[:custom_message_translated] message = title = nil
title = message = opts[:custom_message_translated] with_resolved_locale(check_current_user: false) do
elsif opts[:custom_message] if opts[:custom_message_translated]
title = message = I18n.t(opts[:custom_message]) title = message = opts[:custom_message_translated]
else elsif opts[:custom_message]
message = I18n.t(type) title = message = I18n.t(opts[:custom_message])
if status_code == 403
title = I18n.t("page_forbidden.title")
else else
title = I18n.t("page_not_found.title") message = I18n.t(type)
if status_code == 403
title = I18n.t("page_forbidden.title")
else
title = I18n.t("page_not_found.title")
end
end end
end end
@ -263,9 +266,11 @@ class ApplicationController < ActionController::Base
if show_json_errors if show_json_errors
opts = { type: type, status: status_code } opts = { type: type, status: status_code }
# Include error in HTML format for topics#show. with_resolved_locale(check_current_user: false) do
if (request.params[:controller] == 'topics' && request.params[:action] == 'show') || (request.params[:controller] == 'categories' && request.params[:action] == 'find_by_slug') # Include error in HTML format for topics#show.
opts[:extras] = { html: build_not_found_page(error_page_opts) } if (request.params[:controller] == 'topics' && request.params[:action] == 'show') || (request.params[:controller] == 'categories' && request.params[:action] == 'find_by_slug')
opts[:extras] = { html: build_not_found_page(error_page_opts) }
end
end end
render_json_error message, opts render_json_error message, opts
@ -277,9 +282,10 @@ class ApplicationController < ActionController::Base
rescue Discourse::InvalidAccess rescue Discourse::InvalidAccess
return render plain: message, status: status_code return render plain: message, status: status_code
end end
with_resolved_locale do
error_page_opts[:layout] = opts[:include_ember] ? 'application' : 'no_ember' error_page_opts[:layout] = opts[:include_ember] ? 'application' : 'no_ember'
render html: build_not_found_page(error_page_opts) render html: build_not_found_page(error_page_opts)
end
end end
end end
@ -325,19 +331,23 @@ class ApplicationController < ActionController::Base
end end
end end
def set_locale def with_resolved_locale(check_current_user: true)
if !current_user if check_current_user && current_user
locale = current_user.effective_locale
else
if SiteSetting.set_locale_from_accept_language_header if SiteSetting.set_locale_from_accept_language_header
locale = locale_from_header locale = locale_from_header
else else
locale = SiteSetting.default_locale locale = SiteSetting.default_locale
end end
else
locale = current_user.effective_locale
end end
I18n.locale = I18n.locale_available?(locale) ? locale : SiteSettings::DefaultsProvider::DEFAULT_LOCALE if !I18n.locale_available?(locale)
locale = SiteSettings::DefaultsProvider::DEFAULT_LOCALE
end
I18n.ensure_all_loaded! I18n.ensure_all_loaded!
I18n.with_locale(locale) { yield }
end end
def store_preloaded(key, json) def store_preloaded(key, json)

View File

@ -533,8 +533,8 @@ RSpec.describe Admin::SiteTextsController do
} }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(Category.find(SiteSetting.staff_category_id).name).to eq(I18n.t("staff_category_name")) expect(Category.find(SiteSetting.staff_category_id).name).to eq(I18n.t("staff_category_name", locale: :de))
expect(Topic.find(SiteSetting.guidelines_topic_id).title).to eq(I18n.t("guidelines_topic.title")) expect(Topic.find(SiteSetting.guidelines_topic_id).title).to eq(I18n.t("guidelines_topic.title", locale: :de))
end end
end end
end end

View File

@ -628,4 +628,107 @@ RSpec.describe ApplicationController do
get "/t/#{topic.slug}/#{topic.id}" get "/t/#{topic.slug}/#{topic.id}"
expect(response.body).to have_tag("link", with: { rel: "canonical", href: "http://test.localhost/t/#{topic.slug}/#{topic.id}" }) expect(response.body).to have_tag("link", with: { rel: "canonical", href: "http://test.localhost/t/#{topic.slug}/#{topic.id}" })
end end
describe "set_locale" do
# Using /bootstrap.json because it returns a locale-dependent value
def headers(locale)
{ HTTP_ACCEPT_LANGUAGE: locale }
end
context "allow_user_locale disabled" do
context "accept-language header differs from default locale" do
before do
SiteSetting.allow_user_locale = false
SiteSetting.default_locale = "en"
end
context "with an anonymous user" do
it "uses the default locale" do
get "/bootstrap.json", headers: headers("fr")
expect(response.status).to eq(200)
expect(response.parsed_body['bootstrap']['locale_script']).to end_with("en.js")
end
end
context "with a logged in user" do
it "it uses the default locale" do
user = Fabricate(:user, locale: :fr)
sign_in(user)
get "/bootstrap.json", headers: headers("fr")
expect(response.status).to eq(200)
expect(response.parsed_body['bootstrap']['locale_script']).to end_with("en.js")
end
end
end
end
context "set_locale_from_accept_language_header enabled" do
context "accept-language header differs from default locale" do
before do
SiteSetting.allow_user_locale = true
SiteSetting.set_locale_from_accept_language_header = true
SiteSetting.default_locale = "en"
end
context "with an anonymous user" do
it "uses the locale from the headers" do
get "/bootstrap.json", headers: headers("fr")
expect(response.status).to eq(200)
expect(response.parsed_body['bootstrap']['locale_script']).to end_with("fr.js")
end
it "doesn't leak after requests" do
get "/bootstrap.json", headers: headers("fr")
expect(response.status).to eq(200)
expect(response.parsed_body['bootstrap']['locale_script']).to end_with("fr.js")
expect(I18n.locale.to_s).to eq(SiteSettings::DefaultsProvider::DEFAULT_LOCALE)
end
end
context "with a logged in user" do
before do
user = Fabricate(:user, locale: :fr)
sign_in(user)
end
it "uses the user's preferred locale" do
get "/bootstrap.json", headers: headers("fr")
expect(response.status).to eq(200)
expect(response.parsed_body['bootstrap']['locale_script']).to end_with("fr.js")
end
it "serves a 404 page in the preferred locale" do
get "/missingroute", headers: headers("fr")
expect(response.status).to eq(404)
expected_title = I18n.t("page_not_found.title", locale: :fr)
expect(response.body).to include(CGI.escapeHTML(expected_title))
end
end
end
context "the preferred locale includes a region" do
it "returns the locale and region separated by an underscore" do
SiteSetting.allow_user_locale = true
SiteSetting.set_locale_from_accept_language_header = true
SiteSetting.default_locale = "en"
get "/bootstrap.json", headers: headers("zh-CN")
expect(response.status).to eq(200)
expect(response.parsed_body['bootstrap']['locale_script']).to end_with("zh_CN.js")
end
end
context 'accept-language header is not set' do
it 'uses the site default locale' do
SiteSetting.allow_user_locale = true
SiteSetting.default_locale = 'en'
get "/bootstrap.json", headers: headers("")
expect(response.status).to eq(200)
expect(response.parsed_body['bootstrap']['locale_script']).to end_with("en.js")
end
end
end
end
end end

View File

@ -1993,94 +1993,6 @@ RSpec.describe TopicsController do
end end
end end
describe "set_locale" do
def headers(locale)
{ HTTP_ACCEPT_LANGUAGE: locale }
end
context "allow_user_locale disabled" do
context "accept-language header differs from default locale" do
before do
SiteSetting.allow_user_locale = false
SiteSetting.default_locale = "en"
end
context "with an anonymous user" do
it "uses the default locale" do
get "/t/#{topic.id}.json", headers: headers("fr")
expect(response.status).to eq(200)
expect(I18n.locale).to eq(:en)
end
end
context "with a logged in user" do
it "it uses the default locale" do
user = Fabricate(:user, locale: :fr)
sign_in(user)
get "/t/#{topic.id}.json", headers: headers("fr")
expect(response.status).to eq(200)
expect(I18n.locale).to eq(:en)
end
end
end
end
context "set_locale_from_accept_language_header enabled" do
context "accept-language header differs from default locale" do
before do
SiteSetting.allow_user_locale = true
SiteSetting.set_locale_from_accept_language_header = true
SiteSetting.default_locale = "en"
end
context "with an anonymous user" do
it "uses the locale from the headers" do
get "/t/#{topic.id}.json", headers: headers("fr")
expect(response.status).to eq(200)
expect(I18n.locale).to eq(:fr)
end
end
context "with a logged in user" do
it "uses the user's preferred locale" do
user = Fabricate(:user, locale: :fr)
sign_in(user)
get "/t/#{topic.id}.json", headers: headers("fr")
expect(response.status).to eq(200)
expect(I18n.locale).to eq(:fr)
end
end
end
context "the preferred locale includes a region" do
it "returns the locale and region separated by an underscore" do
SiteSetting.allow_user_locale = true
SiteSetting.set_locale_from_accept_language_header = true
SiteSetting.default_locale = "en"
get "/t/#{topic.id}.json", headers: headers("zh-CN")
expect(response.status).to eq(200)
expect(I18n.locale).to eq(:zh_CN)
end
end
context 'accept-language header is not set' do
it 'uses the site default locale' do
SiteSetting.allow_user_locale = true
SiteSetting.default_locale = 'en'
get "/t/#{topic.id}.json", headers: headers("")
expect(response.status).to eq(200)
expect(I18n.locale).to eq(:en)
end
end
end
end
describe "read only header" do describe "read only header" do
it "returns no read only header by default" do it "returns no read only header by default" do
get "/t/#{topic.id}.json" get "/t/#{topic.id}.json"