devise 3.1.2 → 3.2.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 (62) hide show
  1. data/CHANGELOG.md +111 -99
  2. data/Gemfile.lock +1 -1
  3. data/README.md +2 -2
  4. data/app/controllers/devise/confirmations_controller.rb +2 -9
  5. data/app/controllers/devise/passwords_controller.rb +1 -1
  6. data/app/controllers/devise/registrations_controller.rb +6 -6
  7. data/app/controllers/devise/sessions_controller.rb +3 -3
  8. data/app/controllers/devise/unlocks_controller.rb +1 -1
  9. data/app/controllers/devise_controller.rb +6 -2
  10. data/app/mailers/devise/mailer.rb +15 -13
  11. data/config/locales/en.yml +1 -2
  12. data/gemfiles/Gemfile.rails-3.2.x.lock +1 -1
  13. data/lib/devise.rb +23 -12
  14. data/lib/devise/controllers/helpers.rb +16 -84
  15. data/lib/devise/controllers/rememberable.rb +2 -12
  16. data/lib/devise/controllers/sign_in_out.rb +103 -0
  17. data/lib/devise/failure_app.rb +11 -2
  18. data/lib/devise/hooks/forgetable.rb +1 -1
  19. data/lib/devise/hooks/proxy.rb +21 -0
  20. data/lib/devise/hooks/rememberable.rb +1 -1
  21. data/lib/devise/hooks/timeoutable.rb +4 -1
  22. data/lib/devise/models.rb +0 -5
  23. data/lib/devise/models/authenticatable.rb +8 -9
  24. data/lib/devise/models/confirmable.rb +0 -4
  25. data/lib/devise/models/database_authenticatable.rb +17 -7
  26. data/lib/devise/models/lockable.rb +6 -4
  27. data/lib/devise/models/recoverable.rb +0 -8
  28. data/lib/devise/modules.rb +0 -1
  29. data/lib/devise/rails/routes.rb +29 -15
  30. data/lib/devise/strategies/database_authenticatable.rb +3 -6
  31. data/lib/devise/test_helpers.rb +1 -0
  32. data/lib/devise/version.rb +1 -1
  33. data/lib/generators/mongoid/devise_generator.rb +0 -3
  34. data/lib/generators/templates/devise.rb +6 -10
  35. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  36. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  37. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  38. data/test/controllers/internal_helpers_test.rb +2 -2
  39. data/test/controllers/sessions_controller_test.rb +1 -1
  40. data/test/devise_test.rb +12 -1
  41. data/test/failure_app_test.rb +11 -0
  42. data/test/integration/confirmable_test.rb +0 -12
  43. data/test/integration/http_authenticatable_test.rb +0 -10
  44. data/test/integration/recoverable_test.rb +2 -2
  45. data/test/integration/rememberable_test.rb +3 -3
  46. data/test/integration/timeoutable_test.rb +28 -0
  47. data/test/mapping_test.rb +2 -2
  48. data/test/models/confirmable_test.rb +0 -9
  49. data/test/models/database_authenticatable_test.rb +19 -1
  50. data/test/models/lockable_test.rb +16 -10
  51. data/test/models/recoverable_test.rb +0 -10
  52. data/test/rails_app/app/mongoid/user.rb +0 -3
  53. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -3
  54. data/test/rails_app/db/schema.rb +0 -1
  55. data/test/rails_app/lib/shared_user.rb +1 -1
  56. data/test/support/locale/en.yml +4 -0
  57. data/test/test_helpers_test.rb +22 -0
  58. metadata +4 -8
  59. data/lib/devise/models/token_authenticatable.rb +0 -92
  60. data/lib/devise/strategies/token_authenticatable.rb +0 -91
  61. data/test/integration/token_authenticatable_test.rb +0 -205
  62. data/test/models/token_authenticatable_test.rb +0 -55
