sinatra-security 0.1.6 → 0.2.0

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/.gitignore CHANGED
@@ -19,3 +19,5 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
+ /doc
23
+ /.yardoc
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.6
1
+ 0.2.0
@@ -2,6 +2,8 @@ require 'sinatra/base'
2
2
 
3
3
  module Sinatra
4
4
  module Security
5
+ VERSION = "0.2.0"
6
+
5
7
  autoload :Helpers, 'sinatra/security/helpers'
6
8
  autoload :User, 'sinatra/security/user'
7
9
  autoload :Validations, 'sinatra/security/validations'
@@ -12,12 +14,43 @@ module Sinatra
12
14
  def self.registered(app)
13
15
  app.helpers Helpers
14
16
 
17
+ app.set :login_error_message, "Wrong Email and/or Password combination."
18
+ app.set :login_url, "/login"
19
+ app.set :login_user_class, :User
20
+ app.set :ignored_by_return_to, /(jpe?g|png|gif|css|js)$/
21
+
15
22
  app.post '/login' do
16
23
  if authenticate(params)
17
- redirect_to_stored
24
+ redirect_to_return_url
18
25
  else
19
- session[:error] = "Wrong Username/Email and Password combination."
20
- redirect '/login'
26
+ session[:error] = settings.login_error_message
27
+ redirect settings.login_url
28
+ end
29
+ end
30
+ end
31
+
32
+ # Allows you to declaratively declare secured locations based on their
33
+ # path prefix.
34
+ #
35
+ # @example
36
+ #
37
+ # require_login '/admin'
38
+ #
39
+ # get '/admin/posts' do
40
+ # # Posts here
41
+ # end
42
+ #
43
+ # get '/admin/users' do
44
+ # # Users here
45
+ # end
46
+ #
47
+ # @param [#to_s] path_prefix a string to match again the start of
48
+ # request.fullpath
49
+ # @return [nil]
50
+ def require_login(path_prefix)
51
+ before do
52
+ if request.fullpath =~ /^#{path_prefix}/
53
+ require_login
21
54
  end
22
55
  end
23
56
  end
@@ -1,51 +1,132 @@
1
1
  module Sinatra
2
2
  module Security
3
3
  module Helpers
4
- def redirect_to_stored
5
- if return_to = session[:return_to]
6
- session[:return_to] = nil
7
- redirect return_to
8
- else
9
- redirect "/"
10
- end
11
- end
4
+ # The main gateway. This method will redirect if no user is currently
5
+ # authenticated.
6
+ #
7
+ # @example
8
+ #
9
+ # get '/secured' do
10
+ # require_login
11
+ #
12
+ # # do super private thing here
13
+ # end
14
+ #
15
+ # @param [String] login_url (defaults to /login) the url of the login form.
16
+ # take not that even if you specify a different login form,
17
+ # the POST action for that form should still be '/login'.
18
+ def require_login(login_url = settings.login_url)
19
+ return if logged_in?
12
20
 
13
- def authenticate(params)
14
- if user = ::User.authenticate(params[:username], params[:password])
15
- session[:user] = user.id
21
+ if should_return_to?(request.fullpath)
22
+ session[:return_to] = request.fullpath
16
23
  end
24
+ redirect login_url
17
25
  end
18
-
19
- def require_login
20
- if logged_in?
21
- return true
22
- else
23
- if should_return_to?(request.fullpath)
24
- session[:return_to] = request.fullpath
25
- end
26
- redirect "/login"
27
- return false
28
- end
26
+
27
+ # Dynamic redirection based on the return path that was set.
28
+ #
29
+ # @example
30
+ #
31
+ # # By default assumes you use :return_to and '/'.
32
+ # # You can use this in your code as well. i.e.
33
+ # get '/fb/login' do
34
+ # session[:fb_return_to] = params[:from]
35
+ # # redirect to fb OAuth URI here.
36
+ # end
37
+ #
38
+ # get '/fb/success' do
39
+ # # successfully processed, save whatever here
40
+ # redirect_to_return_url :fb_return_to, "/home"
41
+ # end
42
+ #
43
+ # @param [Symbol] session_key the key in the session, defaults to
44
+ # :return_to.
45
+ # @param [String] default url when no stored value is found in
46
+ # session[session_key]. defaults to '/'.
47
+ def redirect_to_return_url(session_key = :return_to, default = '/')
48
+ redirect session.delete(:return_to) || default
29
49
  end
