Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add environment constraints #52

Merged
merged 1 commit into from
May 3, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ Add Rack::SslEnforcer to your `Gemfile`:
In order for Rack::SslEnforcer to properly work it has to be at the top
of the Rack Middleware.

Using `enable :session` will place Rack::Session::Cookie before Rack::Ssl::Enforcer
Using `enable :session` will place Rack::Session::Cookie before Rack::Ssl::Enforcer
and will prevent Rack::Ssl::Enforcer from marking cookies as secure.

To fix this issue do not use `enable :sessions` instead add the
Rack::Session::Cookie middleware after Rack::Ssl::Enforcer.
Rack::Session::Cookie middleware after Rack::Ssl::Enforcer.

Eg:

```ruby
use Rack::SslEnforcer
use Rack::SslEnforcer
set :session_secret, 'asdfa2342923422f1adc05c837fa234230e3594b93824b00e930ab0fb94b'

#Enable sinatra sessions
Expand Down Expand Up @@ -103,6 +103,23 @@ config.middleware.use Rack::SslEnforcer, :except_methods => ['GET', 'HEAD']

Note: The `:hosts` constraint takes precedence over the `:path` constraint. Please see the tests for examples.


### Environment constraints

You can enforce SSL connections only for certain environments with `:only_environments` or prevent certain environments from being forced to SSL with `:except_environments`.
Environment constraints may be a `String`, a `Regex` or an array of `String` or `Regex` (possibly mixed), as shown in the following examples:

```ruby
config.middleware.use Rack::SslEnforcer, :except_environments => 'development'

config.middleware.use Rack::SslEnforcer, :except_environments => /^[0-9a-f]+_local$/i

config.middleware.use Rack::SslEnforcer, :only_environments => ['production', /^QA/]
```

Note: The `:environments` constraint requires one the following environment variables to be set: `RACK_ENV`, `RAILS_ENV`, `ENV`.


### Force-redirection to non-SSL connection if constraint is not matched

Use the `:strict` option to force non-SSL connection for all requests not matching the constraints you set. Examples:
Expand Down
9 changes: 5 additions & 4 deletions lib/rack/ssl-enforcer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ module Rack
class SslEnforcer

CONSTRAINTS_BY_TYPE = {
:hosts => [:only_hosts, :except_hosts],
:path => [:only, :except],
:methods => [:only_methods, :except_methods]
:hosts => [:only_hosts, :except_hosts],
:path => [:only, :except],
:methods => [:only_methods, :except_methods],
:environments => [:only_environments, :except_environments]
}

# Warning: If you set the option force_secure_cookies to false, make sure that your cookies
Expand Down Expand Up @@ -117,7 +118,7 @@ def enforce_ssl_for?(keys)
else
provided_keys.all? do |key|
rules = [@options[key]].flatten.compact
rules.send([:except_hosts, :except].include?(key) ? :all? : :any?) do |rule|
rules.send([:except_hosts, :except_environments, :except].include?(key) ? :all? : :any?) do |rule|
SslEnforcerConstraint.new(key, rule, @request).matches?
end
end
Expand Down
2 changes: 2 additions & 0 deletions lib/rack/ssl-enforcer/constraint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def tested_string
@request.host
when /methods/
@request.request_method
when /environments/
ENV["RACK_ENV"] || ENV["RAILS_ENV"] || ENV["ENV"]
else
@request.path
end
Expand Down
100 changes: 100 additions & 0 deletions test/rack-ssl-enforcer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,106 @@ class TestRackSslEnforcer < Test::Unit::TestCase
end
end

context ':only_environments (String)' do
setup { mock_app :only_environments => 'production' }

should 'redirect to HTTPS for "production" environment' do
ENV["RACK_ENV"] = "production"
get 'http://www.example.org/'
assert_equal 301, last_response.status
assert_equal 'https://www.example.org/', last_response.location
end

should 'not redirect for "development" environment' do
ENV["RACK_ENV"] = "development"
get 'http://www.example.org/'
assert_equal 200, last_response.status
assert_equal 'Hello world!', last_response.body
end
end

context ':only_environments (Array + Regex)' do
setup { mock_app :only_environments => ['production', /QA\d+/] }

should 'redirect to HTTPS for "production" environment' do
ENV["RACK_ENV"] = "production"
get 'http://www.example.org/'
assert_equal 301, last_response.status
assert_equal 'https://www.example.org/', last_response.location
end

should 'redirect to HTTPS for "QA2" environment' do
ENV["RACK_ENV"] = "QA2"
get 'http://www.example.org/'
assert_equal 301, last_response.status
assert_equal 'https://www.example.org/', last_response.location
end

should 'redirect to HTTPS for "QA15" environment' do
ENV["RACK_ENV"] = "QA15"
get 'http://www.example.org/'
assert_equal 301, last_response.status
assert_equal 'https://www.example.org/', last_response.location
end

should 'not redirect for "development" environment' do
ENV["RACK_ENV"] = "development"
get 'http://www.example.org/'
assert_equal 200, last_response.status
assert_equal 'Hello world!', last_response.body
end
end

context ':except_environments (String)' do
setup { mock_app :except_environments => 'development' }

should 'redirect to HTTPS for "production" environment' do
ENV["RACK_ENV"] = "production"
get 'http://www.example.org/'
assert_equal 301, last_response.status
assert_equal 'https://www.example.org/', last_response.location
end

should 'not redirect for "development" environment' do
ENV["RACK_ENV"] = "development"
get 'http://www.example.org/'
assert_equal 200, last_response.status
assert_equal 'Hello world!', last_response.body
end
end

context ':except_environments (Array + Regex)' do
setup { mock_app :except_environments => ['development', /\w+_local/] }

should 'redirect to HTTPS for "production" environment' do
ENV["RACK_ENV"] = "production"
get 'http://www.example.org/'
assert_equal 301, last_response.status
assert_equal 'https://www.example.org/', last_response.location
end

should 'not redirect for "development" environment' do
ENV["RACK_ENV"] = "development"
get 'http://www.example.org/'
assert_equal 200, last_response.status
assert_equal 'Hello world!', last_response.body
end

should 'not redirect for "jebediah_local" environment' do
ENV["RACK_ENV"] = "jebediah_local"
get 'http://www.example.org/'
assert_equal 200, last_response.status
assert_equal 'Hello world!', last_response.body
end

should 'not redirect for "el_guapo_local" environment' do
ENV["RACK_ENV"] = "el_guapo_local"
get 'http://www.example.org/'
assert_equal 200, last_response.status
assert_equal 'Hello world!', last_response.body
end
end

context 'complex example' do
setup { mock_app :only => '/cart', :ignore => %r{/assets}, :strict => true }

Expand Down