@@ -0,0 +1,103 @@
1
+ module Devise
2
+ module Controllers
3
+ # Provide sign in and sign out functionality.
4
+ # Included by default in all controllers.
5
+ module SignInOut
6
+ # Return true if the given scope is signed in session. If no scope given, return
7
+ # true if any scope is signed in. Does not run authentication hooks.
8
+ def signed_in?(scope=nil)
9
+ [ scope || Devise.mappings.keys ].flatten.any? do |_scope|
10
+ warden.authenticate?(:scope => _scope)
11
+ end
12
+ end
13
+
14
+ # Sign in a user that already was authenticated. This helper is useful for logging
15
+ # users in after sign up.
16
+ #
17
+ # All options given to sign_in is passed forward to the set_user method in warden.
18
+ # The only exception is the :bypass option, which bypass warden callbacks and stores
19
+ # the user straight in session. This option is useful in cases the user is already
20
+ # signed in, but we want to refresh the credentials in session.
21
+ #
22
+ # Examples:
23
+ #
24
+ # sign_in :user, @user # sign_in(scope, resource)
25
+ # sign_in @user # sign_in(resource)
26
+ # sign_in @user, :event => :authentication # sign_in(resource, options)
27
+ # sign_in @user, :store => false # sign_in(resource, options)
28
+ # sign_in @user, :bypass => true # sign_in(resource, options)
29
+ #
30
+ def sign_in(resource_or_scope, *args)
31
+ options = args.extract_options!
32
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
33
+ resource = args.last || resource_or_scope
34
+
35
+ expire_data_after_sign_in!
36
+
37
+ if options[:bypass]
38
+ warden.session_serializer.store(resource, scope)
39
+ elsif warden.user(scope) == resource && !options.delete(:force)
40
+ # Do nothing. User already signed in and we are not forcing it.
41
+ true
42
+ else
43
+ warden.set_user(resource, options.merge!(:scope => scope))
44
+ end
45
+ end
46
+
47
+ # Sign out a given user or scope. This helper is useful for signing out a user
48
+ # after deleting accounts. Returns true if there was a logout and false if there
49
+ # is no user logged in on the referred scope
50
+ #
51
+ # Examples:
52
+ #
53
+ # sign_out :user # sign_out(scope)
54
+ # sign_out @user # sign_out(resource)
55
+ #
56
+ def sign_out(resource_or_scope=nil)
57
+ return sign_out_all_scopes unless resource_or_scope
58
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
59
+ user = warden.user(:scope => scope, :run_callbacks => false) # If there is no user
60
+
61
+ warden.raw_session.inspect # Without this inspect here. The session does not clear.
62
+ warden.logout(scope)
63
+ warden.clear_strategies_cache!(:scope => scope)
64
+ instance_variable_set(:"@current_#{scope}", nil)
65
+
66
+ !!user
67
+ end
68
+
69
+ # Sign out all active users or scopes. This helper is useful for signing out all roles
70
+ # in one click. This signs out ALL scopes in warden. Returns true if there was at least one logout
71
+ # and false if there was no user logged in on all scopes.
72
+ def sign_out_all_scopes(lock=true)
73
+ users = Devise.mappings.keys.map { |s| warden.user(:scope => s, :run_callbacks => false) }
74
+
75
+ warden.raw_session.inspect
76
+ warden.logout
77
+ expire_data_after_sign_out!
78
+ warden.clear_strategies_cache!
79
+ warden.lock! if lock
80
+
81
+ users.any?
82
+ end
83
+
84
+ private
85
+
86
+ def expire_data_after_sign_in!
87
+ # session.keys will return an empty array if the session is not yet loaded.
88
+ # This is a bug in both Rack and Rails.
89
+ # A call to #empty? forces the session to be loaded.
90
+ session.empty?
91
+ session.keys.grep(/^devise\./).each { |k| session.delete(k) }
92
+ end
93
+
94
+ def expire_data_after_sign_out!
95
+ # session.keys will return an empty array if the session is not yet loaded.
96
+ # This is a bug in both Rack and Rails.
97
+ # A call to #empty? forces the session to be loaded.
98
+ session.empty?
99
+ session.keys.grep(/^devise\./).each { |k| session.delete(k) }
100
+ end
101
+ end
102
+ end
103
+ end
@@ -64,12 +64,21 @@ module Devise
64
64
 
