forked from soveran/cuba
-
Notifications
You must be signed in to change notification settings - Fork 140
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add redirect_http_to_https plugin, helping to ensure future requests …
…from the browser are submitted via HTTPS
- Loading branch information
1 parent
f9e5a3f
commit b5da06a
Showing
4 changed files
with
199 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# frozen-string-literal: true | ||
|
||
# | ||
class Roda | ||
module RodaPlugins | ||
# The redirect_http_to_https plugin exposes a +redirect_http_to_https+ | ||
# request method that redirects HTTP requests to HTTPS, helping to ensure | ||
# that future requests by the same browser will be submitted securely. | ||
# | ||
# You should use this plugin if you have an application that can receive | ||
# requests using both HTTP and HTTPS, and you want to make sure that all | ||
# or a subset of routes are only handled for HTTPS requests. | ||
# | ||
# The reason this exposes a request method is so that you can choose where | ||
# in your routing tree to do the redirection: | ||
# | ||
# route do |r| | ||
# # routes available via both HTTP and HTTPS | ||
# r.redirect_http_to_https | ||
# # routes available only via HTTPS | ||
# end | ||
# | ||
# If you want to redirect to HTTPS for all routes in the routing tree, you | ||
# can have this as the very first method call in the routing tree. Note that | ||
# in Roda it is possible to handle routing before the normal routing tree | ||
# using before hooks. The static_routing and heartbeat plugins use this | ||
# feature. If you would like to handle routes before the normal routing tree, | ||
# you can setup a before hook: | ||
# | ||
# plugin :hooks | ||
# | ||
# before do | ||
# request.redirect_http_to_https | ||
# end | ||
module RedirectHttpToHttps | ||
status_map = Hash.new(307) | ||
status_map['GET'] = status_map['HEAD'] = 301 | ||
status_map.freeze | ||
DEFAULTS = {:status_map => status_map}.freeze | ||
private_constant :DEFAULTS | ||
|
||
# Configures redirection from HTTP to HTTPS. Available options: | ||
# | ||
# :body :: The body used in the redirect. If not set, uses an empty body. | ||
# :headers :: Any additional headers used in the redirect response. By default, | ||
# no additional headers are set, the only header used is the Location header. | ||
# :host :: The host to redirect to. If not set, redirects to the same host as the HTTP | ||
# requested to. It is highly recommended that you set this if requests with | ||
# arbitrary Host headers can be submitted to the application. | ||
# :port :: The port to use in the redirect. By default, will not set an explicit port, | ||
# so that it will implicitly use the HTTPS default port of 443. | ||
# :status_map :: A hash mapping request methods to response status codes. By default, | ||
# uses a hash that redirects GET and HEAD requests with a 301 status, | ||
# and other request methods with a 307 status. | ||
def self.configure(app, opts=OPTS) | ||
previous = app.opts[:redirect_http_to_https] || DEFAULTS | ||
opts = app.opts[:redirect_http_to_https] = previous.merge(opts) | ||
opts[:port_string] = opts[:port] ? ":#{opts[:port]}".freeze : "".freeze | ||
opts[:prefix] = opts[:host] ? "https://#{opts[:host]}#{opts[:port_string]}".freeze : nil | ||
opts.freeze | ||
end | ||
|
||
module RequestMethods | ||
# Redirect HTTP requests to HTTPS. While this doesn't secure the | ||
# current request, it makes it more likely that the browser will submit | ||
# future requests securely via HTTPS. | ||
def redirect_http_to_https | ||
return if ssl? | ||
|
||
opts = roda_class.opts[:redirect_http_to_https] | ||
|
||
res = response | ||
|
||
if body = opts[:body] | ||
res.write(body) | ||
end | ||
|
||
if headers = opts[:headers] | ||
res.headers.merge!(headers) | ||
end | ||
|
||
path = if prefix = opts[:prefix] | ||
prefix + fullpath | ||
else | ||
"https://#{host}#{opts[:port_string]}#{fullpath}" | ||
end | ||
|
||
unless status = opts[:status_map][@env['REQUEST_METHOD']] | ||
raise RodaError, "redirect_http_to_https :status_map provided does not support #{@env['REQUEST_METHOD']}" | ||
end | ||
|
||
redirect(path, status) | ||
end | ||
end | ||
end | ||
|
||
register_plugin(:redirect_http_to_https, RedirectHttpToHttps) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
require_relative "../spec_helper" | ||
|
||
describe "redirect_http_to_https plugin" do | ||
before do | ||
app(:redirect_http_to_https) do |r| | ||
r.get 'a' do | ||
"a-#{r.ssl?}" | ||
end | ||
|
||
r.redirect_http_to_https | ||
|
||
"x-#{r.ssl?}" | ||
end | ||
end | ||
|
||
it "should not redirect before call to r.redirect_http_to_https" do | ||
body('/a').must_equal 'a-false' | ||
body('/a', 'HTTPS'=>'on').must_equal 'a-true' | ||
end | ||
|
||
it "r.redirect_http_to_https redirects HTTP requests to HTTP" do | ||
s, h, b = req('/b', 'HTTP_HOST'=>'foo.com') | ||
s.must_equal 301 | ||
h[RodaResponseHeaders::LOCATION].must_equal 'https://foo.com/b' | ||
b.must_be_empty | ||
body('/b', 'HTTPS'=>'on').must_equal 'x-true' | ||
end | ||
|
||
it "uses 301 for HEAD redirects by default" do | ||
status('/b', 'HTTP_HOST'=>'foo.com', 'REQUEST_METHOD'=>'HEAD').must_equal 301 | ||
end | ||
|
||
it "uses 307 for POST redirects by default" do | ||
status('/b', 'HTTP_HOST'=>'foo.com', 'REQUEST_METHOD'=>'POST').must_equal 307 | ||
end | ||
|
||
it "includes query string when redirecting" do | ||
header(RodaResponseHeaders::LOCATION, '/b', 'HTTP_HOST'=>'foo.com', 'QUERY_STRING'=>'foo=bar').must_equal 'https://foo.com/b?foo=bar' | ||
end | ||
|
||
it "supports :body option" do | ||
@app.plugin :redirect_http_to_https, :body=>'RTHS' | ||
s, h, b = req('/b', 'HTTP_HOST'=>'foo.com') | ||
s.must_equal 301 | ||
h[RodaResponseHeaders::LOCATION].must_equal 'https://foo.com/b' | ||
b.must_equal ['RTHS'] | ||
end | ||
|
||
it "supports :headers option" do | ||
@app.plugin :redirect_http_to_https, :headers=>{'foo'=>'bar'} | ||
s, h, b = req('/b', 'HTTP_HOST'=>'foo.com') | ||
s.must_equal 301 | ||
h[RodaResponseHeaders::LOCATION].must_equal 'https://foo.com/b' | ||
h['foo'].must_equal 'bar' | ||
b.must_be_empty | ||
end | ||
|
||
it "supports :host option" do | ||
@app.plugin :redirect_http_to_https, :host=>'bar.foo.com' | ||
s, h, b = req('/b', 'HTTP_HOST'=>'foo.com') | ||
s.must_equal 301 | ||
h[RodaResponseHeaders::LOCATION].must_equal 'https://bar.foo.com/b' | ||
b.must_be_empty | ||
end | ||
|
||
it "supports :port option" do | ||
@app.plugin :redirect_http_to_https, :port=>444 | ||
s, h, b = req('/b', 'HTTP_HOST'=>'foo.com') | ||
s.must_equal 301 | ||
h[RodaResponseHeaders::LOCATION].must_equal 'https://foo.com:444/b' | ||
b.must_be_empty | ||
end | ||
|
||
it "supports :host and :port options together" do | ||
@app.plugin :redirect_http_to_https, :host=>'bar.foo.com', :port=>444 | ||
s, h, b = req('/b', 'HTTP_HOST'=>'foo.com') | ||
s.must_equal 301 | ||
h[RodaResponseHeaders::LOCATION].must_equal 'https://bar.foo.com:444/b' | ||
b.must_be_empty | ||
end | ||
|
||
it "supports :status_map option" do | ||
map = Hash.new(302) | ||
map['GET'] = 301 | ||
@app.plugin :redirect_http_to_https, :status_map=>map | ||
status('/b', 'HTTP_HOST'=>'foo.com', 'REQUEST_METHOD'=>'GET').must_equal 301 | ||
status('/b', 'HTTP_HOST'=>'foo.com', 'REQUEST_METHOD'=>'HEAD').must_equal 302 | ||
end | ||
|
||
it "raise for :status_map that does not handle request mthod" do | ||
@app.plugin :redirect_http_to_https, :status_map=>{'GET'=>302} | ||
status('/b', 'HTTP_HOST'=>'foo.com', 'REQUEST_METHOD'=>'GET').must_equal 302 | ||
proc{status('/b', 'HTTP_HOST'=>'foo.com', 'REQUEST_METHOD'=>'HEAD')}.must_raise Roda::RodaError | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters