sinatra-security 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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
- # in your terminal
10
- [sudo] gem install sinatra-security
8
+
9
+ # taken from examples/classic.rb
11
10
 
12
- # in your sinatra app
13
- require 'sinatra'
14
- require 'sinatra/security'
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
- login_required
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
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
- def self.authenticate(user, pass)
8
- User.new(42) if [ user, pass ] == [ 'quentin', 'test' ]
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
 
@@ -8,8 +8,12 @@ end
8
8
 
9
9
  module Sinatra
10
10
  module Security
11
- autoload :Helpers, 'sinatra/security/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] = "We are sorry: the information supplied is not valid."
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,12 @@
1
+ module Sinatra
2
+ module Security
3
+ module User
4
+ def self.included(user)
5
+ user.send :include, Validations
6
+ user.send :include, Password
7
+
8
+ user.extend Identification
9
+ end
10
+ end
11
+ end
12
+ 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
@@ -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.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-09}
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
- - 1
9
- version: 0.1.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-09 00:00:00 +08:00
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