30
-
31
- def current_user
32
- @current_user ||= ::User[session[:user]] if session[:user]
50
+
51
+ # Returns the currently logged in user, identified through
52
+ # session[:user]. The default finder uses User[id], based on Ohm's
53
+ # finder method.
54
+ #
55
+ # @see http://ohm.keyvalue.org
56
+ #
57
+ # @example
58
+ #
59
+ # # ActiveRecord style finders
60
+ # current_user(lambda { |id| User.find(id) })
61
+ #
62
+ # # Also, if you change the settings to use a different user class,
63
+ # # then that will be respected
64
+ # set :login_user_class, :SuperUser
65
+ #
66
+ # # assuming session[:user] == 1
67
+ # current_user == SuperUser[1]
68
+ # # => true
69
+ #
70
+ # @param [Proc] finder (defaults to User[id]) allows you to pass in a
71
+ # different finder method.
72
+ # @return [User] or alternatively, an instance of settings.login_user_class
73
+ def current_user(finder = lambda { |id| __USER__[id] })
74
+ @current_user ||= finder.call(session[:user]) if session[:user]
33
75
  end
34
-
76
+
77
+ # @return [true] if the user is logged in
78
+ # @return [false] if the user is not logged in
35
79
  def logged_in?
36
80
  !! current_user
37
81
  end
38
-
82
+
83
+ # Used for simple atomic authorization rules on a per action / route
84
+ # basis.
85
+ #
86
+ # @example
87
+ #
88
+ # get '/posts/:id/edit' do |id|
89
+ # post = Post[id]
90
+ # ensure_current_user post.author # halts to a 404 if not satisfied.
91
+ #
92
+ # # the rest of this gets executed when
93
+ # # the author is indeed the current user.
94
+ # end
95
+ #
96
+ # @param [User] a user object.
39
97
  def ensure_current_user(user)
40
98
  halt 404 unless user == current_user
41
99
  end
42
-
100
+
101
+ # The method says it all. Mostly for keeping responsibility where it
102
+ # belongs, instead of letting the application code deal with the session
103
+ # keys themselves.
43
104
  def logout!
44
105
  session.delete(:user)
45
106
  end
107
+
108
+ # Internally used by the POST /login route handler.
109
+ #
110
+ # @param [Hash] opts The hash containing :username and :password.
111
+ # @option opts [#to_s] :username The username of a User.
112
+ # @option opts [String] :password The password of a User.
113
+ # @return [String] the `id` of the user if found.
114
+ # @return [nil] if no user matches the :username / :password combination.
115
+ def authenticate(opts)
116
+ if user = __USER__.authenticate(opts[:username], opts[:password])
117
+ session[:user] = user.id
118
+ end
119
+ end
46
120
 
47
- def should_return_to?(path)
48
- !(path =~ /(jpe?g|png|gif|css|js)$/)
121
+ # @private transforms settings.login_user_class to a constant,
122
+ # and used by current_user
123
+ def __USER__
124
+ Object.const_get(settings.login_user_class)
125
+ end
126
+
127
+ # @private internally used by Sinatra::Security::Helpers#require_login
128
+ def should_return_to?(path, ignored = settings.ignored_by_return_to)
129
+ !(path =~ ignored)
49
130
  end
50
131
  end
51
132
  end
@@ -1,17 +1,55 @@
1
1
  module Sinatra
2
2
  module Security
3
+ # The identification module is mixed into Sinatra::Security::User. The
4
+ # API loosely applies to Ohm, although it will work with ActiveRecord.
5
+ #
6
+ # @example
7
+ #
8
+ # class User < Ohm::Model
9
+ # include Sinatra::Security::User
10
+ # end
11
+ #
12
+ # User.respond_to?(:find_by_login)
13
+ # # => true
14
+ #
15
+ # User.respond_to?(:authenticate)
16
+ # # => true
17
+ #
18
+ # If you wish to override any of these, it's as simple as defining your
19
+ # own method in your User class.
3
20
  module Identification
