class Rack::SslEnforcer
Constants
- CONSTRAINTS_BY_TYPE
- VERSION
Public Class Methods
new(app, options={})
click to toggle source
Warning: If you set the option force_secure_cookies to false, make sure that your cookies are encoded and that you understand the consequences (see documentation)
# File lib/rack/ssl-enforcer.rb, line 17 def initialize(app, options={}) default_options = { :redirect_to => nil, :redirect_code => nil, :strict => false, :mixed => false, :hsts => nil, :http_port => nil, :https_port => nil, :force_secure_cookies => true, :redirect_html => nil, :before_redirect => nil } CONSTRAINTS_BY_TYPE.values.each do |constraints| constraints.each { |constraint| default_options[constraint] = nil } end @app, @options = app, default_options.merge(options) end
Public Instance Methods
call(env)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 37 def call(env) req = Rack::Request.new(env) return @app.call(env) if ignore?(req) scheme = if enforce_ssl?(req) 'https' elsif enforce_non_ssl?(req) 'http' end if redirect_required?(req, scheme) call_before_redirect(req) modify_location_and_redirect(req, scheme) elsif ssl_request?(req) status, headers, body = @app.call(env) flag_cookies_as_secure!(headers) if @options[:force_secure_cookies] set_hsts_headers!(headers) if @options[:hsts] && !@options[:strict] [status, headers, body] else @app.call(env) end end
Private Instance Methods
adjust_port_to(scheme)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 174 def adjust_port_to(scheme) if scheme == 'https' @options[:https_port] if @options[:https_port] && @options[:https_port] != URI::HTTPS.default_port elsif scheme == 'http' @options[:http_port] if @options[:http_port] && @options[:http_port] != URI::HTTP.default_port end end
call_before_redirect(req)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 86 def call_before_redirect(req) @options[:before_redirect].call(req) unless @options[:before_redirect].nil? end
current_scheme(req)
click to toggle source
Fixed in rack >= 1.3
# File lib/rack/ssl-enforcer.rb, line 120 def current_scheme(req) if req.env['HTTPS'] == 'on' || req.env['HTTP_X_SSL_REQUEST'] == 'on' 'https' elsif req.env['HTTP_X_FORWARDED_PROTO'] req.env['HTTP_X_FORWARDED_PROTO'].split(',')[0] || req.scheme else req.scheme end end
destination_host()
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 112 def destination_host if @options[:redirect_to] host_parts = URI.split(@options[:redirect_to]) host_parts[2] || host_parts[5] end end
enforce_non_ssl?(req)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 144 def enforce_non_ssl?(req) @options[:strict] || @options[:mixed] && !(req.request_method == 'PUT' || req.request_method == 'POST') end
enforce_ssl?(req)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 148 def enforce_ssl?(req) CONSTRAINTS_BY_TYPE.inject(true) do |memo, (type, keys)| memo && enforce_ssl_for?(keys, req) end end
enforce_ssl_for?(keys, req)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 130 def enforce_ssl_for?(keys, req) provided_keys = keys.select { |key| @options[key] } if provided_keys.empty? true else provided_keys.all? do |key| rules = [@options[key]].flatten.compact rules.send([:except_hosts, :except_agents, :except_environments, :except].include?(key) ? :all? : :any?) do |rule| SslEnforcerConstraint.new(key, rule, req).matches? end end end end
host_mismatch?(req)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 82 def host_mismatch?(req) destination_host && destination_host != req.host end
ignore?(req)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 67 def ignore?(req) if @options[:ignore] rules = [@options[:ignore]].flatten.compact rules.any? do |rule| SslEnforcerConstraint.new(:ignore, rule, req).matches? end else false end end
modify_location_and_redirect(req, scheme)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 90 def modify_location_and_redirect(req, scheme) location = "#{current_scheme(req)}://#{req.host}#{req.fullpath}" location = replace_scheme(location, req, scheme) location = replace_host(location, req, @options[:redirect_to]) redirect_to(location) rescue URI::InvalidURIError [400, { 'Content-Type' => 'text/plain'}, []] end
redirect_required?(req, scheme)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 63 def redirect_required?(req, scheme) scheme_mismatch?(req, scheme) || host_mismatch?(req) end
redirect_to(location)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 99 def redirect_to(location) body = [] body << "<html><body>You are being <a href=\"#{location}\">redirected</a>.</body></html>" if @options[:redirect_html].nil? body << @options[:redirect_html] if @options[:redirect_html].is_a?(String) body = @options[:redirect_html] if @options[:redirect_html].respond_to?('each') [@options[:redirect_code] || 301, { 'Content-Type' => 'text/html', 'Location' => location }, body] end
replace_host(uri, req, host)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 164 def replace_host(uri, req, host) return uri unless host_mismatch?(req) host_parts = URI.split(host) new_host = host_parts[2] || host_parts[5] uri_parts = URI.split(uri) uri_parts[2] = new_host URI::HTTPS.new(*uri_parts).to_s end
replace_scheme(uri, req, scheme)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 154 def replace_scheme(uri, req, scheme) return uri if not scheme_mismatch?(req, scheme) port = adjust_port_to(scheme) uri_parts = URI.split(uri) uri_parts[3] = port unless port.nil? uri_parts[0] = scheme URI::HTTP.new(*uri_parts).to_s end
scheme_mismatch?(req, scheme)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 78 def scheme_mismatch?(req, scheme) scheme && scheme != current_scheme(req) end
set_hsts_headers!(headers)
click to toggle source
see en.wikipedia.org/wiki/Strict_Transport_Security
# File lib/rack/ssl-enforcer.rb, line 197 def set_hsts_headers!(headers) opts = { :expires => 31536000, :subdomains => true, :preload => false } opts.merge!(@options[:hsts]) if @options[:hsts].is_a? Hash value = "max-age=#{opts[:expires]}" value += "; includeSubDomains" if opts[:subdomains] value += "; preload" if opts[:preload] headers.merge!({ 'Strict-Transport-Security' => value }) end
ssl_request?(req)
click to toggle source
# File lib/rack/ssl-enforcer.rb, line 108 def ssl_request?(req) current_scheme(req) == 'https' end