rack-screen-door 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.travis.yml +6 -0
- data/.yardopts +1 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.md +29 -0
- data/Rakefile +10 -0
- data/default_template.html.erb +23 -0
- data/lib/rack-screen-door.rb +1 -0
- data/lib/rack/screen_door.rb +89 -0
- data/lib/rack/screen_door/version.rb +5 -0
- data/rack-screen-door.gemspec +27 -0
- data/spec/screen_door_spec.rb +124 -0
- metadata +164 -0
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create ree-1.8.7-2011.03@rack-screen-door
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/Gemfile
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 John Nishinaga and Pat Deegan, PhD & Associates, LLC.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# rack-screen-door
|
2
|
+
|
3
|
+
Rack middleware for simple question and answer authorization.
|
4
|
+
|
5
|
+
This can be useful to add a thin veneer around website to help it stay
|
6
|
+
semi-private, e.g., during the development stages.
|
7
|
+
|
8
|
+
**WARNING: This is really poor security.** Think of it as a...well...screen door.
|
9
|
+
|
10
|
+
## Install
|
11
|
+
|
12
|
+
gem install rack-screen-door
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
require 'rack/screen_door'
|
17
|
+
|
18
|
+
use Rack::ScreenDoor 'secret answer'
|
19
|
+
|
20
|
+
Additional options can be supplied:
|
21
|
+
|
22
|
+
use Rack::ScreenDoor 'secret answer',
|
23
|
+
:salt => 'some server-side secret',
|
24
|
+
:template_path => 'path/to/my_screen_door.html.erb',
|
25
|
+
:cookie_key => 'my_cookie_name',
|
26
|
+
:cookie_hash => { :domain => '.example.org', :secure => true },
|
27
|
+
:expires => 1.year
|
28
|
+
|
29
|
+
See [default_template.html.erb](https://github.com/patdeegan/rack-screen-door/blob/master/default_template.html.erb) as an example template file.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Enter the secret answer</title>
|
5
|
+
<style>
|
6
|
+
body { font-family: sans-serif; text-align: center; margin: 40px; color: gray; }
|
7
|
+
p, form { max-width: 800px; margin: 1em auto; }
|
8
|
+
.question { font-size: 50px; }
|
9
|
+
.error { color: red; font-size: 50px; }
|
10
|
+
input { font-size: 60px; width: 70%; }
|
11
|
+
</style>
|
12
|
+
</head>
|
13
|
+
<body>
|
14
|
+
<p class="question">Enter the secret answer:</p>
|
15
|
+
<% if error %>
|
16
|
+
<p class="error">Please try again.</p>
|
17
|
+
<% end %>
|
18
|
+
<form method="post">
|
19
|
+
<input type="text" name="answer" id="answer" value="" autofocus>
|
20
|
+
<input type="hidden" name="redirect" value="<%= h redirect_url %>">
|
21
|
+
</form>
|
22
|
+
</body>
|
23
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rack/screen_door'
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'erb'
|
3
|
+
require 'digest/sha2'
|
4
|
+
require 'rack/screen_door/version'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
|
8
|
+
# Rack middleware class for simple question and answer authorization.
|
9
|
+
class ScreenDoor
|
10
|
+
|
11
|
+
DEFAULT_COOKIE_KEY = '_rack_screen_door'
|
12
|
+
DEFAULT_COOKIE_HASH = { :httponly => true }
|
13
|
+
DEFAULT_EXPIRES = 60 * 60 * 24 * 30 # 1 month
|
14
|
+
DEFAULT_TEMPLATE_PATH = ::File.expand_path('../../../default_template.html.erb', __FILE__)
|
15
|
+
DEFAULT_SALT = 'SaltySalt'
|
16
|
+
|
17
|
+
attr_reader :app, :cookie_key, :cookie_hash, :expires
|
18
|
+
attr_reader :template_path, :salt, :answer
|
19
|
+
attr_reader :redirect_url, :error
|
20
|
+
|
21
|
+
# Creates the middleware.
|
22
|
+
#
|
23
|
+
# @param [Application] app application
|
24
|
+
# @param [String] answer secret answer
|
25
|
+
# @param [Hash] options options
|
26
|
+
# @option options [String] :salt (DEFAULT_SALT) a server-side secret
|
27
|
+
# @option options [String] :template_path (DEFAULT_TEMPLATE_PATH) the path to an HTML or ERB file to render
|
28
|
+
# @option options [String] :cookie_key (DEFAULT_COOKIE_KEY) the cookie key (name)
|
29
|
+
# @option options [Hash] :cookie_hash (DEFAULT_COOKIE_HASH) cookie options that will be passed to Rack::Response#set_cookie
|
30
|
+
# @option options [Integer] :expires (DEFAULT_EXPIRES) how long a cookie will persist
|
31
|
+
def initialize(app, answer, options = {})
|
32
|
+
@app = app
|
33
|
+
@answer = answer
|
34
|
+
@salt = options[:salt] || DEFAULT_SALT
|
35
|
+
@template_path = options[:template_path] || DEFAULT_TEMPLATE_PATH
|
36
|
+
@cookie_key = options[:cookie_key] || DEFAULT_COOKIE_KEY
|
37
|
+
@cookie_hash = DEFAULT_COOKIE_HASH.merge(options[:cookie_hash] || {})
|
38
|
+
@expires = options[:expires] || DEFAULT_EXPIRES
|
39
|
+
end
|
40
|
+
|
41
|
+
# Rack middleware chain.
|
42
|
+
def call(env)
|
43
|
+
dup._call(env) # make threadsafe
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
include ERB::Util
|
49
|
+
|
50
|
+
def _call(env)
|
51
|
+
request = Rack::Request.new(env)
|
52
|
+
return app.call(env) if verified_cookie?(request)
|
53
|
+
response = Rack::Response.new
|
54
|
+
if request.post?
|
55
|
+
@redirect_url = request.params['redirect'] || '/'
|
56
|
+
if request.params['answer'] == answer
|
57
|
+
set_verified_cookie(response)
|
58
|
+
response.redirect @redirect_url
|
59
|
+
else
|
60
|
+
@error = true
|
61
|
+
response.write response_html
|
62
|
+
end
|
63
|
+
else
|
64
|
+
@redirect_url = request.url
|
65
|
+
@error = false
|
66
|
+
response.write response_html
|
67
|
+
end
|
68
|
+
response.finish
|
69
|
+
end
|
70
|
+
|
71
|
+
def hashed_answer
|
72
|
+
Digest::SHA256.hexdigest(salt + answer)
|
73
|
+
end
|
74
|
+
|
75
|
+
def verified_cookie?(request)
|
76
|
+
request.cookies[cookie_key] == hashed_answer
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_verified_cookie(response)
|
80
|
+
response.set_cookie(cookie_key, cookie_hash.merge(
|
81
|
+
:value => hashed_answer, :expires => Time.now + expires))
|
82
|
+
end
|
83
|
+
|
84
|
+
def response_html
|
85
|
+
ERB.new(::File.read(template_path)).result(binding)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rack/screen_door/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rack-screen-door"
|
7
|
+
s.version = Rack::ScreenDoor::VERSION
|
8
|
+
s.authors = ["John Nishinaga"]
|
9
|
+
s.email = ["jingoro@casa-z.org"]
|
10
|
+
s.homepage = "https://github.com/jingoro/rack-screen-door"
|
11
|
+
s.summary = "Rack middleware for simple question and answer authorization."
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "rack-screen-door"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_runtime_dependency 'rack'
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'rack-test'
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
s.add_development_dependency 'yard'
|
26
|
+
s.add_development_dependency 'redcarpet'
|
27
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'rack/screen_door'
|
4
|
+
require 'rspec'
|
5
|
+
require 'rack/test'
|
6
|
+
|
7
|
+
describe Rack::ScreenDoor do
|
8
|
+
|
9
|
+
include Rack::Test::Methods
|
10
|
+
|
11
|
+
let(:default_salt) { 'SaltySalt' }
|
12
|
+
let(:default_cookie_key) { '_rack_screen_door' }
|
13
|
+
let(:default_question) { 'Enter the secret answer' }
|
14
|
+
let(:default_error) { 'Please try again' }
|
15
|
+
let(:penguin_answer) { 'penguin' }
|
16
|
+
let(:penguin_hash) { 'efec1f5977e7ca9c5273c667502583d86de0eb723de57997faccca0fb5b3ed74' }
|
17
|
+
let(:default_app_body) { 'Hello World!' }
|
18
|
+
|
19
|
+
let(:default_app) { lambda { |e| [200, {'Content-Type' => 'text/html'}, [default_app_body]] } }
|
20
|
+
let(:app) { Rack::ScreenDoor.new(default_app, penguin_answer) }
|
21
|
+
|
22
|
+
context 'instance' do
|
23
|
+
|
24
|
+
subject { app }
|
25
|
+
|
26
|
+
its(:salt) { should == default_salt }
|
27
|
+
its(:cookie_key) { should == default_cookie_key }
|
28
|
+
its(:cookie_hash) { should == { :httponly => true } }
|
29
|
+
its(:expires) { should == 60 * 60 * 24 * 30 }
|
30
|
+
its(:answer) { should == penguin_answer }
|
31
|
+
its(:hashed_answer) { should == penguin_hash }
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'request' do
|
36
|
+
|
37
|
+
subject { last_response }
|
38
|
+
|
39
|
+
before { clear_cookies }
|
40
|
+
|
41
|
+
def cookie_value
|
42
|
+
rack_mock_session.cookie_jar[default_cookie_key]
|
43
|
+
end
|
44
|
+
|
45
|
+
def headers
|
46
|
+
last_response.headers
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'GET /blah' do
|
50
|
+
|
51
|
+
before { get '/blah' }
|
52
|
+
|
53
|
+
it { should be_ok }
|
54
|
+
its(:body) { should include(default_question) }
|
55
|
+
its(:body) { should_not include(penguin_answer) }
|
56
|
+
its(:body) { should_not include(default_error) }
|
57
|
+
its(:body) { should include('http://example.org/blah') }
|
58
|
+
specify { cookie_value.should be_nil }
|
59
|
+
specify { headers['Content-Type'].should == 'text/html' }
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'POST / with incorrect answer' do
|
64
|
+
|
65
|
+
before { post '/', :answer => 'squirrel', :redirect => 'http://example.org/fred' }
|
66
|
+
|
67
|
+
it { should be_ok }
|
68
|
+
its(:body) { should include(default_question) }
|
69
|
+
its(:body) { should_not include(penguin_answer) }
|
70
|
+
its(:body) { should include(default_error) }
|
71
|
+
its(:body) { should include('http://example.org/fred') }
|
72
|
+
specify { cookie_value.should be_nil }
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'POST / with correct answer' do
|
77
|
+
|
78
|
+
before do
|
79
|
+
Time.stub(:now) { Time.at(946684800).utc } # 2000-1-1
|
80
|
+
post '/', :answer => 'penguin', :redirect => 'http://example.org/stuff'
|
81
|
+
end
|
82
|
+
|
83
|
+
it { should be_redirect }
|
84
|
+
specify { headers['Location'].should == 'http://example.org/stuff' }
|
85
|
+
specify { headers['Set-Cookie'].should include("#{default_cookie_key}=#{penguin_hash}") }
|
86
|
+
specify { headers['Set-Cookie'].should include('expires=Mon, 31-Jan-2000 00:00:00 GMT') }
|
87
|
+
|
88
|
+
context 'after following the redirect' do
|
89
|
+
before { follow_redirect! }
|
90
|
+
it { should be_ok }
|
91
|
+
its(:body) { should == default_app_body }
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'GET / w/ good cookie' do
|
97
|
+
|
98
|
+
before do
|
99
|
+
set_cookie "#{default_cookie_key}=#{penguin_hash}"
|
100
|
+
get '/'
|
101
|
+
end
|
102
|
+
|
103
|
+
it { should be_ok }
|
104
|
+
its(:body) { should == default_app_body }
|
105
|
+
specify { cookie_value.should == penguin_hash }
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'GET / w/ bad cookie' do
|
110
|
+
|
111
|
+
before do
|
112
|
+
set_cookie "#{default_cookie_key}=blubber"
|
113
|
+
get '/'
|
114
|
+
end
|
115
|
+
|
116
|
+
it { should be_ok }
|
117
|
+
its(:body) { should include(default_question) }
|
118
|
+
specify { cookie_value.should == 'blubber' }
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
metadata
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-screen-door
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- John Nishinaga
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-01-11 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
hash: 3
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
requirement: *id001
|
31
|
+
prerelease: false
|
32
|
+
name: rack
|
33
|
+
type: :runtime
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
hash: 3
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
requirement: *id002
|
45
|
+
prerelease: false
|
46
|
+
name: rake
|
47
|
+
type: :development
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
requirement: *id003
|
59
|
+
prerelease: false
|
60
|
+
name: rack-test
|
61
|
+
type: :development
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirement: *id004
|
73
|
+
prerelease: false
|
74
|
+
name: rspec
|
75
|
+
type: :development
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
hash: 3
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
requirement: *id005
|
87
|
+
prerelease: false
|
88
|
+
name: yard
|
89
|
+
type: :development
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
hash: 3
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
requirement: *id006
|
101
|
+
prerelease: false
|
102
|
+
name: redcarpet
|
103
|
+
type: :development
|
104
|
+
description: Rack middleware for simple question and answer authorization.
|
105
|
+
email:
|
106
|
+
- jingoro@casa-z.org
|
107
|
+
executables: []
|
108
|
+
|
109
|
+
extensions: []
|
110
|
+
|
111
|
+
extra_rdoc_files: []
|
112
|
+
|
113
|
+
files:
|
114
|
+
- .gitignore
|
115
|
+
- .rspec
|
116
|
+
- .rvmrc
|
117
|
+
- .travis.yml
|
118
|
+
- .yardopts
|
119
|
+
- Gemfile
|
120
|
+
- MIT-LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- default_template.html.erb
|
124
|
+
- lib/rack-screen-door.rb
|
125
|
+
- lib/rack/screen_door.rb
|
126
|
+
- lib/rack/screen_door/version.rb
|
127
|
+
- rack-screen-door.gemspec
|
128
|
+
- spec/screen_door_spec.rb
|
129
|
+
homepage: https://github.com/jingoro/rack-screen-door
|
130
|
+
licenses: []
|
131
|
+
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
hash: 3
|
143
|
+
segments:
|
144
|
+
- 0
|
145
|
+
version: "0"
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
hash: 3
|
152
|
+
segments:
|
153
|
+
- 0
|
154
|
+
version: "0"
|
155
|
+
requirements: []
|
156
|
+
|
157
|
+
rubyforge_project: rack-screen-door
|
158
|
+
rubygems_version: 1.8.10
|
159
|
+
signing_key:
|
160
|
+
specification_version: 3
|
161
|
+
summary: Rack middleware for simple question and answer authorization.
|
162
|
+
test_files:
|
163
|
+
- spec/screen_door_spec.rb
|
164
|
+
has_rdoc:
|