rack-ssl-enforcer 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,38 +1,64 @@
1
1
  = Rack::SslEnforcer
2
2
 
3
3
  Rack::SslEnforcer is a simple Rack middleware to enforce ssl connections. As of Version 0.2.0, Rack::SslEnforcer marks
4
- Cookies as secure and enables HSTS by default.
5
-
4
+ Cookies as secure by default (HSTS must be set manually).
5
+ Tested on Ruby 1.8.7 & 1.9.2
6
6
 
7
7
  == Installation
8
8
 
9
9
  gem install rack-ssl-enforcer
10
10
 
11
11
 
12
- == Usage
12
+ == Basic Usage
13
13
 
14
- require 'rack-ssl-enforcer'
14
+ require 'rack/ssl-enforcer'
15
15
  use Rack::SslEnforcer
16
16
 
17
- This will redirect all requests to SSL. Rack::SslEnforcer accepts params:
17
+ Or, if you are using Bundler, just add this to your Gemfile:
18
+
19
+ gem 'rack-ssl-enforcer'
20
+
21
+ To use Rack::SslEnforcer in your Rails application, add the following line to your application
22
+ config file (config/application.rb for Rails3, config/environment.rb for Rails2):
23
+
24
+ config.middleware.use Rack::SslEnforcer
25
+
26
+ If all you want is SSL for your whole application, you are done! However, you can specify some
27
+
28
+
29
+ == Options
18
30
 
19
31
  You might need the :redirect_to option if the requested URL can't be determined (e.g. if using a proxy).
20
32
 
21
- use Rack::SslEnforcer, :redirect_to => 'https://example.org'
22
-
23
- You can also define specific regex patterns or paths to redirect.
24
-
25
- use Rack::SslEnforcer, :only => /^\/admin\//
26
- use Rack::SslEnforcer, :only => "/login"
27
- use Rack::SslEnforcer, :only => ["/login", /\.xml$/]
28
-
29
- And force http for non-https path
33
+ config.middleware.use Rack::SslEnforcer, :redirect_to => 'https://example.org'
34
+
35
+ You can also define specific regex patterns or paths or hosts to redirect.
30
36
 
31
- use Rack::SslEnforcer, :only => ["/login", /\.xml$/], :strict => true
37
+ config.middleware.use Rack::SslEnforcer, :only => /^\/admin\//
38
+ config.middleware.use Rack::SslEnforcer, :only => "/login"
39
+ config.middleware.use Rack::SslEnforcer, :only => ["/login", /\.xml$/]
40
+ config.middleware.use Rack::SslEnforcer, :only_hosts => 'api.example.com'
41
+ config.middleware.use Rack::SslEnforcer, :only_hosts => ["[www|api]\.example\.org", 'example.com']
42
+ config.middleware.use Rack::SslEnforcer, :except_hosts => 'help.example.com'
43
+ config.middleware.use Rack::SslEnforcer, :except_hosts => /[help|blog]\.example\.com$/
32
44
 
33
- To set HSTS expiry and subdomain inclusion (defaults: one year, true)
45
+ Note: hosts options take precedence over the path options. See tests for examples.
34
46
 
35
- use Rack::SslEnforcer, :hsts => {:expires => 500, :subdomains => false}
47
+ Use the :strict option to force http for all requests not matching your :only specification
48
+
49
+ config.middleware.use Rack::SslEnforcer, :only => ["/login", /\.xml$/], :strict => true
50
+ config.middleware.use Rack::SslEnforcer, :only_hosts => 'api.example.com', :strict => true
51
+
52
+ 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.
53
+
54
+ config.middleware.use Rack::SslEnforcer, :only => [/^\/users\/(.+)\/edit/], :mixed => true
55
+
56
+ The above will allow you to post/put from the secure/non-secure urls keeping the original schema.
57
+
58
+ To set HSTS expiry and subdomain inclusion (defaults: one year, true). Strict option disables HSTS.
59
+
60
+ config.middleware.use Rack::SslEnforcer, :hsts => { :expires => 500, :subdomains => false }
61
+ config.middleware.use Rack::SslEnforcer, :hsts => true # equivalent to { :expires => 31536000, :subdomains => true }
36
62
 
37
63
 
38
64
  == TODO
