Spaces :)
This commit is contained in:
Родитель
b399e73c96
Коммит
789edeb8de
12
README.rdoc
12
README.rdoc
|
@ -13,7 +13,7 @@ Cookies as secure and enables HSTS by default.
|
|||
|
||||
require 'rack/ssl-enforcer'
|
||||
use Rack::SslEnforcer
|
||||
|
||||
|
||||
Or, if you are using Bundler, just add this to your Gemfile:
|
||||
|
||||
gem 'rack-ssl-enforcer', :require => 'rack/ssl-enforcer'
|
||||
|
@ -31,9 +31,9 @@ If all you want is SSL for your whole application, you are done! However, you ca
|
|||
You might need the :redirect_to option if the requested URL can't be determined (e.g. if using a proxy).
|
||||
|
||||
config.middleware.use Rack::SslEnforcer, :redirect_to => 'https://example.org'
|
||||
|
||||
|
||||
You can also define specific regex patterns or paths or hosts to redirect.
|
||||
|
||||
|
||||
config.middleware.use Rack::SslEnforcer, :only => /^\/admin\//
|
||||
config.middleware.use Rack::SslEnforcer, :only => "/login"
|
||||
config.middleware.use Rack::SslEnforcer, :only => ["/login", /\.xml$/]
|
||||
|
@ -43,7 +43,7 @@ You can also define specific regex patterns or paths or hosts to redirect.
|
|||
config.middleware.use Rack::SslEnforcer, :except_hosts => /[help|blog]\.example\.com$/
|
||||
|
||||
Note: hosts options take precedence over the path options. See tests for examples.
|
||||
|
||||
|
||||
Use the :strict option to force http for all requests not matching your :only specification
|
||||
|
||||
config.middleware.use Rack::SslEnforcer, :only => ["/login", /\.xml$/], :strict => true
|
||||
|
@ -75,13 +75,13 @@ Flagging cookies as secure functionality and HSTS support is greatly inspired by
|
|||
|
||||
|
||||
== Note on Patches/Pull Requests
|
||||
|
||||
|
||||
* Fork the project.
|
||||
* Make your feature addition or bug fix.
|
||||
* Add tests for it. This is important so I don't break it in a
|
||||
future version unintentionally.
|
||||
* Commit, do not mess with rakefile, version, or history.
|
||||
(if you want to have your own version,
|
||||
(if you want to have your own version,
|
||||
that is fine but bump version in a commit by itself I can ignore when I pull)
|
||||
* Send me a pull request. Bonus points for topic branches.
|
||||
|
||||
|
|
2
Rakefile
2
Rakefile
|
@ -27,7 +27,7 @@ require 'rake/rdoctask'
|
|||
require 'rack/ssl-enforcer/version'
|
||||
Rake::RDocTask.new do |rdoc|
|
||||
version = Rack::SslEnforcer::VERSION
|
||||
|
||||
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = "rack-ssl-enforcer #{version}"
|
||||
rdoc.rdoc_files.include('README*')
|
||||
|
|
|
@ -1 +1 @@
|
|||
require 'rack/ssl-enforcer'
|
||||
require 'rack/ssl-enforcer'
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
module Rack
|
||||
class SslEnforcer
|
||||
|
||||
|
||||
def initialize(app, options = {})
|
||||
@app, @options = app, options
|
||||
end
|
||||
|
||||
|
||||
def call(env)
|
||||
@req = Rack::Request.new(env)
|
||||
if enforce_ssl?(@req)
|
||||
|
@ -26,14 +26,13 @@ module Rack
|
|||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def ssl_request?(env)
|
||||
scheme(env) == 'https'
|
||||
end
|
||||
|
||||
|
||||
# Fixed in rack >= 1.3
|
||||
def scheme(env)
|
||||
if env['HTTPS'] == 'on'
|
||||
|
@ -44,7 +43,7 @@ module Rack
|
|||
env['rack.url_scheme']
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def matches?(key, pattern, req)
|
||||
if pattern.is_a?(Regexp)
|
||||
case key
|
||||
|
@ -103,7 +102,7 @@ module Rack
|
|||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def replace_scheme(req, scheme)
|
||||
Rack::Request.new(req.env.merge(
|
||||
'rack.url_scheme' => scheme,
|
||||
|
@ -112,7 +111,7 @@ module Rack
|
|||
'SERVER_PORT' => port_for(scheme).to_s
|
||||
))
|
||||
end
|
||||
|
||||
|
||||
def port_for(scheme)
|
||||
scheme == 'https' ? 443 : 80
|
||||
end
|
||||
|
@ -129,7 +128,7 @@ module Rack
|
|||
}.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# see http://en.wikipedia.org/wiki/Strict_Transport_Security
|
||||
def set_hsts_headers!(headers)
|
||||
opts = { :expires => 31536000, :subdomains => true }.merge(@options[:hsts] || {})
|
||||
|
@ -137,6 +136,6 @@ module Rack
|
|||
value += "; includeSubDomains" if opts[:subdomains]
|
||||
headers.merge!({ 'Strict-Transport-Security' => value })
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,4 +2,4 @@ module Rack
|
|||
class SslEnforcer
|
||||
VERSION = "0.2.0"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,4 +25,4 @@ Gem::Specification.new do |s|
|
|||
|
||||
s.files = Dir.glob("{lib}/**/*") + %w[LICENSE README.rdoc]
|
||||
s.require_path = 'lib'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,9 +10,9 @@ require 'rack/ssl-enforcer'
|
|||
|
||||
class Test::Unit::TestCase
|
||||
include Rack::Test::Methods
|
||||
|
||||
|
||||
def app; Rack::Lint.new(@app); end
|
||||
|
||||
|
||||
def mock_app(options = {})
|
||||
main_app = lambda { |env|
|
||||
request = Rack::Request.new(env)
|
||||
|
@ -20,7 +20,7 @@ class Test::Unit::TestCase
|
|||
headers['Set-Cookie'] = "id=1; path=/\ntoken=abc; path=/; secure; HttpOnly"
|
||||
[200, headers, ['Hello world!']]
|
||||
}
|
||||
|
||||
|
||||
builder = Rack::Builder.new
|
||||
builder.use Rack::SslEnforcer, options
|
||||
builder.run main_app
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
require 'helper'
|
||||
|
||||
class TestRackSslEnforcer < Test::Unit::TestCase
|
||||
|
||||
|
||||
context 'that has no :redirect_to set' do
|
||||
setup { mock_app }
|
||||
|
||||
|
||||
should 'respond with a ssl redirect to plain-text requests' do
|
||||
get 'http://www.example.org/'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.example.org/', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond with a ssl redirect to plain-text requests and keep params' do
|
||||
get 'http://www.example.org/admin?token=33'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.example.org/admin?token=33', last_response.location
|
||||
end
|
||||
|
||||
|
||||
#heroku / etc do proxied SSL
|
||||
#http://github.com/pivotal/refraction/issues/issue/2
|
||||
should 'respect X-Forwarded-Proto header for proxied SSL' do
|
||||
|
@ -24,13 +24,13 @@ class TestRackSslEnforcer < Test::Unit::TestCase
|
|||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.example.org/', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond not redirect ssl requests' do
|
||||
get 'https://www.example.org/'
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal 'Hello world!', last_response.body
|
||||
end
|
||||
|
||||
|
||||
should 'respond not redirect ssl requests and respect X-Forwarded-Proto header for proxied SSL' do
|
||||
get 'http://www.example.org/', {}, { 'HTTP_X_FORWARDED_PROTO' => 'https', 'rack.url_scheme' => 'http' }
|
||||
assert_equal 200, last_response.status
|
||||
|
@ -41,114 +41,114 @@ class TestRackSslEnforcer < Test::Unit::TestCase
|
|||
get 'http://example.org:81/', {}, { 'rack.url_scheme' => 'http' }
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://example.org/', last_response.location
|
||||
end
|
||||
end
|
||||
|
||||
should 'secure cookies' do
|
||||
get 'https://www.example.org/'
|
||||
assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly"], last_response.headers['Set-Cookie'].split("\n")
|
||||
end
|
||||
|
||||
|
||||
should 'not set default hsts headers to all ssl requests' do
|
||||
get 'https://www.example.org/'
|
||||
assert last_response.headers["Strict-Transport-Security"].nil?
|
||||
end
|
||||
|
||||
|
||||
should 'not set hsts headers to non ssl requests' do
|
||||
get 'http://www.example.org/'
|
||||
assert last_response.headers["Strict-Transport-Security"].nil?
|
||||
assert last_response.headers["Strict-Transport-Security"].nil?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'that has :redirect_to set' do
|
||||
setup { mock_app :redirect_to => 'https://www.google.com' }
|
||||
|
||||
|
||||
should 'respond with a ssl redirect to plain-text requests and redirect to :redirect_to' do
|
||||
get 'http://www.example.org/'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.google.com', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond not redirect ssl requests' do
|
||||
get 'https://www.example.org/'
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal 'Hello world!', last_response.body
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'that has regex pattern as only option' do
|
||||
setup { mock_app :only => /^\/admin/ }
|
||||
|
||||
|
||||
should 'respond with a ssl redirect for /admin path' do
|
||||
get 'http://www.example.org/admin'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.example.org/admin', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond not redirect ssl requests' do
|
||||
get 'http://www.example.org/foo'
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal 'Hello world!', last_response.body
|
||||
end
|
||||
|
||||
|
||||
should 'secure cookies' do
|
||||
get 'https://www.example.org/'
|
||||
assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly"], last_response.headers['Set-Cookie'].split("\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'that has path as only option' do
|
||||
setup { mock_app :only => "/login" }
|
||||
|
||||
|
||||
should 'respond with a ssl redirect for /login path' do
|
||||
get 'http://www.example.org/login'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.example.org/login', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond not redirect ssl requests' do
|
||||
get 'http://www.example.org/foo/'
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal 'Hello world!', last_response.body
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'that has array of regex pattern & path as only option' do
|
||||
setup { mock_app :only => [/\.xml$/, "/login"] }
|
||||
|
||||
|
||||
should 'respond with a ssl redirect for /login path' do
|
||||
get 'http://www.example.org/login'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.example.org/login', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond with a ssl redirect for /admin path' do
|
||||
get 'http://www.example.org/users.xml'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.example.org/users.xml', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond not redirect ssl requests' do
|
||||
get 'http://www.example.org/foo/'
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal 'Hello world!', last_response.body
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'that has array of regex pattern & path as only option with strict option' do
|
||||
setup { mock_app :only => [/\.xml$/, "/login"], :strict => 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/login'
|
||||
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
|
||||
|
@ -159,65 +159,65 @@ class TestRackSslEnforcer < Test::Unit::TestCase
|
|||
get 'https://www.example.org/login'
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
context 'that has regex pattern as except option' do
|
||||
setup { mock_app :except => /^\/foo/ }
|
||||
|
||||
|
||||
should 'respond with a ssl redirect for /admin path' do
|
||||
get 'http://www.example.org/admin'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.example.org/admin', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond not redirect ssl requests' do
|
||||
get 'http://www.example.org/foo'
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal 'Hello world!', last_response.body
|
||||
end
|
||||
|
||||
|
||||
should 'secure cookies' do
|
||||
get 'https://www.example.org/'
|
||||
assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly"], last_response.headers['Set-Cookie'].split("\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'that has path as except option' do
|
||||
setup { mock_app :except => "/foo" }
|
||||
|
||||
|
||||
should 'respond with a ssl redirect for /login path' do
|
||||
get 'http://www.example.org/login'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'https://www.example.org/login', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond not redirect ssl requests' do
|
||||
get 'http://www.example.org/foo/'
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal 'Hello world!', last_response.body
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'that has path as except option with strict option' do
|
||||
setup { mock_app :except => "/foo", :strict => 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/login'
|
||||
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/foo', {}, { 'rack.url_scheme' => 'https' }
|
||||
assert_equal 301, last_response.status
|
||||
|
@ -228,7 +228,7 @@ class TestRackSslEnforcer < Test::Unit::TestCase
|
|||
get 'https://www.example.org/'
|
||||
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/foo'
|
||||
assert_equal ["id=1; path=/", "token=abc; path=/; secure; HttpOnly"], last_response.headers['Set-Cookie'].split("\n")
|
||||
|
@ -463,19 +463,19 @@ class TestRackSslEnforcer < Test::Unit::TestCase
|
|||
|
||||
context 'that has array of regex pattern & domain as only_hosts option with strict option' do
|
||||
setup { mock_app :only_hosts => [/[www|api]\.example\.org$/, "example.com"], :strict => true }
|
||||
|
||||
|
||||
should 'respond with a http redirect from non-allowed https url' do
|
||||
get 'https://abc.example.org/'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'http://abc.example.org/', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond from allowed https url' do
|
||||
get 'https://www.example.org/'
|
||||
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://goo.example.org:80/', {}, { 'rack.url_scheme' => 'https' }
|
||||
assert_equal 301, last_response.status
|
||||
|
@ -486,7 +486,7 @@ class TestRackSslEnforcer < Test::Unit::TestCase
|
|||
get 'https://www.example.org/'
|
||||
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://goo.example.org/'
|
||||
assert_equal ["id=1; path=/", "token=abc; path=/; secure; HttpOnly"], last_response.headers['Set-Cookie'].split("\n")
|
||||
|
@ -533,19 +533,19 @@ class TestRackSslEnforcer < Test::Unit::TestCase
|
|||
|
||||
context 'that has regex pattern as except_hosts option with strict option' do
|
||||
setup { mock_app :except_hosts => /[www|api]\.example\.org$/, :strict => true }
|
||||
|
||||
|
||||
should 'respond with a http redirect from non-allowed https url' do
|
||||
get 'https://www.example.org/'
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal 'http://www.example.org/', last_response.location
|
||||
end
|
||||
|
||||
|
||||
should 'respond from allowed https url' do
|
||||
get 'https://abc.example.org/'
|
||||
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://www.example.org:80/', {}, { 'rack.url_scheme' => 'https' }
|
||||
assert_equal 301, last_response.status
|
||||
|
@ -556,27 +556,25 @@ class TestRackSslEnforcer < Test::Unit::TestCase
|
|||
get 'https://goo.example.org/'
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
|
||||
context 'that has hsts options set' do
|
||||
setup { mock_app :hsts => {:expires => '500', :subdomains => false} }
|
||||
|
||||
|
||||
should 'set expiry option' do
|
||||
get 'https://www.example.org/'
|
||||
assert_equal "max-age=500", last_response.headers["Strict-Transport-Security"]
|
||||
assert_equal "max-age=500", last_response.headers["Strict-Transport-Security"]
|
||||
end
|
||||
|
||||
|
||||
should 'not include subdomains' do
|
||||
get 'https://www.example.org/'
|
||||
assert !last_response.headers["Strict-Transport-Security"].include?("includeSubDomains")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
Загрузка…
Ссылка в новой задаче