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 ADDED
@@ -0,0 +1,5 @@
1
+ .bundle
2
+ .yardoc
3
+ Gemfile.lock
4
+ doc
5
+ pkg
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format documentation
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create ree-1.8.7-2011.03@rack-screen-door
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - ree
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
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,10 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'yard'
5
+ YARD::Rake::YardocTask.new
6
+
7
+ require 'rspec/core/rake_task'
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ task :default => :spec
@@ -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,5 @@
1
+ module Rack
2
+ class ScreenDoor
3
+ VERSION = "0.0.1"
4
+ end
5
+ 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: