devise 3.0.4 → 3.1.0.rc2

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 (52) hide show
  1. data/{CHANGELOG.rdoc → CHANGELOG.md} +41 -30
  2. data/Gemfile.lock +14 -13
  3. data/README.md +12 -11
  4. data/app/controllers/devise/confirmations_controller.rb +6 -2
  5. data/app/controllers/devise/registrations_controller.rb +2 -2
  6. data/app/controllers/devise/sessions_controller.rb +1 -1
  7. data/app/mailers/devise/mailer.rb +6 -3
  8. data/app/views/devise/mailer/confirmation_instructions.html.erb +1 -1
  9. data/app/views/devise/mailer/reset_password_instructions.html.erb +1 -1
  10. data/app/views/devise/mailer/unlock_instructions.html.erb +1 -1
  11. data/app/views/devise/shared/_links.erb +2 -2
  12. data/config/locales/en.yml +2 -2
  13. data/devise.gemspec +1 -0
  14. data/gemfiles/Gemfile.rails-3.2.x.lock +45 -42
  15. data/lib/devise.rb +20 -13
  16. data/lib/devise/controllers/helpers.rb +1 -0
  17. data/lib/devise/hooks/rememberable.rb +2 -1
  18. data/lib/devise/mailers/helpers.rb +0 -6
  19. data/lib/devise/models.rb +8 -12
  20. data/lib/devise/models/authenticatable.rb +8 -16
  21. data/lib/devise/models/confirmable.rb +27 -37
  22. data/lib/devise/models/lockable.rb +15 -17
  23. data/lib/devise/models/recoverable.rb +21 -27
  24. data/lib/devise/models/token_authenticatable.rb +4 -1
  25. data/lib/devise/parameter_sanitizer.rb +49 -19
  26. data/lib/devise/rails.rb +7 -11
  27. data/lib/devise/rails/routes.rb +12 -9
  28. data/lib/devise/rails/warden_compat.rb +1 -0
  29. data/lib/devise/strategies/authenticatable.rb +0 -12
  30. data/lib/devise/strategies/database_authenticatable.rb +3 -6
  31. data/lib/devise/token_generator.rb +70 -0
  32. data/lib/devise/version.rb +1 -1
  33. data/lib/generators/templates/devise.rb +14 -8
  34. data/test/controllers/passwords_controller_test.rb +3 -4
  35. data/test/failure_app_test.rb +1 -1
  36. data/test/integration/confirmable_test.rb +16 -41
  37. data/test/integration/lockable_test.rb +11 -14
  38. data/test/integration/recoverable_test.rb +23 -15
  39. data/test/mailers/confirmation_instructions_test.rb +6 -2
  40. data/test/mailers/reset_password_instructions_test.rb +6 -2
  41. data/test/mailers/unlock_instructions_test.rb +6 -2
  42. data/test/models/confirmable_test.rb +20 -30
  43. data/test/models/lockable_test.rb +15 -5
  44. data/test/models/recoverable_test.rb +20 -48
  45. data/test/models_test.rb +0 -19
  46. data/test/parameter_sanitizer_test.rb +23 -9
  47. data/test/rails_app/config/initializers/devise.rb +3 -0
  48. data/test/rails_app/lib/shared_admin.rb +3 -0
  49. data/test/rails_app/lib/shared_user.rb +4 -0
  50. data/test/support/helpers.rb +0 -21
  51. metadata +23 -7
  52. data/app/views/devise/_links.erb +0 -3
@@ -14,6 +14,7 @@ module Devise
14
14
  autoload :ParameterSanitizer, 'devise/parameter_sanitizer'
15
15
  autoload :TestHelpers, 'devise/test_helpers'
16
16
  autoload :TimeInflector, 'devise/time_inflector'
17
+ autoload :TokenGenerator, 'devise/token_generator'
17
18
 
18
19
  module Controllers
19
20
  autoload :Helpers, 'devise/controllers/helpers'
@@ -45,6 +46,20 @@ module Devise
45
46
  # True values used to check params
46
47
  TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE']
47
48
 
49
+ # Secret key used by the key generator
50
+ mattr_accessor :secret_key
51
+ @@secret_key = nil
52
+
53
+ # Allow insecure token lookup. Must be used
54
+ # temporarily just for migration.
55
+ mattr_accessor :allow_insecure_token_lookup
56
+ @@allow_insecure_tokens_lookup = false
57
+
58
+ # Allow insecure sign in after confirmation. Must be used
59
+ # temporarily just for migration.
60
+ mattr_accessor :allow_insecure_sign_in_after_confirmation
61
+ @@allow_insecure_sign_in_after_confirmation = false
62
+
48
63
  # Custom domain or key for cookies. Not set by default
