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.

Files changed (80) hide show
  1. data/CHANGELOG.rdoc +21 -2
  2. data/README.rdoc +40 -54
  3. data/Rakefile +1 -1
  4. data/TODO +1 -3
  5. data/app/controllers/confirmations_controller.rb +9 -20
  6. data/app/controllers/passwords_controller.rb +9 -20
  7. data/app/controllers/sessions_controller.rb +9 -9
  8. data/app/controllers/unlocks_controller.rb +22 -0
  9. data/app/models/devise_mailer.rb +6 -1
  10. data/app/views/confirmations/new.html.erb +1 -5
  11. data/app/views/devise_mailer/unlock_instructions.html.erb +7 -0
  12. data/app/views/passwords/edit.html.erb +1 -5
  13. data/app/views/passwords/new.html.erb +1 -5
  14. data/app/views/sessions/new.html.erb +1 -7
  15. data/app/views/shared/_devise_links.erb +15 -0
  16. data/app/views/unlocks/new.html.erb +12 -0
  17. data/generators/devise/templates/migration.rb +2 -0
  18. data/generators/devise/templates/model.rb +4 -1
  19. data/generators/devise_install/templates/devise.rb +20 -10
  20. data/lib/devise.rb +62 -18
  21. data/lib/devise/controllers/common.rb +24 -0
  22. data/lib/devise/controllers/helpers.rb +160 -80
  23. data/lib/devise/controllers/internal_helpers.rb +120 -0
  24. data/lib/devise/controllers/url_helpers.rb +2 -10
  25. data/lib/devise/encryptors/bcrypt.rb +2 -2
  26. data/lib/devise/hooks/activatable.rb +1 -4
  27. data/lib/devise/hooks/rememberable.rb +30 -0
  28. data/lib/devise/hooks/timeoutable.rb +4 -2
  29. data/lib/devise/locales/en.yml +9 -2
  30. data/lib/devise/mapping.rb +15 -11
  31. data/lib/devise/models.rb +16 -35
  32. data/lib/devise/models/activatable.rb +1 -1
  33. data/lib/devise/models/authenticatable.rb +1 -9
  34. data/lib/devise/models/confirmable.rb +6 -2
  35. data/lib/devise/models/lockable.rb +142 -0
  36. data/lib/devise/models/rememberable.rb +19 -2
  37. data/lib/devise/models/timeoutable.rb +1 -2
  38. data/lib/devise/orm/active_record.rb +2 -0
  39. data/lib/devise/orm/data_mapper.rb +1 -1
  40. data/lib/devise/orm/mongo_mapper.rb +12 -1
  41. data/lib/devise/rails/routes.rb +5 -1
  42. data/lib/devise/rails/warden_compat.rb +13 -13
  43. data/lib/devise/schema.rb +7 -0
  44. data/lib/devise/strategies/authenticatable.rb +1 -3
  45. data/lib/devise/strategies/base.rb +1 -1
  46. data/lib/devise/strategies/rememberable.rb +37 -0
  47. data/lib/devise/test_helpers.rb +1 -1
  48. data/lib/devise/version.rb +1 -1
  49. data/test/controllers/helpers_test.rb +155 -33
  50. data/test/controllers/internal_helpers_test.rb +55 -0
  51. data/test/devise_test.rb +24 -3
  52. data/test/encryptors_test.rb +3 -1
  53. data/test/integration/lockable_test.rb +83 -0
  54. data/test/integration/rememberable_test.rb +1 -1
  55. data/test/mailers/unlock_instructions_test.rb +62 -0
  56. data/test/models/authenticatable_test.rb +0 -23
  57. data/test/models/lockable_test.rb +202 -0
  58. data/test/models/timeoutable_test.rb +7 -7
  59. data/test/models/validatable_test.rb +2 -2
  60. data/test/models_test.rb +9 -76
  61. data/test/orm/active_record.rb +1 -0
  62. data/test/orm/mongo_mapper.rb +0 -1
  63. data/test/rails_app/app/active_record/admin.rb +1 -1
  64. data/test/rails_app/app/active_record/user.rb +2 -1
  65. data/test/rails_app/app/mongo_mapper/admin.rb +1 -1
  66. data/test/rails_app/app/mongo_mapper/user.rb +2 -1
  67. data/test/rails_app/config/initializers/devise.rb +13 -10
  68. data/test/rails_app/config/routes.rb +5 -3
  69. data/test/routes_test.rb +5 -0
  70. data/test/support/integration_tests_helper.rb +1 -0
  71. metadata +16 -12
  72. data/lib/devise/controllers/filters.rb +0 -186
  73. data/lib/devise/models/cookie_serializer.rb +0 -21
  74. data/lib/devise/models/session_serializer.rb +0 -19
  75. data/lib/devise/serializers/base.rb +0 -23
  76. data/lib/devise/serializers/cookie.rb +0 -43
  77. data/lib/devise/serializers/session.rb +0 -22
  78. data/test/controllers/filters_test.rb +0 -177
  79. data/test/rails_app/app/active_record/account.rb +0 -7
  80. data/test/rails_app/app/mongo_mapper/account.rb +0 -9
