sinatra-security 0.1.1 → 0.1.2
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.markdown +7 -8
- data/VERSION +1 -1
- data/examples/classic.rb +12 -3
- data/lib/sinatra/security.rb +7 -3
- data/lib/sinatra/security/helpers.rb +2 -2
- data/lib/sinatra/security/identification.rb +17 -0
- data/lib/sinatra/security/password.rb +69 -0
- data/lib/sinatra/security/user.rb +12 -0
- data/lib/sinatra/security/validations.rb +24 -0
- data/sinatra-security.gemspec +10 -2
- data/test/helper.rb +22 -1
- data/test/test_password.rb +70 -0
- data/test/test_validations.rb +159 -0
- metadata +11 -3
data/README.markdown
CHANGED
@@ -5,22 +5,21 @@ This gem just provides you with the standard authentication mechanisms you would
|
|
5
5
|
|
6
6
|
How to use
|
7
7
|
==========
|
8
|
-
|
9
|
-
#
|
10
|
-
[sudo] gem install sinatra-security
|
8
|
+
|
9
|
+
# taken from examples/classic.rb
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
get "/" do
|
12
|
+
haml :home
|
13
|
+
end
|
15
14
|
|
16
15
|
get "/public" do
|
17
16
|
"Hello public world"
|
18
17
|
end
|
19
18
|
|
20
19
|
get "/private" do
|
21
|
-
|
20
|
+
require_login
|
22
21
|
|
23
|
-
"Hello private world"
|
22
|
+
"Hello private world <a href='/logout'>Logout</a>"
|
24
23
|
end
|
25
24
|
|
26
25
|
get "/login" do
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.2
|
data/examples/classic.rb
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
require 'sinatra'
|
2
2
|
require 'sinatra/security'
|
3
3
|
|
4
|
-
class User
|
4
|
+
class ::User
|
5
5
|
attr :id
|
6
|
+
|
7
|
+
def self.attribute(att)
|
8
|
+
@attributes ||= []
|
9
|
+
@attributes << att
|
10
|
+
attr_accessor att
|
11
|
+
end
|
12
|
+
include Sinatra::Security::User
|
6
13
|
|
7
|
-
|
8
|
-
|
14
|
+
# we override this for the sake of example
|
15
|
+
def self.find_by_login(login)
|
16
|
+
User.new(42) if login == 'quentin'
|
9
17
|
end
|
10
18
|
|
11
19
|
def self.[](id)
|
@@ -14,6 +22,7 @@ class User
|
|
14
22
|
|
15
23
|
def initialize(id = nil)
|
16
24
|
@id = id
|
25
|
+
@crypted_password = Sinatra::Security::Password::Hashing.update('test')
|
17
26
|
end
|
18
27
|
end
|
19
28
|
|
data/lib/sinatra/security.rb
CHANGED
@@ -8,8 +8,12 @@ end
|
|
8
8
|
|
9
9
|
module Sinatra
|
10
10
|
module Security
|
11
|
-
autoload :Helpers,
|
12
|
-
|
11
|
+
autoload :Helpers, 'sinatra/security/helpers'
|
12
|
+
autoload :User, 'sinatra/security/user'
|
13
|
+
autoload :Validations, 'sinatra/security/validations'
|
14
|
+
autoload :Password, 'sinatra/security/password'
|
15
|
+
autoload :Identification, 'sinatra/security/identification'
|
16
|
+
|
13
17
|
def self.registered(app)
|
14
18
|
app.helpers Helpers
|
15
19
|
|
@@ -17,7 +21,7 @@ module Sinatra
|
|
17
21
|
if authenticate(params)
|
18
22
|
redirect_to_stored
|
19
23
|
else
|
20
|
-
session[:error] = "
|
24
|
+
session[:error] = "Wrong Username/Email and Password combination."
|
21
25
|
haml :login
|
22
26
|
end
|
23
27
|
end
|
@@ -11,7 +11,7 @@ module Sinatra
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def authenticate(params)
|
14
|
-
if user = User.authenticate(params[:username], params[:password])
|
14
|
+
if user = ::User.authenticate(params[:username], params[:password])
|
15
15
|
session[:user] = user.id
|
16
16
|
end
|
17
17
|
end
|
@@ -27,7 +27,7 @@ module Sinatra
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def current_user
|
30
|
-
@current_user ||= User[session[:user]] if session[:user]
|
30
|
+
@current_user ||= ::User[session[:user]] if session[:user]
|
31
31
|
end
|
32
32
|
|
33
33
|
def logged_in?
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Security
|
3
|
+
module Identification
|
4
|
+
def find_by_login(login)
|
5
|
+
find(:email => login).first
|
6
|
+
end
|
7
|
+
|
8
|
+
def authenticate(login, pass)
|
9
|
+
if user = find_by_login(login)
|
10
|
+
if Sinatra::Security::Password::Hashing.check(pass, user.crypted_password)
|
11
|
+
user
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
|
3
|
+
module Sinatra
|
4
|
+
module Security
|
5
|
+
module Password
|
6
|
+
def self.included(base)
|
7
|
+
base.send :attribute, :crypted_password
|
8
|
+
base.send :attr_accessor, :password, :password_confirmation
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
def write
|
13
|
+
if !password.to_s.empty?
|
14
|
+
write_local :crypted_password, Sinatra::Security::Password::Hashing.update(password)
|
15
|
+
end
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
module Hashing
|
21
|
+
extend self
|
22
|
+
|
23
|
+
# Generates a new salt and rehashes the password
|
24
|
+
def update(password)
|
25
|
+
salt = self.salt
|
26
|
+
hash = self.hash(password,salt)
|
27
|
+
self.store(hash, salt)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Checks the password against the stored password
|
31
|
+
def check(password, stored)
|
32
|
+
hash = self.get_hash(stored)
|
33
|
+
salt = self.get_salt(stored)
|
34
|
+
|
35
|
+
self.hash(password, salt) == hash
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
# Generates a psuedo-random 64 character string
|
40
|
+
def salt
|
41
|
+
salt = ""
|
42
|
+
64.times {
|
43
|
+
salt << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }
|
44
|
+
salt
|
45
|
+
end
|
46
|
+
|
47
|
+
# Generates a 128 character hash
|
48
|
+
def hash(password, salt)
|
49
|
+
Digest::SHA512.hexdigest("#{ password }:#{ salt }")
|
50
|
+
end
|
51
|
+
|
52
|
+
# Mixes the hash and salt together for storage
|
53
|
+
def store(hash, salt)
|
54
|
+
hash + salt
|
55
|
+
end
|
56
|
+
|
57
|
+
# Gets the hash from a stored password
|
58
|
+
def get_hash(stored)
|
59
|
+
stored[0..127]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Gets the salt from a stored password
|
63
|
+
def get_salt(stored)
|
64
|
+
stored[128..192]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Security
|
3
|
+
module Validations
|
4
|
+
EMAIL_FORMAT = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
|
5
|
+
|
6
|
+
protected
|
7
|
+
def assert_login_using_email(att, error = [att, :not_email])
|
8
|
+
if assert_present att
|
9
|
+
if assert_format att, EMAIL_FORMAT, error
|
10
|
+
assert_unique att
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_password(att, error = [att, :not_present])
|
16
|
+
confirmation_att = :"#{ att }_confirmation"
|
17
|
+
|
18
|
+
if new? && assert_present(att) || !send(att).to_s.empty?
|
19
|
+
assert send(att) == send(confirmation_att), [att, :not_confirmed]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/sinatra-security.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{sinatra-security}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Cyril David"]
|
12
|
-
s.date = %q{2010-04-
|
12
|
+
s.date = %q{2010-04-29}
|
13
13
|
s.description = %q{For use with Sinatra + Monk + OHM}
|
14
14
|
s.email = %q{cyx.ucron@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -28,10 +28,16 @@ Gem::Specification.new do |s|
|
|
28
28
|
"examples/views/login.haml",
|
29
29
|
"lib/sinatra/security.rb",
|
30
30
|
"lib/sinatra/security/helpers.rb",
|
31
|
+
"lib/sinatra/security/identification.rb",
|
32
|
+
"lib/sinatra/security/password.rb",
|
33
|
+
"lib/sinatra/security/user.rb",
|
34
|
+
"lib/sinatra/security/validations.rb",
|
31
35
|
"sinatra-security.gemspec",
|
32
36
|
"test/helper.rb",
|
37
|
+
"test/test_password.rb",
|
33
38
|
"test/test_sinatra-security.rb",
|
34
39
|
"test/test_sinatra_security_helpers.rb",
|
40
|
+
"test/test_validations.rb",
|
35
41
|
"views/login.haml"
|
36
42
|
]
|
37
43
|
s.homepage = %q{http://github.com/cyx/sinatra-security}
|
@@ -41,8 +47,10 @@ Gem::Specification.new do |s|
|
|
41
47
|
s.summary = %q{Sinatra authentication extension}
|
42
48
|
s.test_files = [
|
43
49
|
"test/helper.rb",
|
50
|
+
"test/test_password.rb",
|
44
51
|
"test/test_sinatra-security.rb",
|
45
52
|
"test/test_sinatra_security_helpers.rb",
|
53
|
+
"test/test_validations.rb",
|
46
54
|
"examples/classic.rb"
|
47
55
|
]
|
48
56
|
|
data/test/helper.rb
CHANGED
@@ -25,9 +25,30 @@ end
|
|
25
25
|
# Test Fixtures appear here
|
26
26
|
class User
|
27
27
|
attr :id
|
28
|
+
attr_accessor :email, :password
|
28
29
|
|
29
|
-
def initialize(id)
|
30
|
+
def initialize(id = nil)
|
30
31
|
@id = id
|
31
32
|
end
|
33
|
+
|
34
|
+
def errors
|
35
|
+
@errors ||= []
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def assert(value, error)
|
40
|
+
value or errors.push(error) && false
|
41
|
+
end
|
42
|
+
|
43
|
+
def assert_present(att, error = [att, :not_present])
|
44
|
+
assert(!send(att).to_s.empty?, error)
|
45
|
+
end
|
46
|
+
|
47
|
+
def assert_format(att, format, error = [att, :format])
|
48
|
+
if assert_present(att, error)
|
49
|
+
assert(send(att).to_s.match(format), error)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
32
53
|
end
|
33
54
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class FakeOhm
|
4
|
+
def self.attribute(att)
|
5
|
+
end
|
6
|
+
|
7
|
+
def write
|
8
|
+
:real_write_response
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
def write_local(att, value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class UserWithPassword < FakeOhm
|
17
|
+
include Sinatra::Security::Password
|
18
|
+
end
|
19
|
+
|
20
|
+
class TestPassword < Test::Unit::TestCase
|
21
|
+
include Sinatra::Security
|
22
|
+
|
23
|
+
describe "#update" do
|
24
|
+
should "return a 192 character string" do
|
25
|
+
assert_equal 192, Password::Hashing.update('123456').length
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#check" do
|
30
|
+
should "be able to match the original password given" do
|
31
|
+
@password = 'password100'
|
32
|
+
|
33
|
+
assert Password::Hashing.check(@password, Password::Hashing.update(@password))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#write when Password is included" do
|
38
|
+
setup do
|
39
|
+
@user = UserWithPassword.new
|
40
|
+
end
|
41
|
+
|
42
|
+
should "return the original write response" do
|
43
|
+
assert_equal :real_write_response, @user.send(:write)
|
44
|
+
end
|
45
|
+
|
46
|
+
should "write the crypted password given and match the original via #check" do
|
47
|
+
@user.password = 'password'
|
48
|
+
@crypted_password = nil
|
49
|
+
@user.expects(:write_local).with() { |field, v|
|
50
|
+
field == :crypted_password && !(@crypted_password = v).nil?
|
51
|
+
}
|
52
|
+
|
53
|
+
@user.send(:write)
|
54
|
+
|
55
|
+
assert Password::Hashing.check('password', @crypted_password)
|
56
|
+
end
|
57
|
+
|
58
|
+
should "not write a crypted password when the password is empty" do
|
59
|
+
@user.password = ''
|
60
|
+
@crypted_password = nil
|
61
|
+
@user.stubs(:write_local).raises(RuntimeError)
|
62
|
+
|
63
|
+
assert_nothing_raised RuntimeError do
|
64
|
+
@user.send(:write)
|
65
|
+
end
|
66
|
+
|
67
|
+
assert_nil @crypted_password
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class UserWithEmailValidation < User
|
4
|
+
include Sinatra::Security::Validations
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super
|
8
|
+
|
9
|
+
@password = 'password'
|
10
|
+
@password_confirmation = 'password'
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate
|
14
|
+
assert_login_using_email :email
|
15
|
+
end
|
16
|
+
|
17
|
+
def errors
|
18
|
+
@errors ||= []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class UserWithPasswordValidation < User
|
23
|
+
include Sinatra::Security::Validations
|
24
|
+
attr_accessor :password_confirmation
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
super
|
28
|
+
|
29
|
+
@email = 'real@email.com'
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate
|
33
|
+
assert_password :password
|
34
|
+
end
|
35
|
+
|
36
|
+
def errors
|
37
|
+
@errors ||= []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class TestValidations < Test::Unit::TestCase
|
42
|
+
context "without an email" do
|
43
|
+
should "require the email to be present" do
|
44
|
+
user = UserWithEmailValidation.new
|
45
|
+
user.validate
|
46
|
+
|
47
|
+
assert_equal [[:email, :not_present]], user.errors
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "with an email _not_an_email_" do
|
52
|
+
should "have an error in the format" do
|
53
|
+
user = UserWithEmailValidation.new
|
54
|
+
user.email = '_not_an_email_'
|
55
|
+
user.validate
|
56
|
+
|
57
|
+
assert_equal [[:email, :not_email]], user.errors
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "given a real deal email" do
|
62
|
+
should "check if it's actually unique" do
|
63
|
+
user = UserWithEmailValidation.new
|
64
|
+
user.email = 'real@email.com'
|
65
|
+
|
66
|
+
user.expects(:assert_unique).with(:email)
|
67
|
+
user.validate
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "without a password" do
|
72
|
+
context "when it's a new User" do
|
73
|
+
should "have a password error" do
|
74
|
+
user = UserWithPasswordValidation.new
|
75
|
+
user.stubs(:new?).returns(true)
|
76
|
+
user.validate
|
77
|
+
|
78
|
+
assert_equal [[:password, :not_present]], user.errors
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when it's an existing user" do
|
83
|
+
should "not require a password" do
|
84
|
+
user = UserWithPasswordValidation.new
|
85
|
+
user.stubs(:new?).returns(false)
|
86
|
+
user.validate
|
87
|
+
|
88
|
+
assert_equal [], user.errors
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "a new User with a password and no confirmation" do
|
94
|
+
setup do
|
95
|
+
@user = UserWithPasswordValidation.new
|
96
|
+
@user.password = 'password'
|
97
|
+
@user.stubs(:new?).returns(true)
|
98
|
+
end
|
99
|
+
|
100
|
+
should "have a password confirmation error" do
|
101
|
+
@user.validate
|
102
|
+
assert_equal [[:password, :not_confirmed]], @user.errors
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when the password confirmation is filled up but mismatched" do
|
106
|
+
should "still have a password confirmation error" do
|
107
|
+
@user.password_confirmation = 'password1'
|
108
|
+
@user.validate
|
109
|
+
assert_equal [[:password, :not_confirmed]], @user.errors
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "an existing User with a password and no confirmation" do
|
115
|
+
setup do
|
116
|
+
@user = UserWithPasswordValidation.new
|
117
|
+
@user.password = 'password'
|
118
|
+
@user.stubs(:new?).returns(false)
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
should "have a password confirmation error" do
|
123
|
+
@user.validate
|
124
|
+
assert_equal [[:password, :not_confirmed]], @user.errors
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when mismatched confirmation is filled up" do
|
128
|
+
should "still have a confirmation error" do
|
129
|
+
@user.password_confirmation = 'password1'
|
130
|
+
@user.validate
|
131
|
+
assert_equal [[:password, :not_confirmed]], @user.errors
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context "given correct passwords" do
|
137
|
+
setup do
|
138
|
+
@user = UserWithPasswordValidation.new
|
139
|
+
@user.password = 'password'
|
140
|
+
@user.password_confirmation = 'password'
|
141
|
+
end
|
142
|
+
|
143
|
+
context "when new user" do
|
144
|
+
should "have no errors" do
|
145
|
+
@user.stubs(:new?).returns(true)
|
146
|
+
@user.validate
|
147
|
+
assert @user.errors.empty?
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "when existing user" do
|
152
|
+
should "have no errors" do
|
153
|
+
@user.stubs(:new?).returns(false)
|
154
|
+
@user.validate
|
155
|
+
assert @user.errors.empty?
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 2
|
9
|
+
version: 0.1.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Cyril David
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-29 00:00:00 +08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -39,10 +39,16 @@ files:
|
|
39
39
|
- examples/views/login.haml
|
40
40
|
- lib/sinatra/security.rb
|
41
41
|
- lib/sinatra/security/helpers.rb
|
42
|
+
- lib/sinatra/security/identification.rb
|
43
|
+
- lib/sinatra/security/password.rb
|
44
|
+
- lib/sinatra/security/user.rb
|
45
|
+
- lib/sinatra/security/validations.rb
|
42
46
|
- sinatra-security.gemspec
|
43
47
|
- test/helper.rb
|
48
|
+
- test/test_password.rb
|
44
49
|
- test/test_sinatra-security.rb
|
45
50
|
- test/test_sinatra_security_helpers.rb
|
51
|
+
- test/test_validations.rb
|
46
52
|
- views/login.haml
|
47
53
|
has_rdoc: true
|
48
54
|
homepage: http://github.com/cyx/sinatra-security
|
@@ -76,6 +82,8 @@ specification_version: 3
|
|
76
82
|
summary: Sinatra authentication extension
|
77
83
|
test_files:
|
78
84
|
- test/helper.rb
|
85
|
+
- test/test_password.rb
|
79
86
|
- test/test_sinatra-security.rb
|
80
87
|
- test/test_sinatra_security_helpers.rb
|
88
|
+
- test/test_validations.rb
|
81
89
|
- examples/classic.rb
|