65
65
  protected
66
66
 
67
+ def i18n_options(options)
68
+ options
69
+ end
70
+
67
71
  def i18n_message(default = nil)
68
72
  message = warden_message || default || :unauthenticated
69
73
 
70
74
  if message.is_a?(Symbol)
71
- I18n.t(:"#{scope}.#{message}", :resource_name => scope,
72
- :scope => "devise.failure", :default => [message])
75
+ options = {}
76
+ options[:resource_name] = scope
77
+ options[:scope] = "devise.failure"
78
+ options[:default] = [message]
79
+ options = i18n_options(options)
80
+
81
+ I18n.t(:"#{scope}.#{message}", options)
73
82
  else
74
83
  message.to_s
75
84
  end
@@ -4,6 +4,6 @@
4
4
  # This avoids forgetting deleted users.
5
5
  Warden::Manager.before_logout do |record, warden, options|
6
6
  if record.respond_to?(:forget_me!)
7
- Devise::Controllers::Rememberable::Proxy.new(warden).forget_me(record)
7
+ Devise::Hooks::Proxy.new(warden).forget_me(record)
8
8
  end
9
9
  end
@@ -0,0 +1,21 @@
1
+ module Devise
2
+ module Hooks
3
+ # A small warden proxy so we can remember, forget and
4
+ # sign out users from hooks.
5
+ class Proxy #:nodoc:
6
+ include Devise::Controllers::Rememberable
7
+ include Devise::Controllers::SignInOut
8
+
9
+ attr_reader :warden
10
+ delegate :cookies, :env, :to => :warden
11
+
12
+ def initialize(warden)
13
+ @warden = warden
14
+ end
15
+
16
+ def session
17
+ warden.request.session
18
+ end
19
+ end
20
+ end
21
+ end
@@ -2,6 +2,6 @@ Warden::Manager.after_set_user :except => :fetch do |record, warden, options|
2
2
  scope = options[:scope]
3
3
  if record.respond_to?(:remember_me) && options[:store] != false &&
4
4
  record.remember_me && warden.authenticated?(scope)
5
- Devise::Controllers::Rememberable::Proxy.new(warden).remember_me(record)
5
+ Devise::Hooks::Proxy.new(warden).remember_me(record)
6
6
  end
7
7
  end
@@ -9,12 +9,15 @@ Warden::Manager.after_set_user do |record, warden, options|
9
9
 
10
10
  if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) && options[:store] != false
11
11
  last_request_at = warden.session(scope)['last_request_at']
12
+ proxy = Devise::Hooks::Proxy.new(warden)
12
13
 
13
14
  if record.timedout?(last_request_at) && !env['devise.skip_timeout']
14
- warden.logout(scope)
15
+ Devise.sign_out_all_scopes ? proxy.sign_out : sign_out(scope)
16
+
15
17
  if record.respond_to?(:expire_auth_token_on_timeout) && record.expire_auth_token_on_timeout
16
18
  record.reset_authentication_token!
17
19
  end
20
+
18
21
  throw :warden, :scope => scope, :message => :timeout
19
22
  end
20
23
 
@@ -84,11 +84,6 @@ module Devise
84
84
  devise_modules_hook! do
85
85
  include Devise::Models::Authenticatable
86
86
 
87
- if selected_modules.include?(:token_authenticatable)
88
- ActiveSupport::Deprecation.warn "devise :token_authenticatable is deprecated. " \
89
- "Please check Devise 3.1 release notes for more information on how to upgrade."
90
- end
91
-
92
87
  selected_modules.each do |m|
