door_code 0.0.3 → 0.0.5
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/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
|