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 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