diff --git a/README.rdoc b/README.rdoc index 31f485b..3ae6f04 100644 --- a/README.rdoc +++ b/README.rdoc @@ -39,6 +39,12 @@ And force http for non-https path config.middleware.use Rack::SslEnforcer, :only => ["/login", /\.xml$/], :strict => true +Or in the case where you have matching urls with different methods (rails restful routes: get#users post#users || get#user/:id put#user/:id) you may need to post and put to secure but redirect to http on get. + + config.middleware.use Rack::SslEnforcer, :only => [/^\/users\/(.+)\/edit/], :mixed => true + +The above will allow you to post/put from the secure/non-secure urls keeping the original schema. + To set HSTS expiry and subdomain inclusion (defaults: one year, true) config.middleware.use Rack::SslEnforcer, :hsts => {:expires => 500, :subdomains => false} diff --git a/lib/rack/ssl-enforcer.rb b/lib/rack/ssl-enforcer.rb index 932460a..1754ed3 100644 --- a/lib/rack/ssl-enforcer.rb +++ b/lib/rack/ssl-enforcer.rb @@ -9,7 +9,7 @@ def call(env) @req = Rack::Request.new(env) if enforce_ssl?(env) scheme = 'https' unless ssl_request?(env) - elsif ssl_request?(env) && @options[:strict] + elsif ssl_request?(env) && enforcement_non_ssl?(env) scheme = 'http' end @@ -26,9 +26,15 @@ def call(env) @app.call(env) end end - - + private + + def enforcement_non_ssl?(env) + return true if @options[:strict] + unless (env['REQUEST_METHOD'] == 'PUT' || env['REQUEST_METHOD'] == 'POST') + @options[:mixed] if @options[:mixed] + end + end def ssl_request?(env) scheme(env) == 'https' diff --git a/test/rack-ssl-enforcer_test.rb b/test/rack-ssl-enforcer_test.rb index e14f6dc..c79ec24 100644 --- a/test/rack-ssl-enforcer_test.rb +++ b/test/rack-ssl-enforcer_test.rb @@ -166,6 +166,62 @@ class TestRackSslEnforcer < Test::Unit::TestCase end end + context 'that has array of regex pattern & path as only option with strict option and post option' do + setup { mock_app :only => [/^\/users\/(.+)\/edit/], :mixed => true } + + should 'respond with a http redirect from non-allowed https url' do + get 'https://www.example.org/foo/' + assert_equal 301, last_response.status + assert_equal 'http://www.example.org/foo/', last_response.location + end + + should 'respond from allowed https url' do + get 'https://www.example.org/users/123/edit' + assert_equal 200, last_response.status + assert_equal 'Hello world!', last_response.body + end + + should 'use default https port when redirecting non-standard ssl port to http' do + get 'https://example.org:81/', {}, { 'rack.url_scheme' => 'https' } + assert_equal 301, last_response.status + assert_equal 'http://example.org/', last_response.location + end + + should 'secure cookies' do + get 'https://www.example.org/users/123/edit' + assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly"], last_response.headers['Set-Cookie'].split("\n") + end + + should 'not secure cookies' do + get 'http://www.example.org/' + assert_equal ["id=1; path=/", "token=abc; path=/; secure; HttpOnly"], last_response.headers['Set-Cookie'].split("\n") + end + + should 'not redirect if post' do + post 'https://www.example.org/users/' + assert_equal 200, last_response.status + assert_equal 'Hello world!', last_response.body + end + + should 'not redirect if put' do + put 'https://www.example.org/users/123' + assert_equal 200, last_response.status + assert_equal 'Hello world!', last_response.body + end + + should 'not redirect if post' do + post 'http://www.example.org/users/' + assert_equal 200, last_response.status + assert_equal 'Hello world!', last_response.body + end + + should 'not redirect if put' do + put 'http://www.example.org/users/123' + assert_equal 200, last_response.status + assert_equal 'Hello world!', last_response.body + end + end + context 'that has hsts options set' do setup { mock_app :hsts => {:expires => '500', :subdomains => false} }