rack-ssl-enforcer 0.2.7 → 0.2.8

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.
@@ -1 +1 @@
1
- require 'rack/ssl-enforcer'
1
+ require 'rack/ssl-enforcer'
@@ -1,204 +1,204 @@
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
- @request = Rack::Request.new(env)
39
-
40
- return @app.call(env) if ignore?
41
-
42
- @scheme = if enforce_ssl?
43
- 'https'
44
- elsif enforce_non_ssl?
45
- 'http'
46
- end
47
-
48
- if redirect_required?
49
- call_before_redirect
50
- modify_location_and_redirect
51
- elsif ssl_request?
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?
64
- scheme_mismatch? || host_mismatch?
65
- end
66
-
67
- def ignore?
68
- if @options[:ignore]
69
- rules = [@options[:ignore]].flatten.compact
70
- rules.any? do |rule|
71
- SslEnforcerConstraint.new(:ignore, rule, @request).matches?
72
- end
73
- else
74
- false
75
- end
76
- end
77
-
78
- def scheme_mismatch?
79
- @scheme && @scheme != current_scheme
80
- end
81
-
82
- def host_mismatch?
83
- destination_host && destination_host != @request.host
84
- end
85
-
86
- def call_before_redirect
87
- @options[:before_redirect].call(@request) unless @options[:before_redirect].nil?
88
- end
89
-
90
- def modify_location_and_redirect
91
- location = "#{current_scheme}://#{@request.host}#{@request.fullpath}"
92
- location = replace_scheme(location, @scheme)
93
- location = replace_host(location, @options[:redirect_to])
94
- redirect_to(location)
95
- end
96
-
97
- def redirect_to(location)
98
- body = []
99
- body << "<html><body>You are being <a href=\"#{location}\">redirected</a>.</body></html>" if @options[:redirect_html].nil?
100
- body << @options[:redirect_html] if @options[:redirect_html].is_a?(String)
101
- body = @options[:redirect_html] if @options[:redirect_html].respond_to?('each')
102
-
103
- [@options[:redirect_code] || 301, { 'Content-Type' => 'text/html', 'Location' => location }, body]
104
- end
105
-
106
- def ssl_request?
107
- current_scheme == 'https'
108
- end
109
-
110
- def destination_host
111
- if @options[:redirect_to]
112
- host_parts = URI.split(URI.encode(@options[:redirect_to]))
113
- host_parts[2] || host_parts[5]
114
- end
115
- end
116
-
117
- # Fixed in rack >= 1.3
118
- def current_scheme
119
- if @request.env['HTTPS'] == 'on' || @request.env['HTTP_X_SSL_REQUEST'] == 'on'
120
- 'https'
121
- elsif @request.env['HTTP_X_FORWARDED_PROTO']
122
- @request.env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
123
- else
124
- @request.scheme
125
- end
126
- end
127
-
128
- def enforce_ssl_for?(keys)
129
- provided_keys = keys.select { |key| @options[key] }
130
- if provided_keys.empty?
131
- true
132
- else
133
- provided_keys.all? do |key|
134
- rules = [@options[key]].flatten.compact
135
- rules.send([:except_hosts, :except_agents, :except_environments, :except].include?(key) ? :all? : :any?) do |rule|
136
- SslEnforcerConstraint.new(key, rule, @request).matches?
137
- end
138
- end
139
- end
140
- end
141
-
142
- def enforce_non_ssl?
143
- @options[:strict] || @options[:mixed] && !(@request.request_method == 'PUT' || @request.request_method == 'POST')
144
- end
145
-
146
- def enforce_ssl?
147
- CONSTRAINTS_BY_TYPE.inject(true) do |memo, (type, keys)|
148
- memo && enforce_ssl_for?(keys)
149
- end
150
- end
151
-
152
- def replace_scheme(uri, scheme)
153
- return uri if not scheme_mismatch?
154
-
155
- port = adjust_port_to(scheme)
156
- uri_parts = URI.split(URI.encode(uri))
157
- uri_parts[3] = port unless port.nil?
158
- uri_parts[0] = scheme
159
- URI::HTTP.new(*uri_parts).to_s
160
- end
161
-
162
- def replace_host(uri, host)
163
- return uri unless host_mismatch?
164
-
165
- host_parts = URI.split(URI.encode(host))
166
- new_host = host_parts[2] || host_parts[5]
167
- uri_parts = URI.split(URI.encode(uri))
168
- uri_parts[2] = new_host
169
- URI::HTTPS.new(*uri_parts).to_s
170
- end
171
-
172
- def adjust_port_to(scheme)
173
- if scheme == 'https'
174
- @options[:https_port] if @options[:https_port] && @options[:https_port] != URI::HTTPS.default_port
175
- elsif scheme == 'http'
176
- @options[:http_port] if @options[:http_port] && @options[:http_port] != URI::HTTP.default_port
177
- end
178
- end
179
-
180
- # see http://en.wikipedia.org/wiki/HTTP_cookie#Cookie_theft_and_session_hijacking
181
- def flag_cookies_as_secure!(headers)
182
- if cookies = headers['Set-Cookie']
183
- # Support Rails 2.3 / Rack 1.1 arrays as headers
184
- unless cookies.is_a?(Array)
185
- cookies = cookies.split("\n")
186
- end
187
-
188
- headers['Set-Cookie'] = cookies.map do |cookie|
189
- cookie !~ /(^|;\s)secure($|;)/ ? "#{cookie}; secure" : cookie
190
- end.join("\n")
191
- end
192
- end
193
-
194
- # see http://en.wikipedia.org/wiki/Strict_Transport_Security
195
- def set_hsts_headers!(headers)
196
- opts = { :expires => 31536000, :subdomains => true }
197
- opts.merge!(@options[:hsts]) if @options[:hsts].is_a? Hash
198
- value = "max-age=#{opts[:expires]}"
199
- value += "; includeSubDomains" if opts[:subdomains]
200
- headers.merge!({ 'Strict-Transport-Security' => value })
201
- end
202
-
203
- end
204
- end
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
+ @request = Rack::Request.new(env)
39
+
40
+ return @app.call(env) if ignore?
41
+
42
+ @scheme = if enforce_ssl?
43
+ 'https'
44
+ elsif enforce_non_ssl?
45
+ 'http'
46
+ end
47
+
48
+ if redirect_required?
49
+ call_before_redirect
50
+ modify_location_and_redirect
51
+ elsif ssl_request?
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?
64
+ scheme_mismatch? || host_mismatch?
65
+ end
66
+
67
+ def ignore?
68
+ if @options[:ignore]
69
+ rules = [@options[:ignore]].flatten.compact
70
+ rules.any? do |rule|
71
+ SslEnforcerConstraint.new(:ignore, rule, @request).matches?
72
+ end
73
+ else
74
+ false
75
+ end
76
+ end
77
+
78
+ def scheme_mismatch?
79
+ @scheme && @scheme != current_scheme
80
+ end
81
+
82
+ def host_mismatch?
83
+ destination_host && destination_host != @request.host
84
+ end
85
+
86
+ def call_before_redirect
87
+ @options[:before_redirect].call(@request) unless @options[:before_redirect].nil?
88
+ end
89
+
90
+ def modify_location_and_redirect
91
+ location = "#{current_scheme}://#{@request.host}#{@request.fullpath}"
92
+ location = replace_scheme(location, @scheme)
93
+ location = replace_host(location, @options[:redirect_to])
94
+ redirect_to(location)
95
+ end
96
+
97
+ def redirect_to(location)
98
+ body = []
99
+ body << "<html><body>You are being <a href=\"#{location}\">redirected</a>.</body></html>" if @options[:redirect_html].nil?
100
+ body << @options[:redirect_html] if @options[:redirect_html].is_a?(String)
101
+ body = @options[:redirect_html] if @options[:redirect_html].respond_to?('each')
102
+
103
+ [@options[:redirect_code] || 301, { 'Content-Type' => 'text/html', 'Location' => location }, body]
104
+ end
105
+
106
+ def ssl_request?
107
+ current_scheme == 'https'
108
+ end
109
+
110
+ def destination_host
111
+ if @options[:redirect_to]
112
+ host_parts = URI.split(@options[:redirect_to])
113
+ host_parts[2] || host_parts[5]
114
+ end
115
+ end
116
+
117
+ # Fixed in rack >= 1.3
118
+ def current_scheme
119
+ if @request.env['HTTPS'] == 'on' || @request.env['HTTP_X_SSL_REQUEST'] == 'on'
120
+ 'https'
121
+ elsif @request.env['HTTP_X_FORWARDED_PROTO']
122
+ @request.env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
123
+ else
124
+ @request.scheme
125
+ end
126
+ end
127
+
128
+ def enforce_ssl_for?(keys)
129
+ provided_keys = keys.select { |key| @options[key] }
130
+ if provided_keys.empty?
131
+ true
132
+ else
133
+ provided_keys.all? do |key|
134
+ rules = [@options[key]].flatten.compact
135
+ rules.send([:except_hosts, :except_agents, :except_environments, :except].include?(key) ? :all? : :any?) do |rule|
136
+ SslEnforcerConstraint.new(key, rule, @request).matches?
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def enforce_non_ssl?
143
+ @options[:strict] || @options[:mixed] && !(@request.request_method == 'PUT' || @request.request_method == 'POST')
144
+ end
145
+
146
+ def enforce_ssl?
147
+ CONSTRAINTS_BY_TYPE.inject(true) do |memo, (type, keys)|
148
+ memo && enforce_ssl_for?(keys)
149
+ end
150
+ end
151
+
152
+ def replace_scheme(uri, scheme)
153
+ return uri if not scheme_mismatch?
154
+
155
+ port = adjust_port_to(scheme)
156
+ uri_parts = URI.split(uri)
157
+ uri_parts[3] = port unless port.nil?
158
+ uri_parts[0] = scheme
159
+ URI::HTTP.new(*uri_parts).to_s
160
+ end
161
+
162
+ def replace_host(uri, host)
163
+ return uri unless host_mismatch?
164
+
165
+ host_parts = URI.split(host)
166
+ new_host = host_parts[2] || host_parts[5]
167
+ uri_parts = URI.split(uri)
168
+ uri_parts[2] = new_host
169
+ URI::HTTPS.new(*uri_parts).to_s
170
+ end
171
+
172
+ def adjust_port_to(scheme)
173
+ if scheme == 'https'
174
+ @options[:https_port] if @options[:https_port] && @options[:https_port] != URI::HTTPS.default_port
175
+ elsif scheme == 'http'
176
+ @options[:http_port] if @options[:http_port] && @options[:http_port] != URI::HTTP.default_port
177
+ end
178
+ end
179
+
180
+ # see http://en.wikipedia.org/wiki/HTTP_cookie#Cookie_theft_and_session_hijacking
181
+ def flag_cookies_as_secure!(headers)
182
+ if cookies = headers['Set-Cookie']
183
+ # Support Rails 2.3 / Rack 1.1 arrays as headers
184
+ unless cookies.is_a?(Array)
185
+ cookies = cookies.split("\n")
186
+ end
187
+
188
+ headers['Set-Cookie'] = cookies.map do |cookie|
189
+ cookie !~ /(^|;\s)secure($|;)/ ? "#{cookie}; secure" : cookie
190
+ end.join("\n")
191
+ end
192
+ end
193
+
194
+ # see http://en.wikipedia.org/wiki/Strict_Transport_Security
195
+ def set_hsts_headers!(headers)
196
+ opts = { :expires => 31536000, :subdomains => true }
197
+ opts.merge!(@options[:hsts]) if @options[:hsts].is_a? Hash
198
+ value = "max-age=#{opts[:expires]}"
199
+ value += "; includeSubDomains" if opts[:subdomains]
200
+ headers.merge!({ 'Strict-Transport-Security' => value })
201
+ end
202
+
203
+ end
204
+ end
@@ -1,42 +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
- else
12
- result = tested_string.send(operator, @rule)
13
- end
14
-
15
- negate_result? ? !result : result
16
- end
17
-
18
- private
19
-
20
- def negate_result?
21
- @name.to_s =~ /except/
22
- end
23
-
24
- def operator
25
- @rule.is_a?(Regexp) ? "=~" : "=="
26
- end
27
-
28
- def tested_string
29
- case @name.to_s
30
- when /hosts/
31
- @request.host
32
- when /methods/
33
- @request.request_method
34
- when /environments/
35
- ENV["RACK_ENV"] || ENV["RAILS_ENV"] || ENV["ENV"]
36
- when /agents/
37
- @request.user_agent
38
- else
39
- @request.path
40
- end
41
- end
42
- end
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