93
88
  mod = Devise::Models.const_get(m.to_s.classify)
94
89
 
@@ -29,9 +29,7 @@ module Devise
29
29
  # It also accepts an array specifying the strategies that should allow params authentication.
30
30
  #
31
31
  # * +skip_session_storage+: By default Devise will store the user in session.
32
- # You can skip storage for http and token auth by appending values to array:
33
- # :skip_session_storage => [:token_auth] or :skip_session_storage => [:http_auth, :token_auth],
34
- # by default is set to :skip_session_storage => [:http_auth].
32
+ # By default is set to :skip_session_storage => [:http_auth].
35
33
  #
36
34
  # == active_for_authentication?
37
35
  #
@@ -176,23 +174,24 @@ module Devise
176
174
  end
177
175
 
178
176
  def downcase_keys
179
- self.class.case_insensitive_keys.each { |k| apply_to_attribute_or_variable(k, :downcase!) }
177
+ self.class.case_insensitive_keys.each { |k| apply_to_attribute_or_variable(k, :downcase) }
180
178
  end
181
179
 
182
180
  def strip_whitespace
183
- self.class.strip_whitespace_keys.each { |k| apply_to_attribute_or_variable(k, :strip!) }
181
+ self.class.strip_whitespace_keys.each { |k| apply_to_attribute_or_variable(k, :strip) }
184
182
  end
185
183
 
186
184
  def apply_to_attribute_or_variable(attr, method)
187
185
  if self[attr]
188
- self[attr].try(method)
186
+ self[attr] = self[attr].try(method)
189
187
 
190
188
  # Use respond_to? here to avoid a regression where globally
191
189
  # configured strip_whitespace_keys or case_insensitive_keys were
192
- # attempting to strip! or downcase! when a model didn't have the
190
+ # attempting to strip or downcase when a model didn't have the
193
191
  # globally configured key.
194
- elsif respond_to?(attr)
195
- send(attr).try(method)
192
+ elsif respond_to?(attr) && respond_to?("#{attr}=")
193
+ new_value = send(attr).try(method)
194
+ send("#{attr}=", new_value)
196
195
  end
197
196
  end
198
197
 
@@ -275,10 +275,6 @@ module Devise
275
275
  confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
276
276
 
277
277
  confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
278
- if !confirmable.persisted? && Devise.allow_insecure_token_lookup
279
- confirmable = find_or_initialize_with_error_by(:confirmation_token, original_token)
280
- end
281
-
282
278
  confirmable.confirm! if confirmable.persisted?
283
279
  confirmable.confirmation_token = original_token
284
280
  confirmable
@@ -2,6 +2,11 @@ require 'devise/strategies/database_authenticatable'
2
2
  require 'bcrypt'
3
3
 
4
4
  module Devise
5
+ # Digests the password using bcrypt.
6
+ def self.bcrypt(klass, password)
7
+ ::BCrypt::Password.create("#{password}#{klass.pepper}", :cost => klass.stretches).to_s
8
+ end
9
+
5
10
  module Models
6
11
  # Authenticatable Module, responsible for encrypting password and validating
7
12
  # authenticity of a user while signing in.
@@ -34,7 +39,7 @@ module Devise
34
39
  # Generates password encryption based on the given value.
35
40
  def password=(new_password)
36
41
  @password = new_password
37
- self.encrypted_password = password_digest(@password) if @password.present?
42
+ self.encrypted_password = Devise.bcrypt(self.class, @password) if @password.present?
38
43
  end
39
44
 
40
45
  # Verifies whether an password (ie from sign in) is the user password.
@@ -96,7 +101,7 @@ module Devise
96
101
  end
97
102
 
98
103
  # Destroy record when :current_password matches, otherwise returns
99
- # error on :current_password. It also automatically rejects
104
+ # error on :current_password. It also automatically rejects
100
105
  # :current_password if it is blank.
101
106
  def destroy_with_password(current_password)