@@ -47,21 +73,22 @@ To set HSTS expiry and subdomain inclusion (defaults: one year, true)
47
73
  * {Rémy Coutable}[http://github.com/rymai]
48
74
  * {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]
49
75
  * {Paul Annesley}[https://github.com/pda]
76
+ * {Saimon Moore}[https://github.com/saimonmoore]
50
77
 
51
78
 
52
79
  == Credits
53
80
 
54
- Flagging cookies as secure functionality is greatly inspired by {Joshua Peek's Rack::SSL}[https://github.com/josh/rack-ssl]
81
+ Flagging cookies as secure functionality and HSTS support is greatly inspired by {Joshua Peek's Rack::SSL}[https://github.com/josh/rack-ssl]
55
82
 
56
83
 
57
84
  == Note on Patches/Pull Requests
58
-
85
+
59
86
  * Fork the project.
60
87
  * Make your feature addition or bug fix.
61
88
  * Add tests for it. This is important so I don't break it in a
62
89
  future version unintentionally.
63
90
  * Commit, do not mess with rakefile, version, or history.
64
- (if you want to have your own version,
91
+ (if you want to have your own version,
65
92
  that is fine but bump version in a commit by itself I can ignore when I pull)
66
93
  * Send me a pull request. Bonus points for topic branches.
67
94
 
@@ -0,0 +1 @@
1
+ require 'rack/ssl-enforcer'
@@ -1,39 +1,42 @@
1
1
  module Rack
2
2
  class SslEnforcer
3
-
3
+
4
4
  def initialize(app, options = {})
5
5
  @app, @options = app, options
6
6
  end
7
-
7
+
8
8
  def call(env)
9
9
  @req = Rack::Request.new(env)
10
- if enforce_ssl?(env)
10
+ if enforce_ssl?(@req)
11
11
  scheme = 'https' unless ssl_request?(env)
12
- elsif ssl_request?(env) && @options[:strict]
12
+ elsif ssl_request?(env) && enforcement_non_ssl?(env)
13
13
  scheme = 'http'
14
14
  end
15
-
15
+
16
16
  if scheme
17
- location = @options[:redirect_to] || replace_scheme(@req, scheme).url
17
+ location = replace_scheme(@req, scheme).url
18
18
  body = "<html><body>You are being <a href=\"#{location}\">redirected</a>.</body></html>"
19
19
  [301, { 'Content-Type' => 'text/html', 'Location' => location }, [body]]
20
20
  elsif ssl_request?(env)
21
21
  status, headers, body = @app.call(env)
22
22
  flag_cookies_as_secure!(headers)
23
- set_hsts_headers!(headers)
23
+ set_hsts_headers!(headers) if @options[:hsts] && !@options[:strict]
24
24
  [status, headers, body]
25
25
  else
26
26
  @app.call(env)
27
27
  end
28
28
  end
29
-
30
-
29
+
31
30
  private
32
-
31
+
32
+ def enforcement_non_ssl?(env)
33
+ true if @options[:strict] || @options[:mixed] && !(env['REQUEST_METHOD'] == 'PUT' || env['REQUEST_METHOD'] == 'POST')
34
+ end
35
+
33
36
  def ssl_request?(env)
34
37
  scheme(env) == 'https'
35
38
  end
36
-
39
+
37
40
  # Fixed in rack >= 1.3
38
41
  def scheme(env)
39
42
  if env['HTTPS'] == 'on'
@@ -44,29 +47,81 @@ module Rack
44
47
  env['rack.url_scheme']
45
48
  end
46
49
  end
47
-
48
- def enforce_ssl?(env)
49
- if @options[:only]
50
- rules = [@options[:only]].flatten
51
- rules.any? do |pattern|
52
- if pattern.is_a?(Regexp)
53
- @req.path =~ pattern
50
+
51
+ def matches?(key, pattern, req)
52
+ if pattern.is_a?(Regexp)
53
+ case key
54
+ when :only
55
+ req.path =~ pattern
56
+ when :except
57
+ req.path !~ pattern
58
+ when :only_hosts
59
+ req.host =~ pattern
60
+ when :except_hosts
61
+ req.host !~ pattern
62
+ end
63
+ else
64
+ case key
65
+ when :only
66
+ req.path[0,pattern.length] == pattern
67
+ when :except
68
+ req.path[0,pattern.length] != pattern
69
+ when :only_hosts
70
+ req.host == pattern
71
+ when :except_hosts
72
+ req.host != pattern
73
+ end
74
+ end
75
+ end
76
+
77
+ def enforce_ssl_for?(keys, req)
78
+ if keys.any? {|option| @options.key?(option)}
79
+ keys.any? do |key|
80
+ rules = [@options[key]].flatten.compact
81
+ rules.any? do |pattern|
82
+ matches?(key, pattern, req)
83
+ end
84
+ end
85
+ else
86
+ false
87
+ end
88
+ end
89
+
90
+ def enforce_ssl?(req)
91
+ path_keys = [:only, :except]
92
+ hosts_keys = [:only_hosts, :except_hosts]
93
+ if hosts_keys.any? {|option| @options.key?(option)}
94
+ if enforce_ssl_for?(hosts_keys, req)
95
+ if path_keys.any? {|option| @options.key?(option)}
96
+ enforce_ssl_for?(path_keys, req)
54
97
  else
55
- @req.path[0,pattern.length] == pattern
98
+ true
56
99
  end
100
+ else
101
+ false
57
102
  end
103
+ elsif path_keys.any? {|option| @options.key?(option)}
104
+ enforce_ssl_for?(path_keys, req)
58
105
  else
59
106
  true
60
107
  end
61
108
  end
62
-
109
+
63
110
  def replace_scheme(req, scheme)
111
+ if @options[:redirect_to]
112
+ uri = URI.split(@options[:redirect_to])
113
+ uri = uri[2] || uri[5]
114
+ else
115
+ uri = nil
116
+ end
64
117
  Rack::Request.new(req.env.merge(
65
118
  'rack.url_scheme' => scheme,
119
+ 'HTTP_X_FORWARDED_PROTO' => scheme,
120
+ 'HTTP_X_FORWARDED_PORT' => port_for(scheme).to_s,
66
121
  'SERVER_PORT' => port_for(scheme).to_s
67
- ))
122
+ ).merge(uri ? {'HTTP_HOST' => uri} : {}))
68
123
  end
69
-
124
+
70
125
  def port_for(scheme)
71
126
  scheme == 'https' ? 443 : 80
72
127
  end
@@ -83,7 +138,7 @@ module Rack
83
138
  }.join("\n")
84
139
  end
85
140
  end
86
-
141
+
87
142
  # see http://en.wikipedia.org/wiki/Strict_Transport_Security
88
143
  def set_hsts_headers!(headers)
89
144
  opts = { :expires => 31536000, :subdomains => true }.merge(@options[:hsts] || {})
@@ -91,6 +146,6 @@ module Rack
91
146
  value += "; includeSubDomains" if opts[:subdomains]
92
147
  headers.merge!({ 'Strict-Transport-Security' => value })
93
148
  end
94
-
149
+
95
150
  end
96
- end
151
+ end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class SslEnforcer
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
5
- end
5
+ end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-ssl-enforcer
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 21
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
8
  - 2
8
- - 0
9
- version: 0.2.0
9
+ - 1
10
+ version: 0.2.1
10
11
  platform: ruby
11
12
  authors:
12
13
  - Tobias Matthies
@@ -15,7 +16,7 @@ autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2010-11-17 00:00:00 +01:00
19
+ date: 2011-02-15 00:00:00 +01:00
19
20
  default_executable:
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
@@ -24,13 +25,14 @@ dependencies:
24
25
  requirement: &id001 !ruby/object:Gem::Requirement
25
26
  none: false
26
27
  requirements:
27
- - - ">="
28
+ - - ~>
28
29
  - !ruby/object:Gem::Version
30
+ hash: 19
29
31
  segments:
30
32
  - 0
31
- - 2
32
- - 1
33
- version: 0.2.1
33
+ - 3
34
+ - 0
35
+ version: 0.3.0
34
36
  type: :development
35
37
  version_requirements: *id001
36
38
  - !ruby/object:Gem::Dependency
@@ -41,101 +43,94 @@ dependencies:
41
43
  requirements:
42
44
  - - ~>
43
45
  - !ruby/object:Gem::Version
46
+ hash: 23
44
47
  segments:
45
48
  - 0
46
49
  - 1
47
- - 3
48
- version: 0.1.3
50
+ - 6
51
+ version: 0.1.6
49
52
  type: :development
50
53
  version_requirements: *id002
51
- - !ruby/object:Gem::Dependency
52
- name: guard-bundler
53
- prerelease: false
54
- requirement: &id003 !ruby/object:Gem::Requirement
55
- none: false
56
- requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- segments:
60
- - 0
61
- version: "0"
62
- type: :development
63
- version_requirements: *id003
64
54
  - !ruby/object:Gem::Dependency
65
55
  name: bundler
66
56
  prerelease: false
67
- requirement: &id004 !ruby/object:Gem::Requirement
57
+ requirement: &id003 !ruby/object:Gem::Requirement
68
58
  none: false
69
59
  requirements:
70
60
  - - ~>
71
61
  - !ruby/object:Gem::Version
62
+ hash: 3
72
63
  segments:
73
64
  - 1
74
65
  - 0
75
- - 5
76
- version: 1.0.5
66
+ - 10
67
+ version: 1.0.10
77
68
  type: :development
78
- version_requirements: *id004
69
+ version_requirements: *id003
79
70
  - !ruby/object:Gem::Dependency
80
71
  name: test-unit
81
72
  prerelease: false
82
- requirement: &id005 !ruby/object:Gem::Requirement
73
+ requirement: &id004 !ruby/object:Gem::Requirement
83
74
  none: false
84
75
  requirements:
85
76
  - - ~>
86
77
  - !ruby/object:Gem::Version
78
+ hash: 9
87
79
  segments:
88
80
  - 2
89
81
  - 1
90
82
  - 1
91
83
  version: 2.1.1
92
84
  type: :development
93
- version_requirements: *id005
85
+ version_requirements: *id004
94
86
  - !ruby/object:Gem::Dependency
95
87
  name: shoulda
96
88
  prerelease: false
97
- requirement: &id006 !ruby/object:Gem::Requirement
89
+ requirement: &id005 !ruby/object:Gem::Requirement
98
90
  none: false
99
91
  requirements:
100
92
  - - ~>
101
93
  - !ruby/object:Gem::Version
94
+ hash: 37
102
95
  segments:
103
96
  - 2
104
97
  - 11
105
98
  - 3
106
99
  version: 2.11.3
107
100
  type: :development
108
- version_requirements: *id006
101
+ version_requirements: *id005
109
102
  - !ruby/object:Gem::Dependency
110
103
  name: rack
111
104
  prerelease: false
112
- requirement: &id007 !ruby/object:Gem::Requirement
105
+ requirement: &id006 !ruby/object:Gem::Requirement
113
106
  none: false
114
107
  requirements:
115
108
  - - ~>
116
109
  - !ruby/object:Gem::Version
110
+ hash: 31
117
111
  segments:
118
112
  - 1
119
113
  - 2
120
114
  - 0
121
115
  version: 1.2.0
122
116
  type: :development
123
- version_requirements: *id007
117
+ version_requirements: *id006
124
118
  - !ruby/object:Gem::Dependency
125
119
  name: rack-test
126
120
  prerelease: false
127
- requirement: &id008 !ruby/object:Gem::Requirement
121
+ requirement: &id007 !ruby/object:Gem::Requirement
128
122
  none: false
129
123
  requirements:
130
124
  - - ~>
131
125
  - !ruby/object:Gem::Version
126
+ hash: 3
132
127
  segments:
133
128
  - 0
134
129
  - 5
135
130
  - 4
136
131
  version: 0.5.4
137
132
  type: :development
138
- version_requirements: *id008
133
+ version_requirements: *id007
139
134
  description: Rack::SslEnforcer is a simple Rack middleware to enforce ssl connections
140
135
  email:
141
136
  - tm@mit2m.de
@@ -147,9 +142,9 @@ extensions: []
147
142
  extra_rdoc_files: []
148
143
 
149
144
  files:
150
- - lib/rack/rack-ssl-enforcer.rb
151
145
  - lib/rack/ssl-enforcer/version.rb
152
146
  - lib/rack/ssl-enforcer.rb
147
+ - lib/rack-ssl-enforcer.rb
153
148
  - LICENSE
154
149
  - README.rdoc
155
150
  has_rdoc: true
@@ -166,6 +161,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
166
161
  requirements:
167
162
  - - ">="
168
163
  - !ruby/object:Gem::Version
164
+ hash: 3
169
165
  segments:
170
166
  - 0
171
167
  version: "0"
@@ -174,6 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
174
170
  requirements:
175
171
  - - ">="
176
172
  - !ruby/object:Gem::Version
173
+ hash: 23
177
174
  segments:
178
175
  - 1
179
176
  - 3
@@ -182,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
182
179
  requirements: []
183
180
 
184
181
  rubyforge_project: rack-ssl-enforcer
185
- rubygems_version: 1.3.7
182
+ rubygems_version: 1.5.0
186
183
  signing_key:
187
184
  specification_version: 3
188
185
  summary: A simple Rack middleware to enforce SSL
@@ -1 +0,0 @@
1
- require 'rack/ssl-enforcer'