4
- def find_by_login(login)
5
- find(:email => login).first
6
- end
7
-
8
- def authenticate(login, pass)
21
+ # Finds the User matching the given login / password combination.
22
+ # @see Sinatra::Security::Helpers#authenticate
23
+ #
24
+ # @param [#to_s] login the value of `:email` in your datastore.
25
+ # @param [String] password the raw password for the user in `:email`.
26
+ # @return [User] the user matching the credentials if found.
27
+ # @return [nil] if no matching user found.
28
+ def authenticate(login, password)
9
29
  if user = find_by_login(login)
10
- if Sinatra::Security::Password::Hashing.check(pass, user.crypted_password)
30
+ if Sinatra::Security::Password::Hashing.check(password, user.crypted_password)
11
31
  user
12
32
  end
13
33
  end
14
34
  end
35
+
36
+ # Used internally by User::authenticate to find the user given the
37
+ # `:email` value.
38
+ #
39
+ # @param [#to_s] login the value of `:email` in your datastore. You may
40
+ # also override the key used.
41
+ # @return [User] an instance of User if found.
42
+ # @return [nil] if no user found with the given login value.
43
+ #
44
+ # @see Sinatra::Security::LoginField::attr_name
45
+ def find_by_login(login)
46
+ find(__LOGIN_FIELD__ => login).first
47
+ end
48
+
49
+ protected
50
+ def __LOGIN_FIELD__
51
+ Sinatra::Security::LoginField.attr_name
52
+ end
15
53
  end
16
54
  end
17
55
  end
@@ -1,11 +1,44 @@
1
1
  module Sinatra
2
2
  module Security
3
+ # This module allows you to customize the name of the login field used
4
+ # in your datastore. By default :email is used.
5
+ #
6
+ # @example
7
+ #
8
+ # # somewhere in your code during bootstrapping
9
+ # require 'sinatra/security'
10
+ # Sinatra::Security::LoginField.attr_name :login
11
+ #
12
+ # # then in your actual user.rb or something...
13
+ # class User < Ohm::Model
14
+ # include Sinatra::Security::User
15
+ # # at this point the following are done:
16
+ # # attribute :login
17
+ # # index :login
18
+ # end
3
19
  module LoginField
20
+ # @example
21
+ #
22
+ # Sinatra::Security::LoginField.attr_name :username
23
+ #
24
+ # class User < Ohm::Model
25
+ # include Sinatra::Security::User
26
+ # # effectively executes the following:
27
+ # # attribute :username
28
+ # # index :username
29
+ # end
30
+ #
31
+ # @overload attr_name()
32
+ # Get the value of attr_name.
33
+ # @overload attr_name(attr_name)
34
+ # @param [#to_sym] attr_name the attr_name to be used e.g.
35
+ # :username, :login.
36
+ # @return [Symbol] the current attr_name.
4
37
  def self.attr_name(attr_name = nil)
5
- @attr_name = attr_name if attr_name
38
+ @attr_name = attr_name.to_sym if attr_name
6
39
  @attr_name
7
40
  end
8
- attr_name(:email)
41
+ attr_name :email
9
42
 
10
43
  def self.included(user)
11
44
  user.attribute LoginField.attr_name
@@ -13,4 +46,4 @@ module Sinatra
13
46
  end
14
47
  end
15
48
  end
16
- end
49
+ end
@@ -2,16 +2,36 @@ require 'digest/sha2'
2
2
 
3
3
  module Sinatra
4
4
  module Security
5
+ # This module handles everything related to password handling.
6
+ #
7
+ # @example
8
+ #
9
+ # class User < Ohm::Model
10
+ # include Sinatra::Security::Password
11
+ # end
12
+ #
13
+ # User.attributes == [:crypted_password]
14
+ # # => true
15
+ #
16
+ # User.new.respond_to?(:password)
17
+ # # => true
18
+ #
19
+ # User.new.respond_to?(:password_confirmation)
20
+ # # => true
21
+ #
22
+ # @see http://ohm.keyvalue.org for information about Ohm::Model.
5
23
  module Password
6
- def self.included(base)
7
- base.send :attribute, :crypted_password
8
- base.send :attr_accessor, :password, :password_confirmation
24
+ def self.included(model)
25
+ model.attribute :crypted_password
26
+
27
+ model.send :attr_accessor, :password, :password_confirmation
9
28
  end
10
29
 
11
30
  protected
31
+ # @private internally called by Ohm after validation when persisting.
12
32
  def write
13
33
  if !password.to_s.empty?
14
- write_local :crypted_password, Sinatra::Security::Password::Hashing.update(password)
34
+ write_local :crypted_password, Hashing.encrypt(password)
15
35
  end
16
36
 
17
37
  super
@@ -20,24 +40,61 @@ module Sinatra
20
40
  module Hashing
21
41
  extend self
22
42
 
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)
43
+ # Given any string generates a string which includes both the
44
+ # 128 character (SHA512) hash and the salt.
45
+ #
46
+ # By default the salt is a 64 character pseudo-random string.
47
+ #
48
+ # @example
49
+ #
50
+ # include Sinatra::Security
51
+ #
52
+ # crypted = Password::Hashing.encrypt('123')
53
+ # crypted.length == 192
54
+ # # => true
55
+ #
56
+ # crypted = Password::Hashing.encrypt('123', '123456789012')
57
+ # crypted.length == 140 # i.e. 128 + 12 chars
58
+ # # => true
59
+ #
60
+ # crypted = Password::Hashing.encrypt('123')
61
+ # Password::Hashing.check('123', crypted)
62
+ # # => true
63
+ #
64
+ #
65
+ # @param [String] password any string with any length.
66
+ # @param [String] salt (defaults to Hashing#salt) any string.
67
+ # @return [String] a string holding the crypted password and the salt.
68
+ #
69
+ # @see Sinatra::Security::Password::Hashing#check
70
+ def encrypt(password, salt = self.generate_salt)
71
+ serialize(hash(password, salt), salt)
28
72
  end
29
73
 
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)
74
+ # Checks the password against the serialized password.
75
+ #
76
+ # @example
77
+ #
78
+ # include Sinatra::Security
79
+ # crypted = Password::Hashing.encrypt('123')
80
+ # Password::Hashing.check('123', crypted)
81
+ # # => true
82
+ #
83
+ # @param [String] password a string to check against crypted.
84
+ # @param [String] crypted the serialized string containing the
85
+ # hash and salt.
86
+ #
87
+ # @return [true] if the password matches the hash / salt combination.
88
+ # @return [false] if the password does not match the hash / salt.
89
+ def check(password, crypted)
90
+ hash, salt = unserialize(crypted)
34
91
 
35
92
  self.hash(password, salt) == hash
36
93
  end
37
94
 
38
95
  protected
39
96
  # Generates a psuedo-random 64 character string
40
- def salt
97
+ def generate_salt
41
98
  salt = ""
42
99
  64.times {
43
100
  salt << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }
@@ -50,18 +107,13 @@ module Sinatra
50
107
  end
51
108
 
52
109
  # Mixes the hash and salt together for storage
53
- def store(hash, salt)
110
+ def serialize(hash, salt)
54
111
  hash + salt
55
112
  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]
113
+
114
+ # Returns the original hash and salt generated from serialize.
115
+ def unserialize(serialized)
116
+ return serialized[0..127], serialized[128..-1]
65
117
  end
66
118
  end
67
119
  end
@@ -1,5 +1,30 @@
1
1
  module Sinatra
2
2
  module Security
3
+ # Mixes in to any included class all of the following:
4
+ #
5
+ # * Sinatra::Security::LoginField
6
+ # * Sinatra::Security::Password
7
+ # * Sinatra::Security::Validations
8
+ #
9
+ # It also extends the class with Sinatra::Security::Identification.
10
+ #
11
+ # @example
12
+ #
13
+ # class User < Ohm::Model
14
+ # include Sinatra::Security::User
15
+ # end
16
+ #
17
+ # user = User.new
18
+ # user.valid?
19
+ # user.errors == [[:email, :not_present], [:password, :not_present]]
20
+ # # => true
21
+ #
22
+ # user = User.create(:email => "test@example.com", :password => "pass",
23
+ # :password_confirmation => "pass")
24
+ #
25
+ # User.authenticate("test@example.com", "pass") == user
26
+ # # => true
27
+ #
3
28
  module User