102
107
  result = if valid_password?(current_password)
@@ -110,6 +115,16 @@ module Devise
110
115
  result
111
116
  end
112
117
 
118
+ # A callback initiated after successfully authenticating. This can be
119
+ # used to insert your own logic that is only run after the user successfully
120
+ # authenticates.
121
+ #
122
+ # Example:
123
+ #
124
+ # def after_database_authentication
125
+ # self.update_attribute(:invite_code, nil)
126
+ # end
127
+ #
113
128
  def after_database_authentication
114
129
  end
115
130
 
@@ -120,11 +135,6 @@ module Devise
120
135
 
121
136
  protected
122
137
 
123
- # Digests the password using bcrypt.
124
- def password_digest(password)
125
- ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
126
- end
127
-
128
138
  module ClassMethods
129
139
  Devise::Models.config(self, :pepper, :stretches)
130
140
 
@@ -112,6 +112,8 @@ module Devise
112
112
  # leaks the existence of an account.
113
113
  if Devise.paranoid
114
114
  super
115
+ elsif lock_strategy_enabled?(:failed_attempts) && last_attempt?
116
+ :last_attempt
115
117
  elsif lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?
116
118
  :locked
117
119
  else
@@ -125,6 +127,10 @@ module Devise
125
127
  self.failed_attempts > self.class.maximum_attempts
126
128
  end
127
129
 
130
+ def last_attempt?
131
+ self.failed_attempts == self.class.maximum_attempts
132
+ end
133
+
128
134
  # Tells if the lock is expired if :time unlock strategy is active
129
135
  def lock_expired?
130
136
  if unlock_strategy_enabled?(:time)
@@ -165,10 +171,6 @@ module Devise
165
171
  unlock_token = Devise.token_generator.digest(self, :unlock_token, unlock_token)
166
172
 
167
173
  lockable = find_or_initialize_with_error_by(:unlock_token, unlock_token)
168
- if !lockable.persisted? && Devise.allow_insecure_token_lookup
169
- lockable = find_or_initialize_with_error_by(:unlock_token, original_token)
170
- end
171
-
172
174
  lockable.unlock_access! if lockable.persisted?
173
175
  lockable.unlock_token = original_token
174
176
  lockable
@@ -101,11 +101,6 @@ module Devise
101
101
  recoverable
102
102
  end
103
103
 
104
- # Generate a token checking if one does not already exist in the database.
105
- def reset_password_token
106
- generate_token(:reset_password_token)
107
- end
108
-
109
104
  # Attempt to find a user by its reset_password_token to reset its
110
105
  # password. If a user is found and token is still valid, reset its password and automatically
111
106
  # try saving the record. If not user is found, returns a new user
@@ -116,9 +111,6 @@ module Devise
116
111
  reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token)
117
112
 
118
113
  recoverable = find_or_initialize_with_error_by(:reset_password_token, reset_password_token)
119
- if !recoverable.persisted? && Devise.allow_insecure_token_lookup
120
- recoverable = find_or_initialize_with_error_by(:reset_password_token, original_token)
121
- end
122
114
 
123
115
  if recoverable.persisted?
124
116
  if recoverable.reset_password_period_valid?
@@ -5,7 +5,6 @@ Devise.with_options :model => true do |d|
5
5
  d.with_options :strategy => true do |s|
6
6
  routes = [nil, :new, :destroy]
7
7
  s.add_module :database_authenticatable, :controller => :sessions, :route => { :session => routes }
8
- s.add_module :token_authenticatable, :controller => :sessions, :route => { :session => routes }, :no_input => true
9
8
  s.add_module :rememberable, :no_input => true
10
9
  end
11
10
 
@@ -58,6 +58,28 @@ module ActionDispatch::Routing
58
58
  # user_confirmation GET /users/confirmation(.:format) {:controller=>"devise/confirmations", :action=>"show"}
59
59
  # POST /users/confirmation(.:format) {:controller=>"devise/confirmations", :action=>"create"}
