diff options
Diffstat (limited to 'actionpack')
-rw-r--r-- | actionpack/CHANGELOG.md | 5 | ||||
-rw-r--r-- | actionpack/lib/action_controller/metal/force_ssl.rb | 65 | ||||
-rw-r--r-- | actionpack/test/controller/force_ssl_test.rb | 120 |
3 files changed, 159 insertions, 31 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 817b3cab83..e30029fb6d 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,10 @@ ## Rails 4.0.0 (unreleased) ## +* Add support for passing custom url options other than `:host` and custom + status and flash options to `force_ssl`. + + *Andrew White* + * The `force_ssl` command now builds the redirect url from `request.fullpath`. This ensures that the format is maintained and it doesn't redirect to a route that has the same parameters but is defined earlier in `routes.rb`. Also any diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index fe61dd1f86..7edeba6fc0 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -1,3 +1,6 @@ +require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/slice' + module ActionController # This module provides a method which will redirect browser to use HTTPS # protocol. This will ensure that user's sensitive information will be @@ -14,6 +17,10 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Callbacks + ACTION_OPTIONS = [:only, :except, :if, :unless] + URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path] + REDIRECT_OPTIONS = [:status, :flash, :alert, :notice] + module ClassMethods # Force the request to this particular controller or specified actions to be # under HTTPS protocol. @@ -29,18 +36,33 @@ module ActionController # end # end # - # ==== Options - # * <tt>host</tt> - Redirect to a different host name - # * <tt>only</tt> - The callback should be run only for this action - # * <tt>except</tt> - The callback should be run for all actions except this action - # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback - # will be called only when it returns a true value. - # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback - # will be called only when it returns a false value. + # ==== URL Options + # You can pass any of the following options to affect the redirect url + # * <tt>host</tt> - Redirect to a different host name + # * <tt>subdomain</tt> - Redirect to a different subdomain + # * <tt>domain</tt> - Redirect to a different domain + # * <tt>port</tt> - Redirect to a non-standard port + # * <tt>path</tt> - Redirect to a different path + # + # ==== Redirect Options + # You can pass any of the following options to affect the redirect status and response + # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently) + # * <tt>flash</tt> - Set a flash message when redirecting + # * <tt>alert</tt> - Set a alert message when redirecting + # * <tt>notice</tt> - Set a notice message when redirecting + # + # ==== Action Options + # You can pass any of the following options to affect the before_action callback + # * <tt>only</tt> - The callback should be run only for this action + # * <tt>except</tt> - The callback should be run for all actions except this action + # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a true value. + # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a false value. def force_ssl(options = {}) - host = options.delete(:host) - before_action(options) do - force_ssl_redirect(host) + action_options = options.slice(*ACTION_OPTIONS) + before_action(action_options) do + force_ssl_redirect(options.except(*ACTION_OPTIONS)) end end end @@ -48,17 +70,26 @@ module ActionController # Redirect the existing request to use the HTTPS protocol. # # ==== Parameters - # * <tt>host</tt> - Redirect to a different host name - def force_ssl_redirect(host = nil) + # * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options + # available to the <tt>force_ssl</tt> method. + def force_ssl_redirect(host_or_options = nil) unless request.ssl? - secure_url = ActionDispatch::Http::URL.url_for({ + options = { :protocol => 'https://', + :host => request.host, :path => request.fullpath, - :host => host || request.host - }) + :status => :moved_permanently + } + + if host_or_options.is_a?(Hash) + options.merge!(host_or_options) + elsif host_or_options + options.merge!(:host => host_or_options) + end + secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS)) flash.keep if respond_to?(:flash) - redirect_to secure_url, :status => :moved_permanently + redirect_to secure_url, options.slice(*REDIRECT_OPTIONS) end end end diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb index 2e884cadd4..3655b90e32 100644 --- a/actionpack/test/controller/force_ssl_test.rb +++ b/actionpack/test/controller/force_ssl_test.rb @@ -14,8 +14,42 @@ class ForceSSLControllerLevel < ForceSSLController force_ssl end -class ForceSSLCustomDomain < ForceSSLController - force_ssl :host => "secure.test.host" +class ForceSSLCustomOptions < ForceSSLController + force_ssl :host => "secure.example.com", :only => :redirect_host + force_ssl :port => 8443, :only => :redirect_port + force_ssl :subdomain => 'secure', :only => :redirect_subdomain + force_ssl :domain => 'secure.com', :only => :redirect_domain + force_ssl :path => '/foo', :only => :redirect_path + force_ssl :status => :found, :only => :redirect_status + force_ssl :flash => { :message => 'Foo, Bar!' }, :only => :redirect_flash + force_ssl :alert => 'Foo, Bar!', :only => :redirect_alert + force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice + + def force_ssl_action + render :text => action_name + end + + alias_method :redirect_host, :force_ssl_action + alias_method :redirect_port, :force_ssl_action + alias_method :redirect_subdomain, :force_ssl_action + alias_method :redirect_domain, :force_ssl_action + alias_method :redirect_path, :force_ssl_action + alias_method :redirect_status, :force_ssl_action + alias_method :redirect_flash, :force_ssl_action + alias_method :redirect_alert, :force_ssl_action + alias_method :redirect_notice, :force_ssl_action + + def use_flash + render :text => flash[:message] + end + + def use_alert + render :text => flash[:alert] + end + + def use_notice + render :text => flash[:notice] + end end class ForceSSLOnlyAction < ForceSSLController @@ -80,19 +114,77 @@ class ForceSSLControllerLevelTest < ActionController::TestCase end end -class ForceSSLCustomDomainTest < ActionController::TestCase - tests ForceSSLCustomDomain +class ForceSSLCustomOptionsTest < ActionController::TestCase + tests ForceSSLCustomOptions - def test_banana_redirects_to_https_with_custom_host - get :banana + def setup + @request.env['HTTP_HOST'] = 'www.example.com:80' + end + + def test_redirect_to_custom_host + get :redirect_host assert_response 301 - assert_equal "https://secure.test.host/force_ssl_custom_domain/banana", redirect_to_url + assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_host", redirect_to_url end - def test_cheeseburger_redirects_to_https_with_custom_host - get :cheeseburger + def test_redirect_to_custom_port + get :redirect_port + assert_response 301 + assert_equal "https://www.example.com:8443/force_ssl_custom_options/redirect_port", redirect_to_url + end + + def test_redirect_to_custom_subdomain + get :redirect_subdomain + assert_response 301 + assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_subdomain", redirect_to_url + end + + def test_redirect_to_custom_domain + get :redirect_domain + assert_response 301 + assert_equal "https://www.secure.com/force_ssl_custom_options/redirect_domain", redirect_to_url + end + + def test_redirect_to_custom_path + get :redirect_path assert_response 301 - assert_equal "https://secure.test.host/force_ssl_custom_domain/cheeseburger", redirect_to_url + assert_equal "https://www.example.com/foo", redirect_to_url + end + + def test_redirect_to_custom_status + get :redirect_status + assert_response 302 + assert_equal "https://www.example.com/force_ssl_custom_options/redirect_status", redirect_to_url + end + + def test_redirect_to_custom_flash + get :redirect_flash + assert_response 301 + assert_equal "https://www.example.com/force_ssl_custom_options/redirect_flash", redirect_to_url + + get :use_flash + assert_response 200 + assert_equal "Foo, Bar!", @response.body + end + + def test_redirect_to_custom_alert + get :redirect_alert + assert_response 301 + assert_equal "https://www.example.com/force_ssl_custom_options/redirect_alert", redirect_to_url + + get :use_alert + assert_response 200 + assert_equal "Foo, Bar!", @response.body + end + + def test_redirect_to_custom_notice + get :redirect_notice + assert_response 301 + assert_equal "https://www.example.com/force_ssl_custom_options/redirect_notice", redirect_to_url + + get :use_notice + assert_response 200 + assert_equal "Foo, Bar!", @response.body end end @@ -166,20 +258,20 @@ class ForceSSLFlashTest < ActionController::TestCase end class ForceSSLDuplicateRoutesTest < ActionController::TestCase - tests ForceSSLCustomDomain + tests ForceSSLControllerLevel def test_force_ssl_redirects_to_same_path with_routing do |set| set.draw do - get '/foo', :to => 'force_ssl_custom_domain#banana' - get '/bar', :to => 'force_ssl_custom_domain#banana' + get '/foo', :to => 'force_ssl_controller_level#banana' + get '/bar', :to => 'force_ssl_controller_level#banana' end @request.env['PATH_INFO'] = '/bar' get :banana assert_response 301 - assert_equal 'https://secure.test.host/bar', redirect_to_url + assert_equal 'https://test.host/bar', redirect_to_url end end end |