49
64
  mattr_accessor :rememberable_options
50
65
  @@rememberable_options = {}
@@ -227,18 +242,6 @@ module Devise
227
242
  mattr_accessor :clean_up_csrf_token_on_authentication
228
243
  @@clean_up_csrf_token_on_authentication = true
229
244
 
230
- def self.encryptor=(value)
231
- warn "\n[DEVISE] To select a encryption which isn't bcrypt, you should use devise-encryptable gem.\n"
232
- end
233
-
234
- def self.use_salt_as_remember_token=(value)
235
- warn "\n[DEVISE] Devise.use_salt_as_remember_token is deprecated and has no effect. Please remove it.\n"
236
- end
237
-
238
- def self.apply_schema=(value)
239
- warn "\n[DEVISE] Devise.apply_schema is deprecated and has no effect. Please remove it.\n"
240
- end
241
-
242
245
  # PRIVATE CONFIGURATION
243
246
 
244
247
  # Store scopes mappings.
@@ -263,6 +266,10 @@ module Devise
263
266
  mattr_accessor :paranoid
264
267
  @@paranoid = false
265
268
 
269
+ # Stores the token generator
270
+ mattr_accessor :token_generator
271
+ @@token_generator = nil
272
+
266
273
  # Default way to setup Devise. Run rails generate devise_install to create
267
274
  # a fresh initializer with all configuration values.
268
275
  def self.setup
@@ -451,7 +458,7 @@ module Devise
451
458
 
452
459
  # Generate a friendly string randomly to be used as token.
453
460
  def self.friendly_token
454
- SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')
461
+ SecureRandom.urlsafe_base64(15).tr('lIO0', 'sxyz')
455
462
  end
456
463
 
457
464
  # constant-time comparison algorithm to prevent timing attacks
@@ -117,6 +117,7 @@ module Devise
117
117
  # sign_in :user, @user # sign_in(scope, resource)
118
118
  # sign_in @user # sign_in(resource)
119
119
  # sign_in @user, :event => :authentication # sign_in(resource, options)
120
+ # sign_in @user, :store => false # sign_in(resource, options)
120
121
  # sign_in @user, :bypass => true # sign_in(resource, options)
121
122
  #
122
123
  def sign_in(resource_or_scope, *args)
@@ -1,6 +1,7 @@
1
1
  Warden::Manager.after_set_user :except => :fetch do |record, warden, options|
2
2
  scope = options[:scope]
3
- if record.respond_to?(:remember_me) && record.remember_me && warden.authenticated?(scope)
3
+ if record.respond_to?(:remember_me) && options[:store] != false &&
4
+ record.remember_me && warden.authenticated?(scope)
4
5
  Devise::Controllers::Rememberable::Proxy.new(warden).remember_me(record)
5
6
  end
6
7
  end
@@ -35,12 +35,6 @@ module Devise
35
35
  :template_name => action
36
36
  }.merge(opts)
37
37
 
38
- if resource.respond_to?(:headers_for)
39
- ActiveSupport::Deprecation.warn "Calling headers_for in the model is no longer supported. " <<
40
- "Please customize your mailer instead."
41
- headers.merge!(resource.headers_for(action))
42
- end
43
-
44
38
  @email = headers[:to]
45
39
  headers
46
40
  end
@@ -56,14 +56,8 @@ module Devise
56
56
  klass.devise_modules.each do |mod|
57
57
  constant = const_get(mod.to_s.classify)
58
58
 
59
- if constant.respond_to?(:required_fields)
60
- constant.required_fields(klass).each do |field|
61
- failed_attributes << field unless instance.respond_to?(field)
62
- end
63
- else
64
- ActiveSupport::Deprecation.warn "The module #{mod} doesn't implement self.required_fields(klass). " \
65
- "Devise uses required_fields to warn developers of any missing fields in their models. " \
66
- "Please implement #{mod}.required_fields(klass) that returns an array of symbols with the required fields."
59
+ constant.required_fields(klass).each do |field|
60
+ failed_attributes << field unless instance.respond_to?(field)
67
61
  end
68
62
  end
69
63
 
@@ -89,11 +83,13 @@ module Devise
89
83
 
90
84
  devise_modules_hook! do
91
85
  include Devise::Models::Authenticatable
