From cb2959ac462ce01dc90e30cf504d7c6545be5518 Mon Sep 17 00:00:00 2001 From: Larry Sprock Date: Thu, 3 Feb 2011 12:43:46 -0800 Subject: [PATCH] Add option for :mixed enforcement There are cases where it is necessary to allow ssl on put or post but not get. With restful routes this is harder since the actual route matches but the method changes. The mixed option takes this into account and only forces strict on get and delete. So the following: config.middleware.use Rack::SslEnforcer, :only => [/^\/posts\/(.+)\/edit/], :mixed => true Above will allow PUT#post/:id to maintain the secure url while GET#post/:id will be forced to use http. --- README.rdoc | 6 ++++ lib/rack/ssl-enforcer.rb | 12 ++++++-- test/rack-ssl-enforcer_test.rb | 56 ++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 3 deletions(-) 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} }