From 697038486ebcc8553aa3fc71812fbd7a922287ed Mon Sep 17 00:00:00 2001 From: Roger Campos Date: Wed, 28 Jun 2017 04:43:32 -0400 Subject: [PATCH] Add verify_host_path_consistency option (#167) When using different hosts to identify different locales, it's desirable to ensure that only coherent url's work on the application. Mismatching between the locale assigned to a domain (ej: example.es) and the language of the path (ej: '/cesta-de-la-compra') should be considered 404's. This commit adds a configuration option to enable this behavior. --- README.md | 5 ++- lib/route_translator.rb | 4 ++- lib/route_translator/extensions/route_set.rb | 19 ++++++++-- .../host_path_consistency_lambdas.rb | 25 +++++++++++++ lib/route_translator/translator.rb | 2 +- ...ost_locale_path_verify_consistency_test.rb | 35 +++++++++++++++++++ test/support/configuration_helper.rb | 13 ++++--- 7 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 lib/route_translator/host_path_consistency_lambdas.rb create mode 100644 test/integration/host_locale_path_verify_consistency_test.rb diff --git a/README.md b/README.md index d8ff4a9b..c1c928f7 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,10 @@ end * **locale_segment_proc** The locale segment of the url will by default be `locale.to_s.downcase` You can supply your own mechanism via a Proc that takes `locale` as an argument, e.g. `config.locale_segment_proc = ->(locale) { locale.to_s.upcase }` - +* **verify_host_path_consistency** + By default, if you use different hosts to translate your application, all translated paths will work on all hosts. Set this option to `true` to force + a matching of the host associated locale with the translated path locale as part of the route definition. + Defaults to `false`. ### Host-based Locale diff --git a/lib/route_translator.rb b/lib/route_translator.rb index b8033a4e..67f04dcb 100644 --- a/lib/route_translator.rb +++ b/lib/route_translator.rb @@ -5,6 +5,7 @@ require File.expand_path('../route_translator/extensions', __FILE__) require File.expand_path('../route_translator/translator', __FILE__) require File.expand_path('../route_translator/host', __FILE__) +require File.expand_path('../route_translator/host_path_consistency_lambdas', __FILE__) module RouteTranslator extend RouteTranslator::Host @@ -14,7 +15,7 @@ module RouteTranslator Configuration = Struct.new(:available_locales, :disable_fallback, :force_locale, :hide_locale, :host_locales, :generate_unlocalized_routes, :generate_unnamed_unlocalized_routes, :locale_param_key, - :locale_segment_proc) + :locale_segment_proc, :verify_host_path_consistency) class << self private @@ -40,6 +41,7 @@ def config(&block) @config.generate_unnamed_unlocalized_routes ||= false @config.locale_param_key ||= :locale @config.locale_segment_proc ||= nil + @config.verify_host_path_consistency ||= false yield @config if block diff --git a/lib/route_translator/extensions/route_set.rb b/lib/route_translator/extensions/route_set.rb index d297d028..a7e858f1 100644 --- a/lib/route_translator/extensions/route_set.rb +++ b/lib/route_translator/extensions/route_set.rb @@ -8,9 +8,9 @@ class RouteSet def add_localized_route(mapping, path_ast, name, anchor, scope, path, controller, default_action, to, via, formatted, options_constraints, options) route = RouteTranslator::Route.new(self, path, name, options_constraints, options, mapping) - RouteTranslator::Translator.translations_for(route) do |translated_name, translated_path, translated_options_constraints, translated_options| + RouteTranslator::Translator.translations_for(route) do |locale, translated_name, translated_path, translated_options_constraints, translated_options| translated_path_ast = ::ActionDispatch::Journey::Parser.parse(translated_path) - translated_mapping = ::ActionDispatch::Routing::Mapper::Mapping.build(scope, self, translated_path_ast, controller, default_action, to, via, formatted, translated_options_constraints, anchor, translated_options) + translated_mapping = translate_mapping(locale, self, translated_options, translated_path_ast, scope, controller, default_action, to, formatted, via, translated_options_constraints, anchor) add_route_to_set translated_mapping, translated_path_ast, translated_name, anchor end @@ -24,6 +24,21 @@ def add_localized_route(mapping, path_ast, name, anchor, scope, path, controller private + def translate_mapping(locale, route_set, translated_options, translated_path_ast, scope, controller, default_action, to, formatted, via, translated_options_constraints, anchor) + options = scope[:options] ? scope[:options].merge(translated_options) : translated_options + + defaults = (scope[:defaults] || {}).dup + scope_constraints = scope[:constraints] || {} + + blocks = scope[:blocks] ? scope[:blocks].dup : [] + + if RouteTranslator.config.verify_host_path_consistency + blocks.push RouteTranslator::HostPathConsistencyLambdas.for_locale(locale) + end + + ::ActionDispatch::Routing::Mapper::Mapping.new(route_set, translated_path_ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, blocks, via, translated_options_constraints, anchor, options) + end + def add_route_to_set(mapping, path_ast, name, anchor) if method(:add_route).arity == 4 add_route mapping, path_ast, name, anchor diff --git a/lib/route_translator/host_path_consistency_lambdas.rb b/lib/route_translator/host_path_consistency_lambdas.rb new file mode 100644 index 00000000..f1e30d1b --- /dev/null +++ b/lib/route_translator/host_path_consistency_lambdas.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module RouteTranslator + module HostPathConsistencyLambdas + class << self + private + + def lambdas + @lambdas ||= {} + end + + def sanitize_locale(locale) + locale.to_s.gsub('native_', '') + end + end + + module_function + + def for_locale(locale) + sanitized_locale = sanitize_locale(locale) + + lambdas[sanitized_locale] ||= ->(req) { sanitized_locale == RouteTranslator::Host.locale_from_host(req.host).to_s } + end + end +end diff --git a/lib/route_translator/translator.rb b/lib/route_translator/translator.rb index 24aac307..a4957fb8 100644 --- a/lib/route_translator/translator.rb +++ b/lib/route_translator/translator.rb @@ -71,7 +71,7 @@ def translations_for(route) translated_options_constraints = translate_options_constraints(route.options_constraints, locale) translated_options = translate_options(route.options, locale) - yield translated_name, translated_path, translated_options_constraints, translated_options + yield locale, translated_name, translated_path, translated_options_constraints, translated_options end end diff --git a/test/integration/host_locale_path_verify_consistency_test.rb b/test/integration/host_locale_path_verify_consistency_test.rb new file mode 100644 index 00000000..20f11b86 --- /dev/null +++ b/test/integration/host_locale_path_verify_consistency_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require File.expand_path('../../test_helper', __FILE__) + +class HostLocalePathVerifyConsistencyTest < ActionDispatch::IntegrationTest + include RouteTranslator::ConfigurationHelper + + def setup + config_verify_host_path_consistency true + config_host_locales '*.es' => 'es', 'ru.*.com' => 'ru' + Dummy::Application.reload_routes! + end + + def teardown + config_verify_host_path_consistency false + config_host_locales + Dummy::Application.reload_routes! + end + + def test_host_path_consistency + host! 'www.testapp.es' + get '/dummy' + assert_response :success + + get URI.escape('/манекен') + assert_response :not_found + + host! 'ru.testapp.com' + get '/dummy' + assert_response :not_found + + get URI.escape('/манекен') + assert_response :success + end +end diff --git a/test/support/configuration_helper.rb b/test/support/configuration_helper.rb index 7b8d6eae..208c5a9c 100644 --- a/test/support/configuration_helper.rb +++ b/test/support/configuration_helper.rb @@ -11,10 +11,11 @@ module ConfigurationHelper }.freeze def config_reset - config_available_locales [] - config_default_locale_settings :en - config_host_locales {} - config_locale_segment_proc false + config_available_locales [] + config_default_locale_settings :en + config_host_locales {} + config_locale_segment_proc false + config_verify_host_path_consistency false BOOLEAN_OPTIONS.each do |option, default_value| send(:"config_#{option}", default_value) @@ -40,6 +41,10 @@ def config_locale_segment_proc(a_proc) RouteTranslator.config.locale_segment_proc = a_proc end + def config_verify_host_path_consistency(value) + RouteTranslator.config.verify_host_path_consistency = value + end + BOOLEAN_OPTIONS.keys.each do |option| define_method :"config_#{option}" do |bool| RouteTranslator.config.send(:"#{option}=", bool)