92
- selected_modules.each do |m|
93
- if m == :encryptable && !(defined?(Devise::Models::Encryptable))
94
- warn "[DEVISE] You're trying to include :encryptable in your model but it is not bundled with the Devise gem anymore. Please add `devise-encryptable` to your Gemfile to proceed.\n"
95
- end
96
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
+ selected_modules.each do |m|
97
93
  mod = Devise::Models.const_get(m.to_s.classify)
98
94
 
99
95
  if mod.const_defined?("ClassMethods")
@@ -144,20 +144,20 @@ module Devise
144
144
  #
145
145
  # protected
146
146
  #
147
- # def send_devise_notification(notification, opts = {})
148
- # # if the record is new or changed then delay the
147
+ # def send_devise_notification(notification, *args)
148
+ # # If the record is new or changed then delay the
149
149
  # # delivery until the after_commit callback otherwise
150
150
  # # send now because after_commit will not be called.
151
151
  # if new_record? || changed?
152
- # pending_notifications << [notification, opts]
152
+ # pending_notifications << [notification, args]
153
153
  # else
154
- # devise_mailer.send(notification, self, opts).deliver
154
+ # devise_mailer.send(notification, self, *args).deliver
155
155
  # end
156
156
  # end
157
157
  #
158
158
  # def send_pending_notifications
159
- # pending_notifications.each do |n, opts|
160
- # devise_mailer.send(n, self, opts).deliver
159
+ # pending_notifications.each do |notification, args|
160
+ # devise_mailer.send(notification, self, *args).deliver
161
161
  # end
162
162
  #
163
163
  # # Empty the pending notifications array because the
@@ -171,8 +171,8 @@ module Devise
171
171
  # end
172
172
  # end
173
173
  #
174
- def send_devise_notification(notification, opts={})
175
- devise_mailer.send(notification, self, opts).deliver
174
+ def send_devise_notification(notification, *args)
175
+ devise_mailer.send(notification, self, *args).deliver
176
176
  end
177
177
 
178
178
  def downcase_keys
@@ -279,14 +279,6 @@ module Devise
279
279
  def devise_parameter_filter
280
280
  @devise_parameter_filter ||= Devise::ParameterFilter.new(case_insensitive_keys, strip_whitespace_keys)
281
281
  end
282
-
283
- # Generate a token by looping and ensuring does not already exist.
284
- def generate_token(column)
285
- loop do
286
- token = Devise.friendly_token
287
- break token unless to_adapter.find_first({ column => token })
288
- end
289
- end
290
282
  end
291
283
  end
292
284
  end
@@ -7,7 +7,7 @@ module Devise
7
7
  #
8
8
  # == Options
9
9
  #
10
- # Confirmable adds the following options to devise_for:
10
+ # Confirmable adds the following options to +devise+:
11
11
  #
12
12
  # * +allow_unconfirmed_access_for+: the time you want to allow the user to access his account
13
13
  # before confirming it. After this period, the user access is denied. You can
@@ -40,9 +40,10 @@ module Devise
40
40
  end
41
41
 
42
42
  def initialize(*args, &block)
43
- @bypass_postpone = false
43
+ @bypass_confirmation_postpone = false
44
44
  @reconfirmation_required = false
45
45
  @skip_confirmation_notification = false
46
+ @raw_confirmation_token = nil
46
47
  super
47
48
  end
48
49
 
@@ -93,10 +94,12 @@ module Devise
93
94
 
94
95
  # Send confirmation instructions by email
95
96
  def send_confirmation_instructions
96
- ensure_confirmation_token!
97
+ unless @raw_confirmation_token
98
+ generate_confirmation_token!
99
+ end
97
100
 
98
101
  opts = pending_reconfirmation? ? { :to => unconfirmed_email } : { }
99
- send_devise_notification(:confirmation_instructions, opts)
102
+ send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
100
103
  end
101
104
 
102
105
  def send_reconfirmation_instructions
@@ -109,17 +112,11 @@ module Devise
109
112
 
110
113
  # Resend confirmation token.
111
114
  # Regenerates the token if the period is expired.
112
- def resend_confirmation_token
115
+ def resend_confirmation_instructions
113
116
  pending_any_confirmation do
114
- regenerate_confirmation_token! if confirmation_period_expired?
115
117
  send_confirmation_instructions
116
118
  end
117
119
  end
118
-
119
- # Generate a confirmation token unless already exists and save the record.
120
- def ensure_confirmation_token!
121
- generate_confirmation_token! if should_generate_confirmation_token?
122
- end
123
120
 
124
121
  # Overwrites active_for_authentication? for confirmation
