rack-ssl-facebook 1.3.4
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.
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +13 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/lib/rack/ssl_facebook.rb +100 -0
- data/rack-ssl-facebook.gemspec +47 -0
- data/test/test_ssl.rb +169 -0
- metadata +65 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010 Joshua Peek
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rack-ssl-facebook"
|
8
|
+
gem.summary = "Force SSL/TLS in your app, preserving facebook's signed_request."
|
9
|
+
gem.description = "Rack middleware to force SSL/TLS, preserving facebook's signed_request."
|
10
|
+
gem.email = "mail@recursive-design.com"
|
11
|
+
gem.homepage = "https://github.com/recurser/rack-ssl-facebook"
|
12
|
+
gem.authors = ["Dave Perrett"]
|
13
|
+
gem.rubyforge_project = 'nowarning'
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
task :default => :test
|
22
|
+
Rake::TestTask.new do |t|
|
23
|
+
t.warning = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def version
|
27
|
+
File.exist?('VERSION') ? File.read('VERSION') : ""
|
28
|
+
end
|
29
|
+
|
30
|
+
namespace :gem do
|
31
|
+
|
32
|
+
desc 'Clean build products'
|
33
|
+
task :clean do
|
34
|
+
rm_f 'pkg'
|
35
|
+
end
|
36
|
+
|
37
|
+
desc 'Build the gem'
|
38
|
+
task :build => :clean do
|
39
|
+
system 'rake gemspec'
|
40
|
+
system 'rake build'
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Push the gem to rubygems'
|
44
|
+
task :publish => [:clean, :build] do
|
45
|
+
system "gem push pkg/rack-ssl-facebook-#{version}.gem"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.3.4
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/request'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
class SSLFacebook
|
6
|
+
YEAR = 31536000
|
7
|
+
|
8
|
+
def self.default_hsts_options
|
9
|
+
{ :expires => YEAR, :subdomains => false }
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(app, options = {})
|
13
|
+
@app = app
|
14
|
+
|
15
|
+
@hsts = options[:hsts]
|
16
|
+
@hsts = {} if @hsts.nil? || @hsts == true
|
17
|
+
@hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
|
18
|
+
|
19
|
+
@exclude = options[:exclude]
|
20
|
+
@host = options[:host]
|
21
|
+
@port = options[:port]
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
if @exclude && @exclude.call(env)
|
26
|
+
@app.call(env)
|
27
|
+
elsif scheme(env) == 'https'
|
28
|
+
status, headers, body = @app.call(env)
|
29
|
+
headers = hsts_headers.merge(headers)
|
30
|
+
flag_cookies_as_secure!(headers)
|
31
|
+
[status, headers, body]
|
32
|
+
else
|
33
|
+
redirect_to_https(env)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
# Fixed in rack >= 1.3
|
39
|
+
def scheme(env)
|
40
|
+
if env['HTTPS'] == 'on'
|
41
|
+
'https'
|
42
|
+
elsif env['HTTP_X_FORWARDED_PROTO']
|
43
|
+
env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
|
44
|
+
else
|
45
|
+
env['rack.url_scheme']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def redirect_to_https(env)
|
50
|
+
req = Request.new(env)
|
51
|
+
url = URI(req.url)
|
52
|
+
url.scheme = "https"
|
53
|
+
url.host = @host if @host
|
54
|
+
url.port = @port if @port
|
55
|
+
|
56
|
+
# If a signed_request is present, append it to the query string.
|
57
|
+
if req.POST['signed_request']
|
58
|
+
if url.query.nil? or url.query.empty?
|
59
|
+
url.query = "signed_request=#{req.POST['signed_request']}"
|
60
|
+
else
|
61
|
+
url.query += "&signed_request=#{req.POST['signed_request']}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
headers = hsts_headers.merge('Content-Type' => 'text/html',
|
66
|
+
'Location' => url.to_s)
|
67
|
+
|
68
|
+
[301, headers, []]
|
69
|
+
end
|
70
|
+
|
71
|
+
# http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
|
72
|
+
def hsts_headers
|
73
|
+
if @hsts
|
74
|
+
value = "max-age=#{@hsts[:expires]}"
|
75
|
+
value += "; includeSubDomains" if @hsts[:subdomains]
|
76
|
+
{ 'Strict-Transport-Security' => value }
|
77
|
+
else
|
78
|
+
{}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def flag_cookies_as_secure!(headers)
|
83
|
+
if cookies = headers['Set-Cookie']
|
84
|
+
# Rack 1.1's set_cookie_header! will sometimes wrap
|
85
|
+
# Set-Cookie in an array
|
86
|
+
unless cookies.respond_to?(:to_ary)
|
87
|
+
cookies = cookies.split("\n")
|
88
|
+
end
|
89
|
+
|
90
|
+
headers['Set-Cookie'] = cookies.map { |cookie|
|
91
|
+
if cookie !~ /; secure(;|$)/
|
92
|
+
"#{cookie}; secure"
|
93
|
+
else
|
94
|
+
cookie
|
95
|
+
end
|
96
|
+
}.join("\n")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "rack-ssl-facebook"
|
8
|
+
s.version = "1.3.4"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Dave Perrett"]
|
12
|
+
s.date = "2011-12-05"
|
13
|
+
s.description = "Rack middleware to force SSL/TLS, preserving facebook's signed_request."
|
14
|
+
s.email = "mail@recursive-design.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"Gemfile",
|
21
|
+
"LICENSE",
|
22
|
+
"README.md",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"lib/rack/ssl_facebook.rb",
|
26
|
+
"rack-ssl-facebook.gemspec",
|
27
|
+
"test/test_ssl.rb"
|
28
|
+
]
|
29
|
+
s.homepage = "https://github.com/recurser/rack-ssl-facebook"
|
30
|
+
s.require_paths = ["lib"]
|
31
|
+
s.rubyforge_project = "nowarning"
|
32
|
+
s.rubygems_version = "1.8.11"
|
33
|
+
s.summary = "Force SSL/TLS in your app, preserving facebook's signed_request."
|
34
|
+
|
35
|
+
if s.respond_to? :specification_version then
|
36
|
+
s.specification_version = 3
|
37
|
+
|
38
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
39
|
+
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
40
|
+
else
|
41
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
42
|
+
end
|
43
|
+
else
|
44
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
data/test/test_ssl.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'rack/ssl_facebook'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rack/test'
|
5
|
+
|
6
|
+
class TestSSL < Test::Unit::TestCase
|
7
|
+
include Rack::Test::Methods
|
8
|
+
|
9
|
+
def default_app
|
10
|
+
lambda { |env|
|
11
|
+
headers = {'Content-Type' => "text/html"}
|
12
|
+
headers['Set-Cookie'] = "id=1; path=/\ntoken=abc; path=/; secure; HttpOnly"
|
13
|
+
[200, headers, ["OK"]]
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def app
|
18
|
+
@app ||= Rack::SSLFacebook.new(default_app)
|
19
|
+
end
|
20
|
+
attr_writer :app
|
21
|
+
|
22
|
+
def test_allows_https_url
|
23
|
+
get "https://example.org/path?key=value"
|
24
|
+
assert last_response.ok?
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_allows_https_proxy_header_url
|
28
|
+
get "http://example.org/", {}, 'HTTP_X_FORWARDED_PROTO' => "https"
|
29
|
+
assert last_response.ok?
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_redirects_http_to_https
|
33
|
+
get "http://example.org/path?key=value"
|
34
|
+
assert last_response.redirect?
|
35
|
+
assert_equal "https://example.org/path?key=value",
|
36
|
+
last_response.headers['Location']
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_exclude_from_redirect
|
40
|
+
self.app = Rack::SSLFacebook.new(default_app, :exclude => lambda { |env| true })
|
41
|
+
get "http://example.org/"
|
42
|
+
assert last_response.ok?
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_hsts_header_by_default
|
46
|
+
get "https://example.org/"
|
47
|
+
assert_equal "max-age=31536000",
|
48
|
+
last_response.headers['Strict-Transport-Security']
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_hsts_header
|
52
|
+
self.app = Rack::SSLFacebook.new(default_app, :hsts => true)
|
53
|
+
get "https://example.org/"
|
54
|
+
assert_equal "max-age=31536000",
|
55
|
+
last_response.headers['Strict-Transport-Security']
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_disable_hsts_header
|
59
|
+
self.app = Rack::SSLFacebook.new(default_app, :hsts => false)
|
60
|
+
get "https://example.org/"
|
61
|
+
assert !last_response.headers['Strict-Transport-Security']
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_hsts_expires
|
65
|
+
self.app = Rack::SSLFacebook.new(default_app, :hsts => { :expires => 500 })
|
66
|
+
get "https://example.org/"
|
67
|
+
assert_equal "max-age=500",
|
68
|
+
last_response.headers['Strict-Transport-Security']
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_hsts_include_subdomains
|
72
|
+
self.app = Rack::SSLFacebook.new(default_app, :hsts => { :subdomains => true })
|
73
|
+
get "https://example.org/"
|
74
|
+
assert_equal "max-age=31536000; includeSubDomains",
|
75
|
+
last_response.headers['Strict-Transport-Security']
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_flag_cookies_as_secure
|
79
|
+
get "https://example.org/"
|
80
|
+
assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly" ],
|
81
|
+
last_response.headers['Set-Cookie'].split("\n")
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_flag_cookies_as_secure_at_end_of_line
|
85
|
+
self.app = Rack::SSLFacebook.new(lambda { |env|
|
86
|
+
headers = {
|
87
|
+
'Content-Type' => "text/html",
|
88
|
+
'Set-Cookie' => "problem=def; path=/; HttpOnly; secure"
|
89
|
+
}
|
90
|
+
[200, headers, ["OK"]]
|
91
|
+
})
|
92
|
+
|
93
|
+
get "https://example.org/"
|
94
|
+
assert_equal ["problem=def; path=/; HttpOnly; secure"],
|
95
|
+
last_response.headers['Set-Cookie'].split("\n")
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_legacy_array_headers
|
99
|
+
self.app = Rack::SSLFacebook.new(lambda { |env|
|
100
|
+
headers = {
|
101
|
+
'Content-Type' => "text/html",
|
102
|
+
'Set-Cookie' => ["id=1; path=/", "token=abc; path=/; HttpOnly"]
|
103
|
+
}
|
104
|
+
[200, headers, ["OK"]]
|
105
|
+
})
|
106
|
+
|
107
|
+
get "https://example.org/"
|
108
|
+
assert_equal ["id=1; path=/; secure", "token=abc; path=/; HttpOnly; secure"],
|
109
|
+
last_response.headers['Set-Cookie'].split("\n")
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_no_cookies
|
113
|
+
self.app = Rack::SSLFacebook.new(lambda { |env|
|
114
|
+
[200, {'Content-Type' => "text/html"}, ["OK"]]
|
115
|
+
})
|
116
|
+
get "https://example.org/"
|
117
|
+
assert !last_response.headers['Set-Cookie']
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_redirect_to_host
|
121
|
+
self.app = Rack::SSLFacebook.new(default_app, :host => "ssl.example.org")
|
122
|
+
get "http://example.org/path?key=value"
|
123
|
+
assert_equal "https://ssl.example.org/path?key=value",
|
124
|
+
last_response.headers['Location']
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_redirect_to_port
|
128
|
+
self.app = Rack::SSLFacebook.new(default_app, :port => 8443)
|
129
|
+
get "http://example.org/path?key=value"
|
130
|
+
assert_equal "https://example.org:8443/path?key=value",
|
131
|
+
last_response.headers['Location']
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_redirect_to_host_and_port
|
135
|
+
self.app = Rack::SSLFacebook.new(default_app, :host => "ssl.example.org", :port => 8443)
|
136
|
+
get "http://example.org/path?key=value"
|
137
|
+
assert_equal "https://ssl.example.org:8443/path?key=value",
|
138
|
+
last_response.headers['Location']
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_redirect_to_secure_host_when_on_subdomain
|
142
|
+
self.app = Rack::SSLFacebook.new(default_app, :host => "ssl.example.org")
|
143
|
+
get "http://ssl.example.org/path?key=value"
|
144
|
+
assert_equal "https://ssl.example.org/path?key=value",
|
145
|
+
last_response.headers['Location']
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_redirect_to_secure_subdomain_when_on_deep_subdomain
|
149
|
+
self.app = Rack::SSLFacebook.new(default_app, :host => "example.co.uk")
|
150
|
+
get "http://double.rainbow.what.does.it.mean.example.co.uk/path?key=value"
|
151
|
+
assert_equal "https://example.co.uk/path?key=value",
|
152
|
+
last_response.headers['Location']
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_redirect_with_signed_request
|
156
|
+
self.app = Rack::SSLFacebook.new(default_app, :host => "ssl.example.org")
|
157
|
+
post "http://example.org/path", {:signed_request => '1234'}
|
158
|
+
assert_equal "https://ssl.example.org/path?signed_request=1234",
|
159
|
+
last_response.headers['Location']
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_redirect_with_signed_request_multi_params
|
163
|
+
self.app = Rack::SSLFacebook.new(default_app, :host => "ssl.example.org")
|
164
|
+
post "http://example.org/path?key=value", {:signed_request => '1234'}
|
165
|
+
assert_equal "https://ssl.example.org/path?key=value&signed_request=1234",
|
166
|
+
last_response.headers['Location']
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-ssl-facebook
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dave Perrett
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-05 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: &70095368008140 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70095368008140
|
25
|
+
description: Rack middleware to force SSL/TLS, preserving facebook's signed_request.
|
26
|
+
email: mail@recursive-design.com
|
27
|
+
executables: []
|
28
|
+
extensions: []
|
29
|
+
extra_rdoc_files:
|
30
|
+
- LICENSE
|
31
|
+
- README.md
|
32
|
+
files:
|
33
|
+
- Gemfile
|
34
|
+
- LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- VERSION
|
38
|
+
- lib/rack/ssl_facebook.rb
|
39
|
+
- rack-ssl-facebook.gemspec
|
40
|
+
- test/test_ssl.rb
|
41
|
+
homepage: https://github.com/recurser/rack-ssl-facebook
|
42
|
+
licenses: []
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project: nowarning
|
61
|
+
rubygems_version: 1.8.11
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Force SSL/TLS in your app, preserving facebook's signed_request.
|
65
|
+
test_files: []
|