rack-ssl-enforcer 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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