spinels-rack-ssl-enforcer 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3733938868eed1e628160489ff7e1e037bf2258bbbccc4c711f976b9a7292b78
4
+ data.tar.gz: 9dcace39e52062c55a6ad68490dd750382e0d9400a22e6f315d4dadf1918e961
5
+ SHA512:
6
+ metadata.gz: e38fa0bd54a6693492983439f9bfe6f709093b2529bfe233469bc787ad82d83f948960e1868613b5f8c53c8aec948712404eade6428cfdfadcc30917df17bb55
7
+ data.tar.gz: d4812f9e02c859da5f3f4545e0a6a03d41376bd723ec7e850dc8212d984ae17d0d5a42442842578cc323564569cdfbe55ae8c3aeffb8d15c62e634d1395a0473
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009-2012 Tobias Matthies
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,340 @@
1
+ **`spinels/rack-ssl-enforcer`** is a fork of [`tobmatth/rack-ssl-enforcer`][tobmatth/rack-ssl-enforcer] with the following patches applied:
2
+
3
+ - https://github.com/tobmatth/rack-ssl-enforcer/pull/105
4
+
5
+ It has been republished to RubyGems under the name [`spinels-rack-ssl-enforcer`](https://rubygems.org/gems/spinels-rack-ssl-enforcer).
6
+
7
+ [tobmatth/rack-ssl-enforcer]: https://github.com/tobmatth/rack-ssl-enforcer
8
+
9
+ ---
10
+
11
+ # Rack::SslEnforcer [![CI](https://github.com/spinels/rack-ssl-enforcer/actions/workflows/ci.yml/badge.svg)](https://github.com/spinels/rack-ssl-enforcer/actions/workflows/ci.yml)
12
+
13
+ Rack::SslEnforcer is a simple Rack middleware to enforce TLS/SSL connections. As of Version 0.2.0, Rack::SslEnforcer marks
14
+ Cookies as secure by default (HSTS must be set manually).
15
+
16
+ ## Installation
17
+
18
+ The simplest way to install Rack::SslEnforcer is to use [Bundler](https://bundler.io/).
19
+
20
+ Add Rack::SslEnforcer to your `Gemfile`:
21
+
22
+ ```ruby
23
+ gem 'spinels-rack-ssl-enforcer'
24
+ ```
25
+
26
+ ### Installing on Sinatra / Padrino application
27
+
28
+ In order for Rack::SslEnforcer to properly work it has to be at the top
29
+ of the Rack Middleware.
30
+
31
+ Using `enable :session` will place Rack::Session::Cookie before Rack::Ssl::Enforcer
32
+ and will prevent Rack::Ssl::Enforcer from marking cookies as secure.
33
+
34
+ To fix this issue do not use `enable :sessions` instead add the
35
+ Rack::Session::Cookie middleware after Rack::Ssl::Enforcer.
36
+
37
+ Eg:
38
+
39
+ ```ruby
40
+ use Rack::SslEnforcer
41
+ set :session_secret, 'asdfa2342923422f1adc05c837fa234230e3594b93824b00e930ab0fb94b'
42
+
43
+ #Enable sinatra sessions
44
+ use Rack::Session::Cookie, :key => '_rack_session',
45
+ :path => '/',
46
+ :expire_after => 2592000, # In seconds
47
+ :secret => settings.session_secret
48
+ ```
49
+
50
+ ## Basic Usage
51
+
52
+ If you don't use Bundler, be sure to require Rack::SslEnforcer manually before actually using the middleware:
53
+
54
+ ```ruby
55
+ require 'rack/ssl-enforcer'
56
+ use Rack::SslEnforcer
57
+ ```
58
+
59
+ To use Rack::SslEnforcer in your Rails application, add the following line to your application config file (`config/application.rb` for Rails 3 and above, `config/environment.rb` for Rails 2):
60
+
61
+ ```ruby
62
+ config.middleware.insert_before ActionDispatch::Cookies, Rack::SslEnforcer
63
+ ```
64
+ Cookies should be set before the SslEnforcer processes the headers,
65
+ but due to the middleware calls chain Rack::SslEnforcer should be inserted before the ActionDispatch::Cookies.
66
+
67
+ If all you want is SSL for your whole application, you are done! Otherwise, you can specify some options described below.
68
+
69
+ ## Options
70
+
71
+ ### Host constraints
72
+
73
+ You can enforce SSL connections only for certain hosts with `:only_hosts`, or prevent certain hosts from being forced to SSL with `:except_hosts`. Constraints can be a `String`, a `Regex` or an array of `String` or `Regex` (possibly mixed), as shown in the following examples:
74
+
75
+ ```ruby
76
+ config.middleware.use Rack::SslEnforcer, :only_hosts => 'api.example.com'
77
+ # Please note that, for instance, both http://help.example.com/demo and https://help.example.com/demo would be accessible here
78
+
79
+ config.middleware.use Rack::SslEnforcer, :except_hosts => /(help|blog)\.example\.com$/
80
+
81
+ config.middleware.use Rack::SslEnforcer, :only_hosts => [/(help|blog)\.example\.org$/, 'api.example.com']
82
+ ```
83
+
84
+ ### Path constraints
85
+
86
+ You can enforce SSL connections only for certain paths with `:only`, prevent certain paths from being forced to SSL with `:except`, or - if you don't care how certain paths are accessed - ignore them with `:ignore`. Constraints can be a `String`, a `Regex` or an array of `String` or `Regex` (possibly mixed), as shown in the following examples:
87
+
88
+ ```ruby
89
+ config.middleware.use Rack::SslEnforcer, :only => '/login'
90
+ # Please note that, for instance, both http://example.com/demo and https://example.com/demo would be accessible here
91
+
92
+ config.middleware.use Rack::SslEnforcer, :only => %r{^/admin/}
93
+
94
+ config.middleware.use Rack::SslEnforcer, :except => ['/demo', %r{^/public/}]
95
+
96
+ config.middleware.use Rack::SslEnforcer, :ignore => '/assets'
97
+
98
+ # You can also combine multiple constraints
99
+ config.middleware.use Rack::SslEnforcer, :only => '/cart', :ignore => %r{/assets}, :strict => true
100
+
101
+ # And ignore based on blocks
102
+ config.middleware.use Rack::SslEnforcer, :ignore => lambda { |request| request.env["HTTP_X_IGNORE_SSL_ENFORCEMENT"] == "magic" }
103
+ ```
104
+
105
+ ### Method constraints
106
+
107
+ You can enforce SSL connections only for certain HTTP methods with `:only_methods`, or prevent certain HTTP methods from being forced to SSL with `:except_methods`. Constraints can be a `String` or an array of `String`, as shown in the following examples:
108
+
109
+ ```ruby
110
+ # constraint as a String
111
+ config.middleware.use Rack::SslEnforcer, :only_methods => 'POST'
112
+ # Please note that, for instance, GET requests would be accessible via SSL and non-SSL connection here
113
+
114
+ config.middleware.use Rack::SslEnforcer, :except_methods => ['GET', 'HEAD']
115
+ ```
116
+
117
+ Note: The `:hosts` constraint takes precedence over the `:path` constraint. Please see the tests for examples.
118
+
119
+
120
+ ### Environment constraints
121
+
122
+ You can enforce SSL connections only for certain environments with `:only_environments` or prevent certain environments from being forced to SSL with `:except_environments`.
123
+ Environment constraints may be a `String`, a `Regex` or an array of `String` or `Regex` (possibly mixed), as shown in the following examples:
124
+
125
+ ```ruby
126
+ config.middleware.use Rack::SslEnforcer, :except_environments => 'development'
127
+
128
+ config.middleware.use Rack::SslEnforcer, :except_environments => /^[0-9a-f]+_local$/i
129
+
130
+ config.middleware.use Rack::SslEnforcer, :only_environments => ['production', /^QA/]
131
+ ```
132
+
133
+ Note: The `:environments` constraint requires one the following environment variables to be set: `RACK_ENV`, `RAILS_ENV`, `ENV`.
134
+
135
+ ### User agent constraints
136
+
137
+ You can enforce SSL connections only for certain user agents with `:only_agents` or prevent certain user agents from being forced to SSL with `:except_agents`.
138
+ User agent constraints may be a `String`, a `Regex` or an array of `String` or `Regex` (possibly mixed), as shown in the following examples:
139
+
140
+ ```ruby
141
+ config.middleware.use Rack::SslEnforcer, :except_agents => 'Googlebot'
142
+
143
+ config.middleware.use Rack::SslEnforcer, :except_agents => /Googlebot|bingbot/
144
+
145
+ config.middleware.use Rack::SslEnforcer, :only_agents => ['test-secu-bot', /custom-crawler[0-9a-f]/]
146
+ ```
147
+
148
+ ### Force-redirection to non-SSL connection if constraint is not matched
149
+
150
+ Use the `:strict` option to force non-SSL connection for all requests not matching the constraints you set. Examples:
151
+
152
+ ```ruby
153
+ config.middleware.use Rack::SslEnforcer, :only => ["/login", /\.xml$/], :strict => true
154
+ # https://example.com/demo would be redirected to http://example.com/demo
155
+
156
+ config.middleware.use Rack::SslEnforcer, :except_hosts => 'demo.example.com', :strict => true
157
+ # https://demo.example.com would be redirected to http://demo.example.com
158
+ ```
159
+
160
+ ### Automatic method constraints
161
+
162
+ In the case where you have matching URLs with different HTTP methods – for instance Rails RESTful routes: `GET /users`, `POST /users`, `GET /user/:id` and `PUT /user/:id` – you may need to force POST and PUT requests to SSL connection but redirect to non-SSL connection on GET.
163
+
164
+ ```ruby
165
+ config.middleware.use Rack::SslEnforcer, :only => [%r{^/users/}], :mixed => true
166
+ ```
167
+
168
+ The above will allow you to POST/PUT from the secure/non-secure URLs keeping the original schema.
169
+
170
+ ### HTTP Strict Transport Security (HSTS)
171
+
172
+ To set HSTS expiry and subdomain inclusion (defaults respectively to `one year` and `true`).
173
+
174
+ ```ruby
175
+ config.middleware.use Rack::SslEnforcer, :hsts => { :expires => 500, :subdomains => false, :preload => false }
176
+ config.middleware.use Rack::SslEnforcer, :hsts => true # equivalent to { :expires => 31536000, :subdomains => true, :preload => false }
177
+ ```
178
+ Please note that the strict option disables HSTS.
179
+
180
+ ### Redirect to specific URL (e.g. if you're using a proxy)
181
+
182
+ You might need the `:redirect_to` option if the requested URL can't be determined.
183
+
184
+ ```ruby
185
+ config.middleware.use Rack::SslEnforcer, :redirect_to => 'https://example.org'
186
+ ```
187
+
188
+ ### Redirect with specific HTTP status code
189
+
190
+ By default it redirects with HTTP status code 301(Moved Permanently). Sometimes you might need to redirect with different HTTP status code:
191
+
192
+ ```ruby
193
+ config.middleware.use Rack::SslEnforcer, :redirect_code => 302
194
+ ```
195
+
196
+ ### Custom HTTP port
197
+
198
+ If you're using a different port than the default (80) for HTTP, you can specify it with the `:http_port` option:
199
+
200
+ ```ruby
201
+ config.middleware.use Rack::SslEnforcer, :http_port => 8080
202
+ ```
203
+
204
+ ### Custom HTTPS port
205
+
206
+ If you're using a different port than the default (443) for HTTPS, you can specify it with the `:https_port` option:
207
+
208
+ ```ruby
209
+ config.middleware.use Rack::SslEnforcer, :https_port => 444
210
+ ```
211
+
212
+ ### Secure cookies disabling
213
+
214
+ Finally you might want to share a cookie based session between HTTP and HTTPS.
215
+ This is not possible by default with Rack::SslEnforcer for [security reasons](http://en.wikipedia.org/wiki/HTTP_cookie#Cookie_theft_and_session_hijacking).
216
+
217
+ Nevertheless, you can set the `:force_secure_cookies` option to `false` in order to be able to share a cookie based session between HTTP and HTTPS:
218
+
219
+ ```ruby
220
+ config.middleware.use Rack::SslEnforcer, :only => "/login", :force_secure_cookies => false
221
+ ```
222
+
223
+ But be aware that if you do so, you have to make sure that the content of you cookie is encoded.
224
+ This can be done using a coder with [Rack::Session::Cookie](https://github.com/rack/rack/blob/master/lib/rack/session/cookie.rb#L28-42).
225
+
226
+ ### Running code before redirect
227
+
228
+ You may want to run some code before rack-ssl-enforcer forces a redirect. This code could do something like log that a redirect was done, or if this is used in a Rails app, keeping the flash when redirecting:
229
+
230
+
231
+ ```ruby
232
+ config.middleware.use Rack::SslEnforcer, :only => '/login', :before_redirect => Proc.new { |request|
233
+ #keep flash on redirect
234
+ request.session[:flash].keep if !request.session.nil? && request.session.key?('flash') && !request.session['flash'].empty?
235
+ }
236
+ ```
237
+
238
+ ### Custom or empty response body
239
+
240
+ The default response body is:
241
+
242
+ ```html
243
+ <html><body>You are being <a href="https://www.example.org/">redirected</a>.</body></html>
244
+ ```
245
+
246
+ To supply a custom message, provide a string as `:redirect_html`:
247
+
248
+ ```ruby
249
+ config.middleware.use Rack::SslEnforcer, :redirect_html => 'Redirecting!'
250
+ ```
251
+
252
+ To supply a series of responses for Rack to chunk, provide a [permissable Rack body object](http://rack.rubyforge.org/doc/SPEC.html):
253
+
254
+ ```ruby
255
+ config.middleware.use Rack::SslEnforcer, :redirect_html => ['<html>','<body>','Hello!','</body>','</html>']
256
+ ```
257
+
258
+ To supply an empty response body, provide :redirect_html as false:
259
+
260
+ ```ruby
261
+ config.middleware.use Rack::SslEnforcer, :redirect_html => false
262
+ ```
263
+
264
+ ## Deployment
265
+
266
+ If you run your application behind a proxy (e.g. Nginx) you may need to do some configuration on that side. If you don't you may experience an infinite redirect loop.
267
+
268
+ The reason this happens is that Rack::SslEnforcer can't detect if you are running SSL or not. The solution is to have your front-end server send extra headers for Rack::SslEnforcer to identify the request protocol.
269
+
270
+ ### Nginx
271
+
272
+ In the `location` block for your app's SSL configuration, include the following proxy header configuration:
273
+
274
+ `proxy_set_header X-Forwarded-Proto https;`
275
+
276
+ ### Nginx behind Load Balancer
277
+
278
+ The following instruction has been tested behind AWS ELB, but can be adapted to work behind any load balancer.
279
+ Specifically, the options below account for termination of SSL at the load balancer (HTTP only communication between load balancer and server), and HTTP only health checks.
280
+
281
+ In the `location` block for your app's SSL configuration, reference the following proxy header configurations:
282
+
283
+ ```
284
+ proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
285
+ proxy_set_header Host $http_host;
286
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for
287
+ proxy_redirect off;
288
+ ```
289
+
290
+ In `config/application.rb` for Rails 3 and above, `config/environment.rb` for Rails 2, work off the following line:
291
+
292
+ ```ruby
293
+ config.middleware.use Rack::SslEnforcer, ignore: lambda { |request| request.env["HTTP_X_FORWARDED_PROTO"].blank? }, strict: true
294
+ ```
295
+
296
+ This ignores ELB healthchecks and development environment as ELB healthchecks aren't forwarded requests, so it wouldn't have the forwarded proto header.
297
+
298
+ Same goes for when running without a proxy (like developemnt locally), making `:except_environments => 'development'` unnecessary.
299
+
300
+ In Sinatra or other non-rails ruby applications, work off the following line:
301
+ ```ruby
302
+ use Rack::SslEnforcer, :except_environments => ['development', 'test'], :ignore => '/healthcheck'
303
+ ```
304
+
305
+ ### Passenger
306
+
307
+ Or, if you're using mod_rails/passenger (which will ignore the proxy_xxx directives):
308
+
309
+ `passenger_set_cgi_param HTTP_X_FORWARDED_PROTO https;`
310
+
311
+ If you're sharing a single `server` block for http AND https access you can add:
312
+
313
+ `passenger_set_cgi_param HTTP_X_FORWARDED_PROTO $scheme;`
314
+
315
+ This makes sure that Rack::SslEnforcer knows it's being accessed over SSL. Just restart Nginx for these changes to take effect.
316
+
317
+ ## TODO
318
+
319
+ * Cleanup tests
320
+
321
+ #### Contributors
322
+
323
+ - [https://github.com/tobmatth/rack-ssl-enforcer/graphs/contributors](https://github.com/tobmatth/rack-ssl-enforcer/graphs/contributors)
324
+ - [https://github.com/spinels/rack-ssl-enforcer/graphs/contributors](https://github.com/spinels/rack-ssl-enforcer/graphs/contributors)
325
+
326
+ ## Credits
327
+
328
+ Flagging cookies as secure functionality and HSTS support is greatly inspired by [Joshua Peek's Rack::SSL](https://rubygems.org/gems/rack-ssl).
329
+
330
+ ## Note on Patches / Pull Requests
331
+
332
+ * Fork the project.
333
+ * Code your feature addition or bug fix.
334
+ * **Add tests for it.** This is important so we don't break it in a future version unintentionally.
335
+ * Commit, do not mess with Rakefile or version number. If you want to have your own version, that's fine but bump version in a commit by itself so we can ignore it when merging.
336
+ * Send a pull request. Bonus points for topic branches.
337
+
338
+ ## Copyright
339
+
340
+ Copyright (c) 2010-2013 Tobias Matthies. See [LICENSE](https://github.com/tobmatth/rack-ssl-enforcer/blob/master/LICENSE) for details.
@@ -0,0 +1,44 @@
1
+ class SslEnforcerConstraint
2
+ def initialize(name, rule, request)
3
+ @name = name
4
+ @rule = rule
5
+ @request = request
6
+ end
7
+
8
+ def matches?
9
+ if @rule.is_a?(String) && [:only, :except].include?(@name)
10
+ result = tested_string[0, @rule.size].send(operator, @rule)
11
+ elsif @rule.respond_to?(:call)
12
+ result = @rule.call(@request)
13
+ else
14
+ result = tested_string.send(operator, @rule)
15
+ end
16
+
17
+ negate_result? ? !result : result
18
+ end
19
+
20
+ private
21
+
22
+ def negate_result?
23
+ @name.to_s =~ /except/
24
+ end
25
+
26
+ def operator
27
+ @rule.is_a?(Regexp) ? "=~" : "=="
28
+ end
29
+
30
+ def tested_string
31
+ case @name.to_s
32
+ when /hosts/
33
+ @request.host
34
+ when /methods/
35
+ @request.request_method
36
+ when /environments/
37
+ ENV["RACK_ENV"] || ENV["RAILS_ENV"] || ENV["ENV"]
38
+ when /agents/
39
+ @request.user_agent
40
+ else
41
+ @request.path
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class SslEnforcer
3
+ VERSION = "0.3.0"
4
+ end
5
+ end
@@ -0,0 +1,207 @@
1
+ require 'rack/ssl-enforcer/constraint'
2
+
3
+ module Rack
4
+
5
+ class SslEnforcer
6
+
7
+ CONSTRAINTS_BY_TYPE = {
8
+ :hosts => [:only_hosts, :except_hosts],
9
+ :agents => [:only_agents, :except_agents],
10
+ :path => [:only, :except],
11
+ :methods => [:only_methods, :except_methods],
12
+ :environments => [:only_environments, :except_environments]
13
+ }
14
+
15
+ # Warning: If you set the option force_secure_cookies to false, make sure that your cookies
16
+ # are encoded and that you understand the consequences (see documentation)
17
+ def initialize(app, options={})
18
+ default_options = {
19
+ :redirect_to => nil,
20
+ :redirect_code => nil,
21
+ :strict => false,
22
+ :mixed => false,
23
+ :hsts => nil,
24
+ :http_port => nil,
25
+ :https_port => nil,
26
+ :force_secure_cookies => true,
27
+ :redirect_html => nil,
28
+ :before_redirect => nil
29
+ }
30
+ CONSTRAINTS_BY_TYPE.values.each do |constraints|
31
+ constraints.each { |constraint| default_options[constraint] = nil }
32
+ end
33
+
34
+ @app, @options = app, default_options.merge(options)
35
+ end
36
+
37
+ def call(env)
38
+ req = Rack::Request.new(env)
39
+
40
+ return @app.call(env) if ignore?(req)
41
+
42
+ scheme = if enforce_ssl?(req)
43
+ 'https'
44
+ elsif enforce_non_ssl?(req)
45
+ 'http'
46
+ end
47
+
48
+ if redirect_required?(req, scheme)
49
+ call_before_redirect(req)
50
+ modify_location_and_redirect(req, scheme)
51
+ elsif ssl_request?(req)
52
+ status, headers, body = @app.call(env)
53
+ flag_cookies_as_secure!(headers) if @options[:force_secure_cookies]
54
+ set_hsts_headers!(headers) if @options[:hsts] && !@options[:strict]
55
+ [status, headers, body]
56
+ else
57
+ @app.call(env)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def redirect_required?(req, scheme)
64
+ scheme_mismatch?(req, scheme) || host_mismatch?(req)
65
+ end
66
+
67
+ def ignore?(req)
68
+ if @options[:ignore]
69
+ rules = [@options[:ignore]].flatten.compact
70
+ rules.any? do |rule|
71
+ SslEnforcerConstraint.new(:ignore, rule, req).matches?
72
+ end
73
+ else
74
+ false
75
+ end
76
+ end
77
+
78
+ def scheme_mismatch?(req, scheme)
79
+ scheme && scheme != current_scheme(req)
80
+ end
81
+
82
+ def host_mismatch?(req)
83
+ destination_host && destination_host != req.host
84
+ end
85
+
86
+ def call_before_redirect(req)
87
+ @options[:before_redirect].call(req) unless @options[:before_redirect].nil?
88
+ end
89
+
90
+ def modify_location_and_redirect(req, scheme)
91
+ location = "#{current_scheme(req)}://#{req.host}#{req.fullpath}"
92
+ location = replace_scheme(location, req, scheme)
93
+ location = replace_host(location, req, @options[:redirect_to])
94
+ redirect_to(location)
95
+ rescue URI::InvalidURIError
96
+ [400, { 'Content-Type' => 'text/plain'}, []]
97
+ end
98
+
99
+ def redirect_to(location)
100
+ body = []
101
+ body << "<html><body>You are being <a href=\"#{location}\">redirected</a>.</body></html>" if @options[:redirect_html].nil?
102
+ body << @options[:redirect_html] if @options[:redirect_html].is_a?(String)
103
+ body = @options[:redirect_html] if @options[:redirect_html].respond_to?('each')
104
+
105
+ [@options[:redirect_code] || 301, { 'Content-Type' => 'text/html', 'Location' => location }, body]
106
+ end
107
+
108
+ def ssl_request?(req)
109
+ current_scheme(req) == 'https'
110
+ end
111
+
112
+ def destination_host
113
+ if @options[:redirect_to]
114
+ host_parts = URI.split(@options[:redirect_to])
115
+ host_parts[2] || host_parts[5]
116
+ end
117
+ end
118
+
119
+ # Fixed in rack >= 1.3
120
+ def current_scheme(req)
121
+ if req.env['HTTPS'] == 'on' || req.env['HTTP_X_SSL_REQUEST'] == 'on'
122
+ 'https'
123
+ elsif req.env['HTTP_X_FORWARDED_PROTO']
124
+ req.env['HTTP_X_FORWARDED_PROTO'].split(',')[0] || req.scheme
125
+ else
126
+ req.scheme
127
+ end
128
+ end
129
+
130
+ def enforce_ssl_for?(keys, req)
131
+ provided_keys = keys.select { |key| @options[key] }
132
+ if provided_keys.empty?
133
+ true
134
+ else
135
+ provided_keys.all? do |key|
136
+ rules = [@options[key]].flatten.compact
137
+ rules.send([:except_hosts, :except_agents, :except_environments, :except].include?(key) ? :all? : :any?) do |rule|
138
+ SslEnforcerConstraint.new(key, rule, req).matches?
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ def enforce_non_ssl?(req)
145
+ @options[:strict] || @options[:mixed] && !(req.request_method == 'PUT' || req.request_method == 'POST')
146
+ end
147
+
148
+ def enforce_ssl?(req)
149
+ CONSTRAINTS_BY_TYPE.inject(true) do |memo, (type, keys)|
150
+ memo && enforce_ssl_for?(keys, req)
151
+ end
152
+ end
153
+
154
+ def replace_scheme(uri, req, scheme)
155
+ return uri if not scheme_mismatch?(req, scheme)
156
+
157
+ port = adjust_port_to(scheme)
158
+ uri_parts = URI.split(uri)
159
+ uri_parts[3] = port unless port.nil?
160
+ uri_parts[0] = scheme
161
+ URI::HTTP.new(*uri_parts).to_s
162
+ end
163
+
164
+ def replace_host(uri, req, host)
165
+ return uri unless host_mismatch?(req)
166
+
167
+ host_parts = URI.split(host)
168
+ new_host = host_parts[2] || host_parts[5]
169
+ uri_parts = URI.split(uri)
170
+ uri_parts[2] = new_host
171
+ URI::HTTPS.new(*uri_parts).to_s
172
+ end
173
+
174
+ def adjust_port_to(scheme)
175
+ if scheme == 'https'
176
+ @options[:https_port] if @options[:https_port] && @options[:https_port] != URI::HTTPS.default_port
177
+ elsif scheme == 'http'
178
+ @options[:http_port] if @options[:http_port] && @options[:http_port] != URI::HTTP.default_port
179
+ end
180
+ end
181
+
182
+ # see http://en.wikipedia.org/wiki/HTTP_cookie#Cookie_theft_and_session_hijacking
183
+ def flag_cookies_as_secure!(headers)
184
+ if cookies = headers['Set-Cookie']
185
+ # Support Rails 2.3 / Rack 1.1 arrays as headers
186
+ unless cookies.is_a?(Array)
187
+ cookies = cookies.split("\n")
188
+ end
189
+
190
+ headers['Set-Cookie'] = cookies.map do |cookie|
191
+ cookie !~ /(^|;\s)secure($|;)/ ? "#{cookie}; secure" : cookie
192
+ end.join("\n")
193
+ end
194
+ end
195
+
196
+ # see http://en.wikipedia.org/wiki/Strict_Transport_Security
197
+ def set_hsts_headers!(headers)
198
+ opts = { :expires => 31536000, :subdomains => true, :preload => false }
199
+ opts.merge!(@options[:hsts]) if @options[:hsts].is_a? Hash
200
+ value = "max-age=#{opts[:expires]}"
201
+ value += "; includeSubDomains" if opts[:subdomains]
202
+ value += "; preload" if opts[:preload]
203
+ headers.merge!({ 'Strict-Transport-Security' => value })
204
+ end
205
+
206
+ end
207
+ end
@@ -0,0 +1 @@
1
+ require 'rack/ssl-enforcer'
@@ -0,0 +1 @@
1
+ require 'rack/ssl-enforcer'
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spinels-rack-ssl-enforcer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Matthies
8
+ - Thibaud Guillaume-Gentil
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2022-01-09 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Rack::SslEnforcer is a simple Rack middleware to enforce ssl connections
15
+ email:
16
+ - github@tobiasmatthies.de
17
+ - thibaud@thibaud.me
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README.md
24
+ - lib/rack-ssl-enforcer.rb
25
+ - lib/rack/ssl-enforcer.rb
26
+ - lib/rack/ssl-enforcer/constraint.rb
27
+ - lib/rack/ssl-enforcer/version.rb
28
+ - lib/spinels-rack-ssl-enforcer.rb
29
+ homepage: http://github.com/spinels/rack-ssl-enforcer
30
+ licenses:
31
+ - MIT
32
+ metadata:
33
+ rubygems_mfa_required: 'true'
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.9.3
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.3.6
48
+ requirements: []
49
+ rubygems_version: 3.2.33
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: A simple Rack middleware to enforce SSL
53
+ test_files: []