125
122
  # by verifying whether a user is active to sign in or not. If the user
@@ -149,19 +146,16 @@ module Devise
149
146
  # If you don't want reconfirmation to be sent, neither a code
150
147
  # to be generated, call skip_reconfirmation!
151
148
  def skip_reconfirmation!
152
- @bypass_postpone = true
149
+ @bypass_confirmation_postpone = true
153
150
  end
154
151
 
155
152
  protected
156
- def should_generate_confirmation_token?
157
- confirmation_token.nil? || confirmation_period_expired?
158
- end
159
153
 
160
154
  # A callback method used to deliver confirmation
161
155
  # instructions on creation. This can be overriden
162
156
  # in models to map to a nice sign up e-mail.
163
157
  def send_on_create_confirmation_instructions
164
- send_devise_notification(:confirmation_instructions)
158
+ send_confirmation_instructions
165
159
  end
166
160
 
167
161
  # Callback to overwrite if confirmation is required or not.
@@ -221,10 +215,12 @@ module Devise
221
215
  end
222
216
  end
223
217
 
224
- # Generates a new random token for confirmation, and stores the time
225
- # this token is being generated
218
+ # Generates a new random token for confirmation, and stores
219
+ # the time this token is being generated
226
220
  def generate_confirmation_token
227
- self.confirmation_token = self.class.confirmation_token
221
+ raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
222
+ @raw_confirmation_token = raw
223
+ self.confirmation_token = enc
228
224
  self.confirmation_sent_at = Time.now.utc
229
225
  end
230
226
 
@@ -232,25 +228,16 @@ module Devise
232
228
  generate_confirmation_token && save(:validate => false)
233
229
  end
234
230
 
235
- # Regenerates a new token.
236
- def regenerate_confirmation_token
237
- generate_confirmation_token
238
- end
239
-
240
- def regenerate_confirmation_token!
241
- regenerate_confirmation_token && save(:validate => false)
242
- end
243
-
244
231
  def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
245
232
  @reconfirmation_required = true
246
233
  self.unconfirmed_email = self.email
247
234
  self.email = self.email_was
248
- regenerate_confirmation_token
235
+ generate_confirmation_token
249
236
  end
250
237
 
251
238
  def postpone_email_change?
252
- postpone = self.class.reconfirmable && email_changed? && !@bypass_postpone && !self.email.blank?
253
- @bypass_postpone = false
239
+ postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && !self.email.blank?
240
+ @bypass_confirmation_postpone = false
254
241
  postpone
255
242
  end
256
243
 
@@ -275,7 +262,7 @@ module Devise
275
262
  unless confirmable.try(:persisted?)
276
263
  confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
277
264
  end
278
- confirmable.resend_confirmation_token if confirmable.persisted?
265
+ confirmable.resend_confirmation_instructions if confirmable.persisted?
279
266
  confirmable
280
267
  end
281
268
 
@@ -284,16 +271,19 @@ module Devise
284
271
  # If the user is already confirmed, create an error for the user
285
272
  # Options must have the confirmation_token
286
273
  def confirm_by_token(confirmation_token)
274
+ original_token = confirmation_token
275
+ confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
276
+
287
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
+
288
282
  confirmable.confirm! if confirmable.persisted?
283
+ confirmable.confirmation_token = original_token
289
284
  confirmable
290
285
  end
291
286
 
292
- # Generate a token checking if one does not already exist in the database.
293
- def confirmation_token
294
- generate_token(:confirmation_token)
295
- end
296
-
297
287
  # Find a record for confirmation by unconfirmed email field
298
288
  def find_by_unconfirmed_email_with_errors(attributes = {})
299
289
  unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
@@ -38,7 +38,6 @@ module Devise
38
38
  self.locked_at = Time.now.utc
39
39
 
40
40
  if unlock_strategy_enabled?(:email)
41
- generate_unlock_token!
42
41
  send_unlock_instructions
43
42
  else
44
43
  save(:validate => false)
@@ -60,11 +59,15 @@ module Devise
60
59
 
61
60
  # Send unlock instructions by email
62
61
  def send_unlock_instructions
63
- send_devise_notification(:unlock_instructions)
62
+ raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
63
+ self.unlock_token = enc
64
+ self.save(:validate => false)
65
+ send_devise_notification(:unlock_instructions, raw, {})
66
+ raw
64
67
  end
65
68
 
66
69
  # Resend the unlock instructions if the user is locked.
67
- def resend_unlock_token
70
+ def resend_unlock_instructions
68
71
  if_access_locked { send_unlock_instructions }