@@ -1,4 +1,5 @@
1
- require 'devise/models/cookie_serializer'
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 CookieSerializer
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 timeout?(last_access)
19
+ def timedout?(last_access)
21
20
  last_access && last_access <= self.class.timeout_in.ago
22
21
  end
23
22
 
@@ -7,6 +7,8 @@ module Devise
7
7
  # t.confirmable
8
8
  # t.recoverable
9
9
  # t.rememberable
10
+ # t.trackable
11
+ # t.lockable
10
12
  # t.timestamps
11
13
  # end
12
14
  #
@@ -12,7 +12,7 @@ module Devise
12
12
  end
13
13
 
14
14
  def self.included_modules_hook(klass, modules)
15
- klass.send :extend, self
15
+ klass.send :extend, self
16
16
  klass.send :include, InstanceMethods
17
17
 
18
18
  yield
@@ -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, self
15
+ klass.send :extend, self
16
+ klass.send :include, InstanceMethods
6
17
  yield
7
18
 
8
19
  modules.each do |mod|
@@ -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::Filters
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
- return @request if @request
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
- return @response if @response
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
@@ -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 < Warden::Strategies::Base
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
- module Base
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)
@@ -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
- session["warden.user.#{scope}.key"] = resource.class.serialize_into_session(resource)
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.
@@ -1,3 +1,3 @@
1
1
  module Devise
2
- VERSION = "0.8.2".freeze
2
+ VERSION = "0.9.0".freeze
3
3
  end
@@ -1,55 +1,177 @@
1
1
  require 'test/test_helper'
2
+ require 'ostruct'
2
3
 
3
- class MyController < ApplicationController
4
- include Devise::Controllers::Helpers
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 HelpersTest < ActionController::TestCase
8
- tests MyController
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 'get resource name from request path' do
11
- @request.path = '/users/session'
12
- assert_equal :user, @controller.resource_name
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 'get resource name from specific request path' do
16
- @request.path = '/admin_area/session'
17
- assert_equal :admin, @controller.resource_name
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 'get resource class from request path' do
21
- @request.path = '/users/session'
22
- assert_equal User, @controller.resource_class
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 'get resource instance variable from request path' do
26
- @request.path = '/admin_area/session'
27
- @controller.instance_variable_set(:@admin, admin = Admin.new)
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 'set resource instance variable from request path' do
32
- @request.path = '/admin_area/session'
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
- admin = @controller.send(:resource_class).new
35
- @controller.send(:resource=, admin)
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
- assert_equal admin, @controller.send(:resource)
38
- assert_equal admin, @controller.instance_variable_get(:@admin)
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 'resources methods are not controller actions' do
42
- assert @controller.class.action_methods.empty?
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 'require no authentication tests current mapping' do
46
- @controller.expects(:resource_name).returns(:user).twice
47
- @mock_warden.expects(:authenticated?).with(:user).returns(true)
48
- @controller.expects(:redirect_to).with(root_path)
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 'is a devise controller' do
53
- assert @controller.devise_controller?
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