spinels-rack-ssl-enforcer 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []