devise 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of devise might be problematic. Click here for more details.
- data/CHANGELOG.rdoc +21 -2
- data/README.rdoc +40 -54
- data/Rakefile +1 -1
- data/TODO +1 -3
- data/app/controllers/confirmations_controller.rb +9 -20
- data/app/controllers/passwords_controller.rb +9 -20
- data/app/controllers/sessions_controller.rb +9 -9
- data/app/controllers/unlocks_controller.rb +22 -0
- data/app/models/devise_mailer.rb +6 -1
- data/app/views/confirmations/new.html.erb +1 -5
- data/app/views/devise_mailer/unlock_instructions.html.erb +7 -0
- data/app/views/passwords/edit.html.erb +1 -5
- data/app/views/passwords/new.html.erb +1 -5
- data/app/views/sessions/new.html.erb +1 -7
- data/app/views/shared/_devise_links.erb +15 -0
- data/app/views/unlocks/new.html.erb +12 -0
- data/generators/devise/templates/migration.rb +2 -0
- data/generators/devise/templates/model.rb +4 -1
- data/generators/devise_install/templates/devise.rb +20 -10
- data/lib/devise.rb +62 -18
- data/lib/devise/controllers/common.rb +24 -0
- data/lib/devise/controllers/helpers.rb +160 -80
- data/lib/devise/controllers/internal_helpers.rb +120 -0
- data/lib/devise/controllers/url_helpers.rb +2 -10
- data/lib/devise/encryptors/bcrypt.rb +2 -2
- data/lib/devise/hooks/activatable.rb +1 -4
- data/lib/devise/hooks/rememberable.rb +30 -0
- data/lib/devise/hooks/timeoutable.rb +4 -2
- data/lib/devise/locales/en.yml +9 -2
- data/lib/devise/mapping.rb +15 -11
- data/lib/devise/models.rb +16 -35
- data/lib/devise/models/activatable.rb +1 -1
- data/lib/devise/models/authenticatable.rb +1 -9
- data/lib/devise/models/confirmable.rb +6 -2
- data/lib/devise/models/lockable.rb +142 -0
- data/lib/devise/models/rememberable.rb +19 -2
- data/lib/devise/models/timeoutable.rb +1 -2
- data/lib/devise/orm/active_record.rb +2 -0
- data/lib/devise/orm/data_mapper.rb +1 -1
- data/lib/devise/orm/mongo_mapper.rb +12 -1
- data/lib/devise/rails/routes.rb +5 -1
- data/lib/devise/rails/warden_compat.rb +13 -13
- data/lib/devise/schema.rb +7 -0
- data/lib/devise/strategies/authenticatable.rb +1 -3
- data/lib/devise/strategies/base.rb +1 -1
- data/lib/devise/strategies/rememberable.rb +37 -0
- data/lib/devise/test_helpers.rb +1 -1
- data/lib/devise/version.rb +1 -1
- data/test/controllers/helpers_test.rb +155 -33
- data/test/controllers/internal_helpers_test.rb +55 -0
- data/test/devise_test.rb +24 -3
- data/test/encryptors_test.rb +3 -1
- data/test/integration/lockable_test.rb +83 -0
- data/test/integration/rememberable_test.rb +1 -1
- data/test/mailers/unlock_instructions_test.rb +62 -0
- data/test/models/authenticatable_test.rb +0 -23
- data/test/models/lockable_test.rb +202 -0
- data/test/models/timeoutable_test.rb +7 -7
- data/test/models/validatable_test.rb +2 -2
- data/test/models_test.rb +9 -76
- data/test/orm/active_record.rb +1 -0
- data/test/orm/mongo_mapper.rb +0 -1
- data/test/rails_app/app/active_record/admin.rb +1 -1
- data/test/rails_app/app/active_record/user.rb +2 -1
- data/test/rails_app/app/mongo_mapper/admin.rb +1 -1
- data/test/rails_app/app/mongo_mapper/user.rb +2 -1
- data/test/rails_app/config/initializers/devise.rb +13 -10
- data/test/rails_app/config/routes.rb +5 -3
- data/test/routes_test.rb +5 -0
- data/test/support/integration_tests_helper.rb +1 -0
- metadata +16 -12
- data/lib/devise/controllers/filters.rb +0 -186
- data/lib/devise/models/cookie_serializer.rb +0 -21
- data/lib/devise/models/session_serializer.rb +0 -19
- data/lib/devise/serializers/base.rb +0 -23
- data/lib/devise/serializers/cookie.rb +0 -43
- data/lib/devise/serializers/session.rb +0 -22
- data/test/controllers/filters_test.rb +0 -177
- data/test/rails_app/app/active_record/account.rb +0 -7
- data/test/rails_app/app/mongo_mapper/account.rb +0 -9
@@ -1,4 +1,5 @@
|
|
1
|
-
require 'devise/
|
1
|
+
require 'devise/strategies/rememberable'
|
2
|
+
require 'devise/hooks/rememberable'
|
2
3
|
|
3
4
|
module Devise
|
4
5
|
module Models
|
@@ -32,7 +33,7 @@ module Devise
|
|
32
33
|
|
33
34
|
def self.included(base)
|
34
35
|
base.class_eval do
|
35
|
-
extend
|
36
|
+
extend ClassMethods
|
36
37
|
|
37
38
|
# Remember me option available in after_authentication hook.
|
38
39
|
attr_accessor :remember_me
|
@@ -70,6 +71,22 @@ module Devise
|
|
70
71
|
def remember_expires_at
|
71
72
|
remember_created_at + self.class.remember_for
|
72
73
|
end
|
74
|
+
|
75
|
+
module ClassMethods
|
76
|
+
# Create the cookie key using the record id and remember_token
|
77
|
+
def serialize_into_cookie(record)
|
78
|
+
"#{record.id}::#{record.remember_token}"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Recreate the user based on the stored cookie
|
82
|
+
def serialize_from_cookie(cookie)
|
83
|
+
record_id, record_token = cookie.split('::')
|
84
|
+
record = find(:first, :conditions => { :id => record_id }) if record_id
|
85
|
+
record if record.try(:valid_remember_token?, record_token)
|
86
|
+
end
|
87
|
+
|
88
|
+
Devise::Models.config(self, :remember_for)
|
89
|
+
end
|
73
90
|
end
|
74
91
|
end
|
75
92
|
end
|
@@ -2,7 +2,6 @@ require 'devise/hooks/timeoutable'
|
|
2
2
|
|
3
3
|
module Devise
|
4
4
|
module Models
|
5
|
-
|
6
5
|
# Timeoutable takes care of veryfing whether a user session has already
|
7
6
|
# expired or not. When a session expires after the configured time, the user
|
8
7
|
# will be asked for credentials again, it means, he/she will be redirected
|
@@ -17,7 +16,7 @@ module Devise
|
|
17
16
|
end
|
18
17
|
|
19
18
|
# Checks whether the user session has expired based on configured time.
|
20
|
-
def
|
19
|
+
def timedout?(last_access)
|
21
20
|
last_access && last_access <= self.class.timeout_in.ago
|
22
21
|
end
|
23
22
|
|
@@ -1,8 +1,19 @@
|
|
1
1
|
module Devise
|
2
2
|
module Orm
|
3
3
|
module MongoMapper
|
4
|
+
module InstanceMethods
|
5
|
+
def save(options={})
|
6
|
+
if options == false
|
7
|
+
super(:validate => false)
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
4
14
|
def self.included_modules_hook(klass, modules)
|
5
|
-
klass.send :extend,
|
15
|
+
klass.send :extend, self
|
16
|
+
klass.send :include, InstanceMethods
|
6
17
|
yield
|
7
18
|
|
8
19
|
modules.each do |mod|
|
data/lib/devise/rails/routes.rb
CHANGED
@@ -8,7 +8,7 @@ module ActionController::Routing
|
|
8
8
|
load_routes_without_devise!
|
9
9
|
return if Devise.mappings.empty?
|
10
10
|
|
11
|
-
ActionController::Base.send :include, Devise::Controllers::
|
11
|
+
ActionController::Base.send :include, Devise::Controllers::Helpers
|
12
12
|
ActionController::Base.send :include, Devise::Controllers::UrlHelpers
|
13
13
|
|
14
14
|
ActionView::Base.send :include, Devise::Controllers::UrlHelpers
|
@@ -113,6 +113,10 @@ module ActionController::Routing
|
|
113
113
|
routes.resource :confirmation, :only => [:new, :create, :show], :as => mapping.path_names[:confirmation]
|
114
114
|
end
|
115
115
|
|
116
|
+
def lockable(routes, mapping)
|
117
|
+
routes.resource :unlock, :only => [:new, :create, :show], :as => mapping.path_names[:unlock]
|
118
|
+
end
|
119
|
+
|
116
120
|
end
|
117
121
|
end
|
118
122
|
end
|
@@ -1,12 +1,6 @@
|
|
1
|
-
# Taken from RailsWarden, thanks to Hassox. http://github.com/hassox/rails_warden
|
2
1
|
module Warden::Mixins::Common
|
3
2
|
def request
|
4
|
-
|
5
|
-
if env['action_controller.rescue.request']
|
6
|
-
@request = env['action_controller.rescue.request']
|
7
|
-
else
|
8
|
-
Rack::Request.new(env)
|
9
|
-
end
|
3
|
+
@request ||= env['action_controller.rescue.request']
|
10
4
|
end
|
11
5
|
|
12
6
|
def reset_session!
|
@@ -15,11 +9,17 @@ module Warden::Mixins::Common
|
|
15
9
|
end
|
16
10
|
|
17
11
|
def response
|
18
|
-
|
19
|
-
if env['action_controller.rescue.response']
|
20
|
-
@response = env['action_controller.rescue.response']
|
21
|
-
else
|
22
|
-
Rack::Response.new(env)
|
23
|
-
end
|
12
|
+
@response ||= env['action_controller.rescue.response']
|
24
13
|
end
|
25
14
|
end
|
15
|
+
|
16
|
+
class Warden::SessionSerializer
|
17
|
+
def serialize(record)
|
18
|
+
[record.class, record.id]
|
19
|
+
end
|
20
|
+
|
21
|
+
def deserialize(keys)
|
22
|
+
klass, id = keys
|
23
|
+
klass.find(:first, :conditions => { :id => id })
|
24
|
+
end
|
25
|
+
end
|
data/lib/devise/schema.rb
CHANGED
@@ -45,6 +45,13 @@ module Devise
|
|
45
45
|
apply_schema :last_sign_in_ip, String
|
46
46
|
end
|
47
47
|
|
48
|
+
# Creates failed_attempts, unlock_token and locked_at
|
49
|
+
def lockable
|
50
|
+
apply_schema :failed_attempts, Integer, :default => 0
|
51
|
+
apply_schema :unlock_token, String, :limit => 20
|
52
|
+
apply_schema :locked_at, DateTime
|
53
|
+
end
|
54
|
+
|
48
55
|
# Overwrite with specific modification to create your own schema.
|
49
56
|
def apply_schema(name, type, options={})
|
50
57
|
raise NotImplementedError
|
@@ -4,9 +4,7 @@ module Devise
|
|
4
4
|
module Strategies
|
5
5
|
# Default strategy for signing in a user, based on his email and password.
|
6
6
|
# Redirects to sign_in page if it's not authenticated
|
7
|
-
class Authenticatable <
|
8
|
-
include Devise::Strategies::Base
|
9
|
-
|
7
|
+
class Authenticatable < Base
|
10
8
|
def valid?
|
11
9
|
super && params[scope] && params[scope][:password].present?
|
12
10
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Devise
|
2
2
|
module Strategies
|
3
3
|
# Base strategy for Devise. Responsible for verifying correct scope and mapping.
|
4
|
-
|
4
|
+
class Base < ::Warden::Strategies::Base
|
5
5
|
# Validate strategy. By default will raise an error if no scope or an
|
6
6
|
# invalid mapping is found.
|
7
7
|
def valid?
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'devise/strategies/base'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Strategies
|
5
|
+
# Remember the user through the remember token. This strategy is responsible
|
6
|
+
# to verify whether there is a cookie with the remember token, and to
|
7
|
+
# recreate the user from this cookie if it exists. Must be called *before*
|
8
|
+
# authenticatable.
|
9
|
+
class Rememberable < Devise::Strategies::Base
|
10
|
+
|
11
|
+
# A valid strategy for rememberable needs a remember token in the cookies.
|
12
|
+
def valid?
|
13
|
+
super && remember_me_cookie.present?
|
14
|
+
end
|
15
|
+
|
16
|
+
# To authenticate a user we deserialize the cookie and attempt finding
|
17
|
+
# the record in the database. If the attempt fails, we pass to another
|
18
|
+
# strategy handle the authentication.
|
19
|
+
def authenticate!
|
20
|
+
if resource = mapping.to.serialize_from_cookie(remember_me_cookie)
|
21
|
+
success!(resource)
|
22
|
+
else
|
23
|
+
pass
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Accessor for remember cookie
|
30
|
+
def remember_me_cookie
|
31
|
+
@remember_me_cookie ||= request.cookies["remember_#{mapping.name}_token"]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Warden::Strategies.add(:rememberable, Devise::Strategies::Rememberable)
|
data/lib/devise/test_helpers.rb
CHANGED
@@ -66,7 +66,7 @@ module Devise
|
|
66
66
|
def sign_in(resource_or_scope, resource=nil)
|
67
67
|
scope ||= Devise::Mapping.find_scope!(resource_or_scope)
|
68
68
|
resource ||= resource_or_scope
|
69
|
-
|
69
|
+
warden.session_serializer.store(resource, scope)
|
70
70
|
end
|
71
71
|
|
72
72
|
# Sign out a given resource or scope by calling logout on Warden.
|
data/lib/devise/version.rb
CHANGED
@@ -1,55 +1,177 @@
|
|
1
1
|
require 'test/test_helper'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
|
-
class
|
4
|
-
|
4
|
+
class MockController < ApplicationController
|
5
|
+
attr_accessor :env
|
6
|
+
|
7
|
+
def request
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def path
|
12
|
+
''
|
13
|
+
end
|
5
14
|
end
|
6
15
|
|
7
|
-
class
|
8
|
-
tests
|
16
|
+
class ControllerAuthenticableTest < ActionController::TestCase
|
17
|
+
tests MockController
|
18
|
+
|
19
|
+
def setup
|
20
|
+
@controller = MockController.new
|
21
|
+
@mock_warden = OpenStruct.new
|
22
|
+
@controller.env = { 'warden' => @mock_warden }
|
23
|
+
@controller.session = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
test 'setup warden' do
|
27
|
+
assert_not_nil @controller.warden
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'provide access to warden instance' do
|
31
|
+
assert_equal @controller.warden, @controller.env['warden']
|
32
|
+
end
|
33
|
+
|
34
|
+
test 'proxy signed_in? to authenticated' do
|
35
|
+
@mock_warden.expects(:authenticate?).with(:scope => :my_scope)
|
36
|
+
@controller.signed_in?(:my_scope)
|
37
|
+
end
|
38
|
+
|
39
|
+
test 'proxy current_admin to authenticate with admin scope' do
|
40
|
+
@mock_warden.expects(:authenticate).with(:scope => :admin)
|
41
|
+
@controller.current_admin
|
42
|
+
end
|
9
43
|
|
10
|
-
test '
|
11
|
-
@
|
12
|
-
|
44
|
+
test 'proxy current_user to authenticate with user scope' do
|
45
|
+
@mock_warden.expects(:authenticate).with(:scope => :user)
|
46
|
+
@controller.current_user
|
13
47
|
end
|
14
48
|
|
15
|
-
test '
|
16
|
-
@
|
17
|
-
|
49
|
+
test 'proxy user_authenticate! to authenticate with user scope' do
|
50
|
+
@mock_warden.expects(:authenticate!).with(:scope => :user)
|
51
|
+
@controller.authenticate_user!
|
18
52
|
end
|
19
53
|
|
20
|
-
test '
|
21
|
-
@
|
22
|
-
|
54
|
+
test 'proxy admin_authenticate! to authenticate with admin scope' do
|
55
|
+
@mock_warden.expects(:authenticate!).with(:scope => :admin)
|
56
|
+
@controller.authenticate_admin!
|
23
57
|
end
|
24
58
|
|
25
|
-
test '
|
26
|
-
@
|
27
|
-
@controller.
|
28
|
-
assert_equal admin, @controller.resource
|
59
|
+
test 'proxy user_signed_in? to authenticate? with user scope' do
|
60
|
+
@mock_warden.expects(:authenticate?).with(:scope => :user)
|
61
|
+
@controller.user_signed_in?
|
29
62
|
end
|
30
63
|
|
31
|
-
test '
|
32
|
-
@
|
64
|
+
test 'proxy admin_signed_in? to authenticate? with admin scope' do
|
65
|
+
@mock_warden.expects(:authenticate?).with(:scope => :admin)
|
66
|
+
@controller.admin_signed_in?
|
67
|
+
end
|
68
|
+
|
69
|
+
test 'proxy user_session to session scope in warden' do
|
70
|
+
@mock_warden.expects(:authenticate).with(:scope => :user).returns(true)
|
71
|
+
@mock_warden.expects(:session).with(:user).returns({})
|
72
|
+
@controller.user_session
|
73
|
+
end
|
33
74
|
|
34
|
-
|
35
|
-
@
|
75
|
+
test 'proxy admin_session to session scope in warden' do
|
76
|
+
@mock_warden.expects(:authenticate).with(:scope => :admin).returns(true)
|
77
|
+
@mock_warden.expects(:session).with(:admin).returns({})
|
78
|
+
@controller.admin_session
|
79
|
+
end
|
80
|
+
|
81
|
+
test 'sign in proxy to set_user on warden' do
|
82
|
+
user = User.new
|
83
|
+
@mock_warden.expects(:set_user).with(user, :scope => :user).returns(true)
|
84
|
+
@controller.sign_in(:user, user)
|
85
|
+
end
|
86
|
+
|
87
|
+
test 'sign in accepts a resource as argument' do
|
88
|
+
user = User.new
|
89
|
+
@mock_warden.expects(:set_user).with(user, :scope => :user).returns(true)
|
90
|
+
@controller.sign_in(user)
|
91
|
+
end
|
36
92
|
|
37
|
-
|
38
|
-
|
93
|
+
test 'sign out proxy to logout on warden' do
|
94
|
+
@mock_warden.expects(:user).with(:user).returns(true)
|
95
|
+
@mock_warden.expects(:logout).with(:user).returns(true)
|
96
|
+
@controller.sign_out(:user)
|
39
97
|
end
|
40
98
|
|
41
|
-
test '
|
42
|
-
|
99
|
+
test 'sign out accepts a resource as argument' do
|
100
|
+
@mock_warden.expects(:user).with(:user).returns(true)
|
101
|
+
@mock_warden.expects(:logout).with(:user).returns(true)
|
102
|
+
@controller.sign_out(User.new)
|
43
103
|
end
|
44
104
|
|
45
|
-
test '
|
46
|
-
@controller.
|
47
|
-
@
|
48
|
-
@controller.
|
49
|
-
@controller.send :require_no_authentication
|
105
|
+
test 'stored location for returns the location for a given scope' do
|
106
|
+
assert_nil @controller.stored_location_for(:user)
|
107
|
+
@controller.session[:"user.return_to"] = "/foo.bar"
|
108
|
+
assert_equal "/foo.bar", @controller.stored_location_for(:user)
|
50
109
|
end
|
51
|
-
|
52
|
-
test '
|
53
|
-
|
110
|
+
|
111
|
+
test 'stored location for accepts a resource as argument' do
|
112
|
+
assert_nil @controller.stored_location_for(:user)
|
113
|
+
@controller.session[:"user.return_to"] = "/foo.bar"
|
114
|
+
assert_equal "/foo.bar", @controller.stored_location_for(User.new)
|
115
|
+
end
|
116
|
+
|
117
|
+
test 'stored location cleans information after reading' do
|
118
|
+
@controller.session[:"user.return_to"] = "/foo.bar"
|
119
|
+
assert_equal "/foo.bar", @controller.stored_location_for(:user)
|
120
|
+
assert_nil @controller.session[:"user.return_to"]
|
121
|
+
end
|
122
|
+
|
123
|
+
test 'after sign in path defaults to root path if none by was specified for the given scope' do
|
124
|
+
assert_equal root_path, @controller.after_sign_in_path_for(:user)
|
125
|
+
end
|
126
|
+
|
127
|
+
test 'after sign in path defaults to the scoped root path' do
|
128
|
+
assert_equal admin_root_path, @controller.after_sign_in_path_for(:admin)
|
129
|
+
end
|
130
|
+
|
131
|
+
test 'after sign out path defaults to the root path' do
|
132
|
+
assert_equal root_path, @controller.after_sign_out_path_for(:admin)
|
133
|
+
assert_equal root_path, @controller.after_sign_out_path_for(:user)
|
134
|
+
end
|
135
|
+
|
136
|
+
test 'sign in and redirect uses the stored location' do
|
137
|
+
user = User.new
|
138
|
+
@controller.session[:"user.return_to"] = "/foo.bar"
|
139
|
+
@mock_warden.expects(:set_user).with(user, :scope => :user).returns(true)
|
140
|
+
@controller.expects(:redirect_to).with("/foo.bar")
|
141
|
+
@controller.sign_in_and_redirect(user)
|
142
|
+
end
|
143
|
+
|
144
|
+
test 'sign in and redirect uses the configured after sign in path' do
|
145
|
+
admin = Admin.new
|
146
|
+
@mock_warden.expects(:set_user).with(admin, :scope => :admin).returns(true)
|
147
|
+
@controller.expects(:redirect_to).with(admin_root_path)
|
148
|
+
@controller.sign_in_and_redirect(admin)
|
149
|
+
end
|
150
|
+
|
151
|
+
test 'only redirect if skip is given' do
|
152
|
+
admin = Admin.new
|
153
|
+
@controller.expects(:redirect_to).with(admin_root_path)
|
154
|
+
@controller.sign_in_and_redirect(:admin, admin, true)
|
155
|
+
end
|
156
|
+
|
157
|
+
test 'sign out and redirect uses the configured after sign out path' do
|
158
|
+
@mock_warden.expects(:user).with(:admin).returns(true)
|
159
|
+
@mock_warden.expects(:logout).with(:admin).returns(true)
|
160
|
+
@controller.expects(:redirect_to).with(admin_root_path)
|
161
|
+
@controller.instance_eval "def after_sign_out_path_for(resource); admin_root_path; end"
|
162
|
+
@controller.sign_out_and_redirect(:admin)
|
163
|
+
end
|
164
|
+
|
165
|
+
test 'is not a devise controller' do
|
166
|
+
assert_not @controller.devise_controller?
|
167
|
+
end
|
168
|
+
|
169
|
+
test 'default url options are retrieved from devise' do
|
170
|
+
begin
|
171
|
+
Devise.default_url_options {{ :locale => I18n.locale }}
|
172
|
+
assert_equal({ :locale => :en }, @controller.send(:default_url_options))
|
173
|
+
ensure
|
174
|
+
Devise.default_url_options {{ }}
|
175
|
+
end
|
54
176
|
end
|
55
177
|
end
|