60
60
  #
61
+ # ==== Routes integration
62
+ #
63
+ # +devise_for+ is meant to play nicely with other routes methods. For example,
64
+ # by calling +devise_for+ inside a namespace, it automatically nests your devise
65
+ # controllers:
66
+ #
67
+ # namespace :publisher do
68
+ # devise_for :account
69
+ # end
70
+ #
71
+ # The snippet above will use publisher/sessions controller instead of devise/sessions
72
+ # controller. You can revert this change or configure it directly by passing the :module
73
+ # option described below to +devise_for+.
74
+ #
75
+ # Also note that when you use a namespace it will affect all the helpers and methods
76
+ # for controllers and views. For example, using the above setup you'll end with
77
+ # following methods: current_publisher_account, authenticate_publisher_account!,
78
+ # publisher_account_signed_in, etc.
79
+ #
80
+ # The only aspect not affect by the router configuration is the model name. The
81
+ # model name can be explicitly set via the :class_name option.
82
+ #
61
83
  # ==== Options
62
84
  #
63
85
  # You can configure your routes with some options:
@@ -104,20 +126,6 @@ module ActionDispatch::Routing
104
126
  #
105
127
  # devise_for :users, :module => "users"
106
128
  #
107
- # Notice that whenever you use namespace in the router DSL, it automatically sets the module.
108
- # So the following setup:
109
- #
110
- # namespace :publisher do
111
- # devise_for :account
112
- # end
113
- #
114
- # Will use publisher/sessions controller instead of devise/sessions controller. You can revert
115
- # this by providing the :module option to devise_for.
116
- #
117
- # Also pay attention that when you use a namespace it will affect all the helpers and methods for controllers
118
- # and views. For example, using the above setup you'll end with following methods:
119
- # current_publisher_account, authenticate_publisher_account!, publisher_account_signed_in, etc.
120
- #
121
129
  # * :skip => tell which controller you want to skip routes from being created:
122
130
  #
123
131
  # devise_for :users, :skip => :sessions
@@ -378,8 +386,14 @@ module ActionDispatch::Routing
378
386
  end
379
387
 
380
388
  def devise_omniauth_callback(mapping, controllers) #:nodoc:
389
+ if mapping.fullpath =~ /:[a-zA-Z_]/
390
+ raise "[DEVISE] Nesting omniauth callbacks under scopes with dynamic segments " \
391
+ "is not supported. Please, use Devise.omniauth_path_prefix instead."
392
+ end
393
+
381
394
  path, @scope[:path] = @scope[:path], nil
382
- path_prefix = Devise.omniauth_path_prefix || "/#{mapping.path}/auth".squeeze("/")
395
+ path_prefix = Devise.omniauth_path_prefix || "/#{mapping.fullpath}/auth".squeeze("/")
396
+
383
397
  set_omniauth_path_prefix!(path_prefix)
384
398
 
385
399
  providers = Regexp.union(mapping.to.omniauth_providers.map(&:to_s))
@@ -5,16 +5,13 @@ module Devise
5
5
  # Default strategy for signing in a user, based on his email and password in the database.
6
6
  class DatabaseAuthenticatable < Authenticatable
7
7
  def authenticate!
8
- resource = valid_password? && mapping.to.find_for_database_authentication(authentication_hash)
9
- encrypted = false
8
+ resource = valid_password? && mapping.to.find_for_database_authentication(authentication_hash)
9
+ return fail(:not_found_in_database) unless resource
10
10
 
11
- if validate(resource){ encrypted = true; resource.valid_password?(password) }
11
+ if validate(resource){ resource.valid_password?(password) }
12
12
  resource.after_database_authentication
13
13
  success!(resource)
14
14
  end
15
-
16
- mapping.to.new.password = password if !encrypted && Devise.paranoid
17
- fail(:not_found_in_database) unless resource
18
15
  end
19
16
  end
20
17
  end