door_code 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +23 -4
- data/Rakefile +10 -0
- data/door_code.gemspec +7 -3
- data/lib/door_code.rb +2 -119
- data/lib/{index.html → door_code/index.html} +10 -8
- data/lib/door_code/restricted_access.rb +126 -0
- data/test/helper.rb +31 -0
- data/test/test_restricted_access.rb +59 -0
- metadata +58 -6
data/README.md
CHANGED
@@ -1,19 +1,38 @@
|
|
1
|
-
|
1
|
+
Door Code
|
2
|
+
=========
|
3
|
+
|
4
|
+
### Restrict access with a 3-6 digit PIN code.
|
2
5
|
|
3
6
|
## Installation
|
4
7
|
|
5
|
-
|
8
|
+
Rubygems:
|
9
|
+
|
10
|
+
(sudo) gem install door_code
|
11
|
+
|
12
|
+
Bundler:
|
13
|
+
|
14
|
+
gem 'door_code', '~> 0.0.3'
|
6
15
|
|
7
|
-
|
16
|
+
### Then
|
8
17
|
|
9
18
|
In config.ru:
|
10
19
|
|
11
20
|
use DoorCode::RestrictedAccess, :code => '12345'
|
21
|
+
|
22
|
+
# to use a custom salt for cookie encryption
|
23
|
+
|
24
|
+
use DoorCode::RestrictedAccess, :code => '12345', :salt => "my super secret code"
|
25
|
+
|
26
|
+
|
12
27
|
|
13
28
|
In application.rb (Rails3) or environment.rb (Rails2):
|
14
29
|
|
15
30
|
config.middleware.use DoorCode::RestrictedAccess, :code => '12345'
|
16
31
|
|
32
|
+
## Demo
|
33
|
+
|
34
|
+
There is a simple demo application running on Heroku at [http://doorcodedemo.heroku.com](http://doorcodedemo.heroku.com). Log in using the default door code: `12345`
|
35
|
+
|
17
36
|
## Notes
|
18
37
|
|
19
38
|
* The default code is '12345'
|
@@ -21,5 +40,5 @@ In application.rb (Rails3) or environment.rb (Rails2):
|
|
21
40
|
|
22
41
|
## To Do
|
23
42
|
|
24
|
-
* Example app (heroku, sinatra)
|
25
43
|
* Allow specifying domains and paths to restrict access conditionally
|
44
|
+
* Write more tests
|
data/Rakefile
CHANGED
data/door_code.gemspec
CHANGED
@@ -3,10 +3,10 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "door_code"
|
6
|
-
s.version = '0.0.
|
6
|
+
s.version = '0.0.5'
|
7
7
|
s.platform = Gem::Platform::RUBY
|
8
|
-
s.authors = ["Mike Fulcher", "Alex Neill"]
|
9
|
-
s.email = ["mike@plan9design.co.uk", "alex.neill@gmail.com"]
|
8
|
+
s.authors = ["Mike Fulcher", "Alex Neill", "Spencer Steffen"]
|
9
|
+
s.email = ["mike@plan9design.co.uk", "alex.neill@gmail.com", "spencer@citrusme.com"]
|
10
10
|
s.homepage = "https://github.com/6twenty/door_code"
|
11
11
|
s.summary = %q{Restrict access to your site with a 3-6 digit PIN code}
|
12
12
|
s.description = %q{Rack middleware which requires that visitors to the site enter a 3-6 digit PIN code to gain access.}
|
@@ -20,4 +20,8 @@ Gem::Specification.new do |s|
|
|
20
20
|
|
21
21
|
# Runtime
|
22
22
|
s.add_runtime_dependency 'rack'
|
23
|
+
|
24
|
+
s.add_development_dependency 'shoulda', '2.11.3'
|
25
|
+
s.add_development_dependency 'rack-test', '0.5.7'
|
26
|
+
|
23
27
|
end
|
data/lib/door_code.rb
CHANGED
@@ -1,119 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
MIN_LENGTH = 3
|
5
|
-
MAX_LENGTH = 6
|
6
|
-
|
7
|
-
DEFAULT_CODE = '12345'
|
8
|
-
|
9
|
-
def initialize app, options={}
|
10
|
-
@app = app
|
11
|
-
@code = parse_code(options[:code])
|
12
|
-
end
|
13
|
-
|
14
|
-
# Ensures the code is good & valid, otherwise
|
15
|
-
# reverts to the default
|
16
|
-
def parse_code code
|
17
|
-
parsed_code = code.gsub(/\D/, '')
|
18
|
-
if parsed_code == code
|
19
|
-
# Means the supplied code contains only digits, which is good
|
20
|
-
# Just need to check that the code length is valid
|
21
|
-
parsed_code = DEFAULT_CODE if code.length < MIN_LENGTH || code.length > MAX_LENGTH
|
22
|
-
else
|
23
|
-
# Means the supplied code contained non-digits, so revert to default
|
24
|
-
parsed_code = DEFAULT_CODE
|
25
|
-
end
|
26
|
-
parsed_code
|
27
|
-
end
|
28
|
-
|
29
|
-
def cookie_name
|
30
|
-
'door_code'
|
31
|
-
end
|
32
|
-
|
33
|
-
# Rack::Request wrapper around @env
|
34
|
-
def request
|
35
|
-
@request ||= Rack::Request.new(@env)
|
36
|
-
end
|
37
|
-
|
38
|
-
# Rack::Response object with which to respond with
|
39
|
-
def response
|
40
|
-
@response ||= Rack::Response.new
|
41
|
-
end
|
42
|
-
|
43
|
-
# Is the request verb POST?
|
44
|
-
def post?
|
45
|
-
request.request_method == 'POST'
|
46
|
-
end
|
47
|
-
|
48
|
-
# Was the request called via AJAX?
|
49
|
-
def xhr?
|
50
|
-
@env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
|
51
|
-
end
|
52
|
-
|
53
|
-
# Code supplied from user
|
54
|
-
def supplied_code
|
55
|
-
request.params['code']
|
56
|
-
end
|
57
|
-
|
58
|
-
# Is the supplied code vaid for the current area
|
59
|
-
def valid_code? code
|
60
|
-
@code == code
|
61
|
-
end
|
62
|
-
|
63
|
-
# Check if the supplied code is valid;
|
64
|
-
# Either sets a confirming cookie and Success message
|
65
|
-
# or delete any door code cookie and set Failure message
|
66
|
-
def validate_code!
|
67
|
-
valid_code?(supplied_code) ? confirm! : unconfirm!
|
68
|
-
end
|
69
|
-
|
70
|
-
# Is there a valid code for the area set in the cookie
|
71
|
-
def confirmed?
|
72
|
-
request.cookies[cookie_name] && valid_code?(request.cookies[cookie_name])
|
73
|
-
end
|
74
|
-
|
75
|
-
# Set a cookie for the correct value (server value may change)
|
76
|
-
# Also set up Success message
|
77
|
-
def confirm!
|
78
|
-
xhr? ? response.write('success') : response.redirect('/')
|
79
|
-
response.set_cookie(cookie_name, {:value => supplied_code, :path => "/"})
|
80
|
-
end
|
81
|
-
|
82
|
-
# Delete and invalid cookies
|
83
|
-
# Also set up Failure message
|
84
|
-
def unconfirm!
|
85
|
-
xhr? ? response.write('failure') : response.redirect('/')
|
86
|
-
response.delete_cookie(supplied_code)
|
87
|
-
end
|
88
|
-
|
89
|
-
def code_length
|
90
|
-
@code.length.to_s
|
91
|
-
end
|
92
|
-
|
93
|
-
def build_rack_objects
|
94
|
-
@request = Rack::Request.new(@env)
|
95
|
-
@response = Rack::Response.new
|
96
|
-
end
|
97
|
-
|
98
|
-
# Where the magic happens...
|
99
|
-
def call env
|
100
|
-
@env = env
|
101
|
-
build_rack_objects
|
102
|
-
|
103
|
-
return @app.call(env) if confirmed?
|
104
|
-
p 'Loading DoorCode::RestrictedAccess'
|
105
|
-
|
106
|
-
if post?
|
107
|
-
response['Content-Type'] = 'text/javascript' if xhr?
|
108
|
-
validate_code! # Validate the user's code and set a cookie if valid
|
109
|
-
else
|
110
|
-
index = ::File.read(::File.dirname(__FILE__) + '/index.html')
|
111
|
-
index_with_code_length = index.gsub(/\{\{codeLength\}\}/, code_length)
|
112
|
-
response.write index_with_code_length
|
113
|
-
end
|
114
|
-
|
115
|
-
# Render response
|
116
|
-
return response.finish
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'door_code/restricted_access'
|
@@ -283,7 +283,10 @@
|
|
283
283
|
|
284
284
|
var interval;
|
285
285
|
var code;
|
286
|
-
var
|
286
|
+
var minLength = 3;
|
287
|
+
var maxLength = 6;
|
288
|
+
|
289
|
+
var clones;
|
287
290
|
|
288
291
|
var defaultClass = 'green';
|
289
292
|
var successClass = 'blue';
|
@@ -321,7 +324,7 @@
|
|
321
324
|
// Map clicking on the keys to showing the number in the display
|
322
325
|
$('#keys a').click(function(e){
|
323
326
|
e.preventDefault();
|
324
|
-
if ($('.clone').length
|
327
|
+
if ($('.clone').length <= maxLength) {
|
325
328
|
var num = $(this).attr('rel');
|
326
329
|
if (num === 'clear' || num === 'help') {
|
327
330
|
if (num === 'clear') { reset(); } else
|
@@ -340,7 +343,7 @@
|
|
340
343
|
if (charCode === backspace) { e.preventDefault(); }
|
341
344
|
if (charCode === backspace && $('.clone').length > 0) {
|
342
345
|
$('.clone').last().remove();
|
343
|
-
} else if (validKeys.indexOf(charCode) >= 0 && $('.clone').length
|
346
|
+
} else if (validKeys.indexOf(charCode) >= 0 && $('.clone').length <= maxLength) {
|
344
347
|
var n = charCode;
|
345
348
|
var num = '';
|
346
349
|
if (zero.indexOf(n) >= 0) { num = 'zero' }
|
@@ -382,12 +385,11 @@
|
|
382
385
|
|
383
386
|
function checkCode() {
|
384
387
|
// Check that the code is the correct length, and check it via ajax
|
385
|
-
|
388
|
+
clones = $('.clone');
|
389
|
+
if (clones.length >= minLength && clones.length <= maxLength) {
|
386
390
|
// Now we need to manually build the code based on the digits on display
|
387
391
|
code = '';
|
388
|
-
$('.clone').each(function(){
|
389
|
-
code += $(this).attr('rel');
|
390
|
-
});
|
392
|
+
$('.clone').each(function(){ code += $(this).attr('rel'); });
|
391
393
|
ajax();
|
392
394
|
}
|
393
395
|
}
|
@@ -400,7 +402,7 @@
|
|
400
402
|
dataType: "text",
|
401
403
|
success: function(data, textStatus, jqXHR){
|
402
404
|
if (data === 'success') { showSuccess(); } else
|
403
|
-
if (data === 'failure') { showError(); }
|
405
|
+
if (data === 'failure' && clones.length == maxLength) { showError(); }
|
404
406
|
}
|
405
407
|
});
|
406
408
|
}
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module DoorCode
|
2
|
+
class RestrictedAccess
|
3
|
+
|
4
|
+
MIN_LENGTH = 3
|
5
|
+
MAX_LENGTH = 6
|
6
|
+
|
7
|
+
DEFAULT_CODE = '12345'
|
8
|
+
|
9
|
+
def initialize app, options={}
|
10
|
+
@app = app
|
11
|
+
@salt = parse_salt(options[:salt])
|
12
|
+
@code = parse_code(options[:code])
|
13
|
+
end
|
14
|
+
|
15
|
+
# Ensures the code is good & valid, otherwise
|
16
|
+
# reverts to the default
|
17
|
+
def parse_code(code)
|
18
|
+
parsed_code = code.to_s.gsub(/\D/, '')
|
19
|
+
if parsed_code == code
|
20
|
+
# Means the supplied code contains only digits, which is good
|
21
|
+
# Just need to check that the code length is valid
|
22
|
+
parsed_code = DEFAULT_CODE if code.length < MIN_LENGTH || code.length > MAX_LENGTH
|
23
|
+
else
|
24
|
+
# Means the supplied code contained non-digits, so revert to default
|
25
|
+
parsed_code = DEFAULT_CODE
|
26
|
+
end
|
27
|
+
Digest::SHA1.hexdigest("--#{@salt}--#{parsed_code}--")
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Ensures a salt is supplied, otherwise set to default
|
32
|
+
def parse_salt(salt)
|
33
|
+
if 0 < salt.to_s.length
|
34
|
+
salt = Digest::SHA1.hexdigest("Door Code Secret Key")
|
35
|
+
end
|
36
|
+
salt
|
37
|
+
end
|
38
|
+
|
39
|
+
# Name of the cookie
|
40
|
+
def cookie_name
|
41
|
+
'door_code'
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the value of the saved cookie
|
45
|
+
def cookied_code
|
46
|
+
request.cookies[cookie_name]
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# Rack::Request wrapper around @env
|
51
|
+
def request
|
52
|
+
@request ||= Rack::Request.new(@env)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Rack::Response object with which to respond with
|
56
|
+
def response
|
57
|
+
@response ||= Rack::Response.new
|
58
|
+
end
|
59
|
+
|
60
|
+
# Encrypted code supplied from user
|
61
|
+
def supplied_code
|
62
|
+
Digest::SHA1.hexdigest("--#{@salt}--#{request.params['code']}--")
|
63
|
+
end
|
64
|
+
|
65
|
+
# Is the supplied code valid for the current area
|
66
|
+
def valid_code?(code)
|
67
|
+
@code == code
|
68
|
+
end
|
69
|
+
|
70
|
+
# Check if the supplied code is valid;
|
71
|
+
# Either sets a confirming cookie and Success message
|
72
|
+
# or delete any door code cookie and set Failure message
|
73
|
+
def validate_code!
|
74
|
+
valid_code?(supplied_code) ? confirm! : unconfirm!
|
75
|
+
end
|
76
|
+
|
77
|
+
# Is there a valid code for the area set in the cookie
|
78
|
+
def confirmed?
|
79
|
+
cookied_code && valid_code?(cookied_code)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set a cookie for the correct value (server value may change)
|
83
|
+
# Also set up Success message
|
84
|
+
def confirm!
|
85
|
+
request.xhr? ? response.write('success') : response.redirect('/')
|
86
|
+
response.set_cookie(cookie_name, { :value => supplied_code, :path => "/", :expire_after => (24*60*60) })
|
87
|
+
end
|
88
|
+
|
89
|
+
# Delete and invalid cookies
|
90
|
+
# Also set up Failure message
|
91
|
+
def unconfirm!
|
92
|
+
request.xhr? ? response.write('failure') : response.redirect('/')
|
93
|
+
response.delete_cookie(supplied_code)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Creates instances of Rack::Request and Rack::Response
|
97
|
+
def build_rack_objects
|
98
|
+
@request = Rack::Request.new(@env)
|
99
|
+
@response = Rack::Response.new
|
100
|
+
end
|
101
|
+
|
102
|
+
# Where the magic happens...
|
103
|
+
def call(env)
|
104
|
+
@env = env
|
105
|
+
build_rack_objects
|
106
|
+
|
107
|
+
return @app.call(env) if confirmed?
|
108
|
+
p 'Loading DoorCode::RestrictedAccess'
|
109
|
+
|
110
|
+
if request.post?
|
111
|
+
response['Content-Type'] = 'text/javascript' if request.xhr?
|
112
|
+
validate_code! # Validate the user's code and set a cookie if valid
|
113
|
+
else
|
114
|
+
|
115
|
+
# Set request status to Unauthorized
|
116
|
+
#response.status = 401
|
117
|
+
|
118
|
+
index = ::File.read(::File.dirname(__FILE__) + '/index.html')
|
119
|
+
response.write index
|
120
|
+
end
|
121
|
+
|
122
|
+
# Render response
|
123
|
+
return response.finish
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
ENV["environment"] = "test"
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rack/test'
|
5
|
+
require 'shoulda'
|
6
|
+
require 'door_code'
|
7
|
+
require 'sinatra/base'
|
8
|
+
|
9
|
+
|
10
|
+
module Rack
|
11
|
+
module Test
|
12
|
+
DEFAULT_HOST='localhost'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class TestingApp < Sinatra::Base
|
17
|
+
|
18
|
+
#set :sessions, false
|
19
|
+
|
20
|
+
use DoorCode::RestrictedAccess, :code => '12345'
|
21
|
+
|
22
|
+
get '/' do
|
23
|
+
'Logged In!'
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/logout' do
|
27
|
+
response.delete_cookie('door_code')
|
28
|
+
redirect '/'
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
# '12345' encrypted with the default salt
|
4
|
+
DEFAULT_CODE = '9fa483ac55e30318a84f0046365a21021a409117'
|
5
|
+
|
6
|
+
class TestRestrictedAccess < Test::Unit::TestCase
|
7
|
+
|
8
|
+
include Rack::Test::Methods
|
9
|
+
|
10
|
+
def app
|
11
|
+
TestingApp.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup
|
15
|
+
clear_cookies
|
16
|
+
end
|
17
|
+
|
18
|
+
should "require login" do
|
19
|
+
get "/"
|
20
|
+
assert_equal 200, last_response.status
|
21
|
+
assert last_response.body.include?("Authorized Personnel Only")
|
22
|
+
end
|
23
|
+
|
24
|
+
should "validate login" do
|
25
|
+
post "/", { "code" => "12345" }
|
26
|
+
assert_equal 302, last_response.status
|
27
|
+
|
28
|
+
follow_redirect!
|
29
|
+
|
30
|
+
assert_equal 200, last_response.status
|
31
|
+
assert last_response.body.include?("Logged In")
|
32
|
+
assert_equal DEFAULT_CODE, rack_mock_session.cookie_jar['door_code']
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
context "when cookie exists" do
|
37
|
+
|
38
|
+
setup do
|
39
|
+
set_cookie("door_code=#{DEFAULT_CODE}")
|
40
|
+
end
|
41
|
+
|
42
|
+
should "allow authorized cookies" do
|
43
|
+
get "/"
|
44
|
+
assert_equal 200, last_response.status
|
45
|
+
assert last_response.body.include?("Logged In")
|
46
|
+
end
|
47
|
+
|
48
|
+
should "logout" do
|
49
|
+
get "/logout"
|
50
|
+
assert_equal 302, last_response.status
|
51
|
+
|
52
|
+
follow_redirect!
|
53
|
+
|
54
|
+
assert_equal 200, last_response.status
|
55
|
+
assert last_response.body.include?("Authorized Personnel Only")
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
metadata
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: door_code
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 21
|
4
5
|
prerelease:
|
5
|
-
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 5
|
10
|
+
version: 0.0.5
|
6
11
|
platform: ruby
|
7
12
|
authors:
|
8
13
|
- Mike Fulcher
|
9
14
|
- Alex Neill
|
15
|
+
- Spencer Steffen
|
10
16
|
autorequire:
|
11
17
|
bindir: bin
|
12
18
|
cert_chain: []
|
13
19
|
|
14
|
-
date: 2011-02-
|
20
|
+
date: 2011-02-21 00:00:00 +00:00
|
15
21
|
default_executable:
|
16
22
|
dependencies:
|
17
23
|
- !ruby/object:Gem::Dependency
|
@@ -22,13 +28,49 @@ dependencies:
|
|
22
28
|
requirements:
|
23
29
|
- - ">="
|
24
30
|
- !ruby/object:Gem::Version
|
31
|
+
hash: 3
|
32
|
+
segments:
|
33
|
+
- 0
|
25
34
|
version: "0"
|
26
35
|
type: :runtime
|
27
36
|
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: shoulda
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - "="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 37
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 11
|
49
|
+
- 3
|
50
|
+
version: 2.11.3
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rack-test
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - "="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 5
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 5
|
65
|
+
- 7
|
66
|
+
version: 0.5.7
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id003
|
28
69
|
description: Rack middleware which requires that visitors to the site enter a 3-6 digit PIN code to gain access.
|
29
70
|
email:
|
30
71
|
- mike@plan9design.co.uk
|
31
72
|
- alex.neill@gmail.com
|
73
|
+
- spencer@citrusme.com
|
32
74
|
executables: []
|
33
75
|
|
34
76
|
extensions: []
|
@@ -42,7 +84,10 @@ files:
|
|
42
84
|
- Rakefile
|
43
85
|
- door_code.gemspec
|
44
86
|
- lib/door_code.rb
|
45
|
-
- lib/index.html
|
87
|
+
- lib/door_code/index.html
|
88
|
+
- lib/door_code/restricted_access.rb
|
89
|
+
- test/helper.rb
|
90
|
+
- test/test_restricted_access.rb
|
46
91
|
has_rdoc: true
|
47
92
|
homepage: https://github.com/6twenty/door_code
|
48
93
|
licenses: []
|
@@ -57,19 +102,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
57
102
|
requirements:
|
58
103
|
- - ">="
|
59
104
|
- !ruby/object:Gem::Version
|
105
|
+
hash: 3
|
106
|
+
segments:
|
107
|
+
- 0
|
60
108
|
version: "0"
|
61
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
110
|
none: false
|
63
111
|
requirements:
|
64
112
|
- - ">="
|
65
113
|
- !ruby/object:Gem::Version
|
114
|
+
hash: 3
|
115
|
+
segments:
|
116
|
+
- 0
|
66
117
|
version: "0"
|
67
118
|
requirements: []
|
68
119
|
|
69
120
|
rubyforge_project: door_code
|
70
|
-
rubygems_version: 1.5.
|
121
|
+
rubygems_version: 1.5.1
|
71
122
|
signing_key:
|
72
123
|
specification_version: 3
|
73
124
|
summary: Restrict access to your site with a 3-6 digit PIN code
|
74
|
-
test_files:
|
75
|
-
|
125
|
+
test_files:
|
126
|
+
- test/helper.rb
|
127
|
+
- test/test_restricted_access.rb
|