Skip to content

Commit

Permalink
Add verify_host_path_consistency option (#167)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rogercampos authored and tagliala committed Jun 28, 2017
1 parent 406bc9c commit 6970384
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 9 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion lib/route_translator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down
19 changes: 17 additions & 2 deletions lib/route_translator/extensions/route_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
25 changes: 25 additions & 0 deletions lib/route_translator/host_path_consistency_lambdas.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/route_translator/translator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
35 changes: 35 additions & 0 deletions test/integration/host_locale_path_verify_consistency_test.rb
Original file line number Diff line number Diff line change
@@ -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
13 changes: 9 additions & 4 deletions test/support/configuration_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 6970384

Please sign in to comment.