4
29
  def self.included(user)
5
30
  user.send :include, LoginField
@@ -10,4 +35,4 @@ module Sinatra
10
35
  end
11
36
  end
12
37
  end
13
- end
38
+ end
@@ -4,11 +4,14 @@ module Sinatra
4
4
  EMAIL_FORMAT = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
5
5
 
6
6
  def validate
7
- # TODO : email requirement should only be done if
8
- # LoginField.attr_name == :email maybe
9
- # then just let users expilicity declare there own
10
- # validation rules
11
- assert_login_using_email :email
7
+ login_field = Sinatra::Security::LoginField.attr_name
8
+
9
+ if login_field == :email
10
+ assert_login_using_email :email
11
+ else
12
+ assert_present(login_field) and assert_unique(login_field)
13
+ end
14
+
12
15
  assert_password :password
13
16
 
14
17
  super
@@ -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.6"
8
+ s.version = "0.2.0"
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-05-18}
12
+ s.date = %q{2010-05-26}
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 = [
@@ -35,6 +35,7 @@ Gem::Specification.new do |s|
35
35
  "lib/sinatra/security/validations.rb",
36
36
  "sinatra-security.gemspec",
37
37
  "test/helper.rb",
38
+ "test/test_login_field_flexibility.rb",
38
39
  "test/test_password.rb",
39
40
  "test/test_sinatra-security.rb",
40
41
  "test/test_sinatra_security_helpers.rb",
@@ -44,10 +45,11 @@ Gem::Specification.new do |s|
44
45
  s.homepage = %q{http://github.com/sinefunc/sinatra-security}
45
46
  s.rdoc_options = ["--charset=UTF-8"]
46
47
  s.require_paths = ["lib"]
47
- s.rubygems_version = %q{1.3.5}
48
+ s.rubygems_version = %q{1.3.6}
48
49
  s.summary = %q{Sinatra authentication extension}
49
50
  s.test_files = [
50
51
  "test/helper.rb",
52
+ "test/test_login_field_flexibility.rb",
51
53
  "test/test_password.rb",
52
54
  "test/test_sinatra-security.rb",
53
55
  "test/test_sinatra_security_helpers.rb",
@@ -0,0 +1,37 @@
1
+ require "helper"
2
+
3
+ class LoginFieldFlexbilityTest < Test::Unit::TestCase
4
+ def setup
5
+ Sinatra::Security::LoginField.attr_name :login
6
+ end
7
+
8
+ def teardown
9
+ Sinatra::Security::LoginField.attr_name :email
10
+ end
11
+
12
+ class DiffUser
13
+ def self.attribute(att)
14
+ end
15
+
16
+ def self.index(att)
17
+ end
18
+
19
+ include Sinatra::Security::User
20
+ end
21
+
22
+ test "find_by_login uses :login" do
23
+ @set = stub("Set", :first => :user)
24
+ DiffUser.expects(:find).with(:login => 'foobar').returns(@set)
25
+
26
+ assert_equal :user, DiffUser.find_by_login('foobar')
27
+ end
28
+
29
+ test "authenticate method uses :login" do
30
+ @user = stub("User", :id => 1001, :crypted_password => '_crypted_')
31
+ @set = stub("Set", :first => @user)
32
+ DiffUser.expects(:find).with(:login => 'foobar').returns(@set)
33
+
34
+
35
+ DiffUser.authenticate('foobar', 'pass')
36
+ end
37
+ end
@@ -21,8 +21,13 @@ class TestPassword < Test::Unit::TestCase
21
21
  include Sinatra::Security
22
22
 
23
23
  describe "#update" do
24
- should "return a 192 character string" do
25
- assert_equal 192, Password::Hashing.update('123456').length
24
+ should "return a 192 character string by default" do
25
+ assert_equal 192, Password::Hashing.encrypt('123456').length
26
+ end
27
+
28
+ should "return a different length if you pass a custom salt" do
29
+ assert_equal 140,
30
+ Password::Hashing.encrypt('123456', '123456789012').length
26
31
  end
27
32
  end
28
33
 
@@ -30,7 +35,14 @@ class TestPassword < Test::Unit::TestCase
30
35
  should "be able to match the original password given" do
31
36
  @password = 'password100'
32
37
 
33
- assert Password::Hashing.check(@password, Password::Hashing.update(@password))
38
+ assert Password::Hashing.check(@password, Password::Hashing.encrypt(@password))
39
+ end
40
+
41
+ should "be able to match original pass given custom salt" do
42
+ password = 'password100'
43
+ crypted = Password::Hashing.encrypt(password, 'customsalt')
44
+
45
+ assert Password::Hashing.check(password, crypted)
34
46
  end
35
47
  end
36
48
 
@@ -5,6 +5,8 @@ class BasicApp < Sinatra::Base
5
5
 
6
6
  register Sinatra::Security
7
7
 
8
+ require_login '/mass'
9
+
8
10
  get '/login' do
9
11
  "<h1>Login Page</h1>"
10
12
  end
@@ -16,6 +18,14 @@ class BasicApp < Sinatra::Base
16
18
  get '/private' do
17
19
  require_login
18
20
  end
21
+
22
+ get '/mass/private1' do
23
+ "Private 1"
24
+ end
25
+
26
+ get '/mass/private2' do
27
+ "Private 2"
28
+ end
19
29
 
20
30
  get '/css/main.css' do
21
31
  require_login
@@ -136,4 +146,23 @@ class TestSinatraSecurity < Test::Unit::TestCase
136
146
  assert_equal '/login', last_response.headers['Location']
137
147
  end
138
148
  end
149
+
150
+ describe "going to /mass/private1" do
151
+ should "redirect to /login" do
152
+ get '/mass/private1'
153
+
154
+ assert_equal 302, last_response.status
155
+ assert_equal '/login', last_response.headers['Location']
156
+ end
157
+ end
158
+
159
+ describe "going to /mass/private2" do
160
+ should "redirect to /login" do
161
+ get '/mass/private2'
162
+
163
+ assert_equal 302, last_response.status
164
+ assert_equal '/login', last_response.headers['Location']
165
+ end
166
+ end
167
+
139
168
  end
@@ -27,6 +27,9 @@ class TestSinatraSecurityHelpers < Test::Unit::TestCase
27
27
 
28
28
  describe "when session[:user] is set to 1" do
29
29
  setup do
30
+ @settings = stub("Settings", :login_user_class => :User)
31
+
32
+ @context.stubs(:settings).returns(@settings)
30
33
  @context.session[:user] = 1
31
34
  end
32
35
 
@@ -83,15 +86,27 @@ class TestSinatraSecurityHelpers < Test::Unit::TestCase
83
86
 
84
87
  describe "#require_login" do
85
88
  context "when logged_in?" do
86
- should "return true" do
89
+ should "not redirect" do
90
+ @settings = stub("Settings", :ignored_by_return_to => /foobared$/,
91
+ :login_url => '/new-login-url')
92
+ @context.stubs(:settings).returns(@settings)
93
+
94
+
95
+ @context.expects(:redirect).never
87
96
  @context.expects(:logged_in?).returns(true)
88
- assert @context.require_login
97
+
98
+ @context.require_login
89
99
  end
90
100
  end
91
101
 
92
102
  context "when not logged_in?" do
93
103
  setup do
94
104
  @context.stubs(:logged_in?).returns(false)
105
+ @settings = stub("Settings", :ignored_by_return_to => /foobared$/,
106
+ :login_url => '/new-login-url')
107
+
108
+ @context.stubs(:settings).returns(@settings)
109
+
95
110
  @context.request = stub("Request", :fullpath => "/some/fullpath/here")
96
111
  end
97
112
 
@@ -102,7 +117,7 @@ class TestSinatraSecurityHelpers < Test::Unit::TestCase
102
117
  end
103
118
 
104
119
  should "redirect to /login" do
105
- @context.expects(:redirect).with('/login')
120
+ @context.expects(:redirect).with('/new-login-url')
106
121
 
107
122
  @context.require_login
108
123
  end
@@ -110,6 +125,15 @@ class TestSinatraSecurityHelpers < Test::Unit::TestCase
110
125
  should "return false" do
111
126
  assert ! @context.require_login
112
127
  end
128
+
129
+ context "when the request matches ignored" do
130
+ should "not store the fullpath in return_to" do
131
+ @context.request = stub("Request", :fullpath => "/some/fullpath/foobared")
132
+ @context.require_login
133
+
134
+ assert_nil @context.session[:return_to]
135
+ end
136
+ end
113
137
  end
114
138
  end
115
139
 
@@ -46,6 +46,64 @@ class UserDefaultValidation < TestFixtures::User
46
46
  end
47
47
 
48
48
  class TestValidations < Test::Unit::TestCase
49
+ context "with a different login field" do
50
+ class User < TestFixtures::User
51
+ include Sinatra::Security::Validations
52
+
53
+ attr_accessor :login
54
+
55
+ def errors
56
+ @errors ||= []
57
+ end
58
+ end
59
+
60
+ setup do
61
+ Sinatra::Security::LoginField.attr_name :login
62
+ end
63
+
64
+ teardown do
65
+ Sinatra::Security::LoginField.attr_name :email
66
+ end
67
+
68
+ context "when a new record" do
69
+ should "assert login present" do
70
+ user = User.new
71
+ user.expects(:new?).returns(true)
72
+ user.validate
73
+
74
+ assert_equal [[:login, :not_present], [:password, :not_present]],
75
+ user.errors
76
+ end
77
+
78
+ should "assert login unique" do
79
+ user = User.new
80
+ user.login = 'login'
81
+ user.expects(:new?).returns(true)
82
+ user.expects(:assert_unique).with(:login)
83
+ user.validate
84
+ end
85
+ end
86
+
87
+ context "when an existing record" do
88
+ should "still assert login present" do
89
+ user = User.new
90
+ user.expects(:new?).returns(false)
91
+ user.validate
92
+
93
+ assert_equal [[:login, :not_present]], user.errors
94
+ end
95
+
96
+ should "assert login unique" do
97
+ user = User.new
98
+ user.login = 'login'
99
+ user.expects(:new?).returns(false)
100
+ user.expects(:assert_unique).with(:login)
101
+ user.validate
102
+ end
103
+ end
104
+
105
+ end
106
+
49
107
  context "without an email" do
50
108
  should "require the email to be present" do
51
109
  user = UserWithEmailValidation.new
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-security
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Cyril David
@@ -9,7 +14,7 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-05-18 00:00:00 +08:00
17
+ date: 2010-05-26 00:00:00 +08:00
13
18
  default_executable:
14
19
  dependencies: []
15
20
 
@@ -41,6 +46,7 @@ files:
41
46
  - lib/sinatra/security/validations.rb
42
47
  - sinatra-security.gemspec
43
48
  - test/helper.rb
49
+ - test/test_login_field_flexibility.rb
44
50
  - test/test_password.rb
45
51
  - test/test_sinatra-security.rb
46
52
  - test/test_sinatra_security_helpers.rb
@@ -59,23 +65,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
59
65
  requirements:
60
66
  - - ">="
61
67
  - !ruby/object:Gem::Version
68
+ segments:
69
+ - 0
62
70
  version: "0"
63
- version:
64
71
  required_rubygems_version: !ruby/object:Gem::Requirement
65
72
  requirements:
66
73
  - - ">="
67
74
  - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
68
77
  version: "0"
69
- version:
70
78
  requirements: []
71
79
 
72
80
  rubyforge_project:
73
- rubygems_version: 1.3.5
81
+ rubygems_version: 1.3.6
74
82
  signing_key:
75
83
  specification_version: 3
76
84
  summary: Sinatra authentication extension
77
85
  test_files:
78
86
  - test/helper.rb
87
+ - test/test_login_field_flexibility.rb
79
88
  - test/test_password.rb
80
89
  - test/test_sinatra-security.rb
81
90
  - test/test_sinatra_security_helpers.rb