69
72
  end
70
73
 
@@ -122,15 +125,6 @@ module Devise
122
125
  self.failed_attempts > self.class.maximum_attempts
123
126
  end
124
127
 
125
- # Generates unlock token
126
- def generate_unlock_token
127
- self.unlock_token = self.class.unlock_token
128
- end
129
-
130
- def generate_unlock_token!
131
- generate_unlock_token && save(:validate => false)
132
- end
133
-
134
128
  # Tells if the lock is expired if :time unlock strategy is active
135
129
  def lock_expired?
136
130
  if unlock_strategy_enabled?(:time)
@@ -158,7 +152,7 @@ module Devise
158
152
  # Options must contain the user's unlock keys
159
153
  def send_unlock_instructions(attributes={})
160
154
  lockable = find_or_initialize_with_errors(unlock_keys, attributes, :not_found)
161
- lockable.resend_unlock_token if lockable.persisted?
155
+ lockable.resend_unlock_instructions if lockable.persisted?
162
156
  lockable
163
157
  end
164
158
 
@@ -167,8 +161,16 @@ module Devise
167
161
  # If the user is not locked, creates an error for the user
168
162
  # Options must have the unlock_token
169
163
  def unlock_access_by_token(unlock_token)
164
+ original_token = unlock_token
165
+ unlock_token = Devise.token_generator.digest(self, :unlock_token, unlock_token)
166
+
170
167
  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
+
171
172
  lockable.unlock_access! if lockable.persisted?
173
+ lockable.unlock_token = original_token
172
174
  lockable
173
175
  end
174
176
 
@@ -182,10 +184,6 @@ module Devise
182
184
  self.lock_strategy == strategy
183
185
  end
184
186
 
185
- def unlock_token
186
- Devise.friendly_token
187
- end
188
-
189
187
  Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys)
190
188
  end
191
189
  end
@@ -42,17 +42,19 @@ module Devise
42
42
  save
43
43
  end
44
44
 
45
- # Resets reset password token and send reset password instructions by email
45
+ # Resets reset password token and send reset password instructions by email.
46
+ # Returns the token sent in the e-mail.
46
47
  def send_reset_password_instructions
47
- ensure_reset_password_token!
48
- send_devise_notification(:reset_password_instructions)
48
+ raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
49
+
50
+ self.reset_password_token = enc
51
+ self.reset_password_sent_at = Time.now.utc
52
+ self.save(:validate => false)
53
+
54
+ send_devise_notification(:reset_password_instructions, raw, {})
55
+ raw
49
56
  end
50
-
51
- # Generate reset password token unless already exists and save the record.
52
- def ensure_reset_password_token!
53
- generate_reset_password_token! if should_generate_reset_token?
54
- end
55
-
57
+
56
58
  # Checks if the reset password token sent is within the limit time.
57
59
  # We do this by calculating if the difference between today and the
58
60
  # sending date does not exceed the confirm in time configured.
@@ -79,23 +81,6 @@ module Devise
79
81
 
80
82
  protected
81
83
 
82
- def should_generate_reset_token?
83
- reset_password_token.nil? || !reset_password_period_valid?
84
- end
85
-
86
- # Generates a new random token for reset password
87
- def generate_reset_password_token
88
- self.reset_password_token = self.class.reset_password_token
89
- self.reset_password_sent_at = Time.now.utc
90
- self.reset_password_token
91
- end
92
-
93
- # Resets the reset password token with and save the record without
94
- # validating
95
- def generate_reset_password_token!
96
- generate_reset_password_token && save(:validate => false)
97
- end
98
-
99
84
  # Removes reset_password token
100
85
  def clear_reset_password_token
101
86
  self.reset_password_token = nil
@@ -127,7 +112,14 @@ module Devise
127
112
  # containing an error in reset_password_token attribute.
128
113
  # Attributes must contain reset_password_token, password and confirmation
129
114
  def reset_password_by_token(attributes={})
130
- recoverable = find_or_initialize_with_error_by(:reset_password_token, attributes[:reset_password_token])
115
+ original_token = attributes[:reset_password_token]
116
+ reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token)
117
+
118
+ 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
+
131
123
  if recoverable.persisted?
132
124
  if recoverable.reset_password_period_valid?
133
125
  recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
@@ -135,6 +127,8 @@ module Devise
135
127
  recoverable.errors.add(:reset_password_token, :expired)
136
128
  end
137
129
  end
130
+
131
+ recoverable.reset_password_token = original_token
138
132
  recoverable
139
133
  end
140
134