devise 4.2.0 → 4.2.1

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +9 -4
  3. data/CHANGELOG.md +14 -1
  4. data/CONTRIBUTING.md +68 -28
  5. data/Gemfile.lock +69 -74
  6. data/README.md +10 -6
  7. data/app/controllers/devise/registrations_controller.rb +1 -0
  8. data/app/mailers/devise/mailer.rb +4 -0
  9. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  10. data/config/locales/en.yml +2 -0
  11. data/gemfiles/Gemfile.rails-4.1-stable.lock +65 -68
  12. data/gemfiles/Gemfile.rails-4.2-stable.lock +65 -70
  13. data/lib/devise.rb +5 -1
  14. data/lib/devise/controllers/store_location.rb +1 -1
  15. data/lib/devise/failure_app.rb +12 -12
  16. data/lib/devise/hooks/lockable.rb +4 -1
  17. data/lib/devise/mailers/helpers.rb +4 -3
  18. data/lib/devise/models.rb +1 -1
  19. data/lib/devise/models/confirmable.rb +14 -2
  20. data/lib/devise/models/database_authenticatable.rb +16 -1
  21. data/lib/devise/models/recoverable.rb +8 -4
  22. data/lib/devise/models/rememberable.rb +1 -1
  23. data/lib/devise/rails/routes.rb +1 -1
  24. data/lib/devise/test/controller_helpers.rb +1 -1
  25. data/lib/devise/test_helpers.rb +1 -1
  26. data/lib/devise/version.rb +1 -1
  27. data/lib/generators/templates/controllers/registrations_controller.rb +2 -2
  28. data/lib/generators/templates/controllers/sessions_controller.rb +1 -1
  29. data/lib/generators/templates/devise.rb +4 -1
  30. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  31. data/lib/generators/templates/markerb/password_change.markerb +2 -2
  32. data/test/controllers/helpers_test.rb +3 -3
  33. data/test/integration/authenticatable_test.rb +1 -1
  34. data/test/mailers/email_changed_test.rb +130 -0
  35. data/test/mailers/mailer_test.rb +18 -0
  36. data/test/models/confirmable_test.rb +17 -0
  37. data/test/models/database_authenticatable_test.rb +13 -1
  38. data/test/models/recoverable_test.rb +11 -1
  39. data/test/omniauth/config_test.rb +9 -7
  40. metadata +9 -3
@@ -153,7 +153,11 @@ module Devise
153
153
  mattr_accessor :pepper
154
154
  @@pepper = nil
155
155
 
156
- # Used to enable sending notification to user when their password is changed
156
+ # Used to send notification to the original user email when their email is changed.
157
+ mattr_accessor :send_email_changed_notification
158
+ @@send_email_changed_notification = false
159
+
160
+ # Used to enable sending notification to user when their password is changed.
157
161
  mattr_accessor :send_password_change_notification
158
162
  @@send_password_change_notification = false
159
163
 
@@ -29,7 +29,7 @@ module Devise
29
29
  # Example:
30
30
  #
31
31
  # store_location_for(:user, dashboard_path)
32
- # redirect_to user_omniauth_authorize_path(:facebook)
32
+ # redirect_to user_facebook_omniauth_authorize_path
33
33
  #
34
34
  def store_location_for(resource_or_scope, location)
35
35
  session_key = stored_location_key_for(resource_or_scope)
@@ -2,9 +2,9 @@ require "action_controller/metal"
2
2
 
3
3
  module Devise
4
4
  # Failure application that will be called every time :warden is thrown from
5
- # any strategy or hook. Responsible for redirect the user to the sign in
6
- # page based on current scope and mapping. If no scope is given, redirect
7
- # to the default_url.
5
+ # any strategy or hook. It is responsible for redirecting the user to the sign
6
+ # in page based on current scope and mapping. If no scope is given, it
7
+ # redirects to the default_url.
8
8
  class FailureApp < ActionController::Metal
9
9
  include ActionController::UrlFor
10
10
  include ActionController::Redirecting
@@ -160,12 +160,12 @@ module Devise
160
160
  %w(html */*).include? request_format.to_s
161
161
  end
162
162
 
163
- # Choose whether we should respond in a http authentication fashion,
163
+ # Choose whether we should respond in an HTTP authentication fashion,
164
164
  # including 401 and optional headers.
165
165
  #
166
- # This method allows the user to explicitly disable http authentication
167
- # on ajax requests in case they want to redirect on failures instead of
168
- # handling the errors on their own. This is useful in case your ajax API
166
+ # This method allows the user to explicitly disable HTTP authentication
167
+ # on AJAX requests in case they want to redirect on failures instead of
168
+ # handling the errors on their own. This is useful in case your AJAX API
169
169
  # is the same as your public API and uses a format like JSON (so you
170
170
  # cannot mark JSON as a navigational format).
171
171
  def http_auth?
@@ -176,7 +176,7 @@ module Devise
176
176
  end
177
177
  end
178
178
 
179
- # It does not make sense to send authenticate headers in ajax requests
179
+ # It doesn't make sense to send authenticate headers in AJAX requests
180
180
  # or if the user disabled them.
181
181
  def http_auth_header?
182
182
  scope_class.http_authenticatable && !request.xhr?
@@ -225,10 +225,10 @@ module Devise
225
225
  warden_options[:attempted_path]
226
226
  end
227
227
 
228
- # Stores requested uri to redirect the user after signing in. We cannot use
229
- # scoped session provided by warden here, since the user is not authenticated
230
- # yet, but we still need to store the uri based on scope, so different scopes
231
- # would never use the same uri to redirect.
228
+ # Stores requested URI to redirect the user after signing in. We can't use
229
+ # the scoped session provided by warden here, since the user is not
230
+ # authenticated yet, but we still need to store the URI based on scope, so
231
+ # different scopes would never use the same URI to redirect.
232
232
  def store_location!
233
233
  store_location_for(scope, attempted_path) if request.get? && !http_auth?
234
234
  end
@@ -2,6 +2,9 @@
2
2
  # This is only triggered when the user is explicitly set (with set_user)
3
3
  Warden::Manager.after_set_user except: :fetch do |record, warden, options|
4
4
  if record.respond_to?(:failed_attempts) && warden.authenticated?(options[:scope])
5
- record.update_attribute(:failed_attempts, 0) unless record.failed_attempts.to_i.zero?
5
+ unless record.failed_attempts.to_i.zero?
6
+ record.failed_attempts = 0
7
+ record.save(validate: false)
8
+ end
6
9
  end
7
10
  end
@@ -5,15 +5,16 @@ module Devise
5
5
 
6
6
  included do
7
7
  include Devise::Controllers::ScopedViews
8
- attr_reader :scope_name, :resource
9
8
  end
10
9
 
11
10
  protected
12
11
 
12
+ attr_reader :scope_name, :resource
13
+
13
14
  # Configure default email options
14
- def devise_mail(record, action, opts={})
15
+ def devise_mail(record, action, opts = {}, &block)
15
16
  initialize_from_record(record)
16
- mail headers_for(action, opts)
17
+ mail headers_for(action, opts), &block
17
18
  end
18
19
 
19
20
  def initialize_from_record(record)
@@ -12,7 +12,7 @@ module Devise
12
12
 
13
13
  # Creates configuration values for Devise and for the given module.
14
14
  #
15
- # Devise::Models.config(Devise::DatabaseAuthenticatable, :stretches)
15
+ # Devise::Models.config(Devise::Models::DatabaseAuthenticatable, :stretches)
16
16
  #
17
17
  # The line above creates:
18
18
  #
@@ -26,7 +26,9 @@ module Devise
26
26
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
27
27
  # db field to be set up (t.reconfirmable in migrations). Until confirmed, new email is
28
28
  # stored in unconfirmed email column, and copied to email column on successful
29
- # confirmation.
29
+ # confirmation. Also, when used in conjunction with `send_email_changed_notification`,
30
+ # the notification is sent to the original email when the change is requested,
31
+ # not when the unconfirmed email is confirmed.
30
32
  # * +confirm_within+: the time before a sent confirmation token becomes invalid.
31
33
  # You can use this to force the user to confirm within a set period of time.
32
34
  # Confirmable will not generate a new token if a repeat confirmation is requested
@@ -223,7 +225,7 @@ module Devise
223
225
  # confirmation_period_expired? # will always return false
224
226
  #
225
227
  def confirmation_period_expired?
226
- self.class.confirm_within && self.confirmation_sent_at && (Time.now > self.confirmation_sent_at + self.class.confirm_within)
228
+ self.class.confirm_within && self.confirmation_sent_at && (Time.now.utc > self.confirmation_sent_at.utc + self.class.confirm_within)
227
229
  end
228
230
 
229
231
  # Checks whether the record requires any confirmation.
@@ -277,6 +279,16 @@ module Devise
277
279
  confirmation_required? && !@skip_confirmation_notification && self.email.present?
278
280
  end
279
281
 
282
+ # With reconfirmable, notify the original email when the user first
283
+ # requests the email change, instead of when the change is confirmed.
284
+ def send_email_changed_notification?
285
+ if self.class.reconfirmable
286
+ self.class.send_email_changed_notification && reconfirmation_required?
287
+ else
288
+ super
289
+ end
290
+ end
291
+
280
292
  # A callback initiated after successfully confirming. This can be
281
293
  # used to insert your own logic that is only run after the user successfully
282
294
  # confirms.
@@ -14,6 +14,10 @@ module Devise
14
14
  #
15
15
  # * +stretches+: the cost given to bcrypt.
16
16
  #
17
+ # * +send_email_changed_notification+: notify original email when it changes.
18
+ #
19
+ # * +send_password_change_notification+: notify email when password changes.
20
+ #
17
21
  # == Examples
18
22
  #
19
23
  # User.find(1).valid_password?('password123') # returns true/false
@@ -22,6 +26,7 @@ module Devise
22
26
  extend ActiveSupport::Concern
23
27
 
24
28
  included do
29
+ after_update :send_email_changed_notification, if: :send_email_changed_notification?
25
30
  after_update :send_password_change_notification, if: :send_password_change_notification?
26
31
 
27
32
  attr_reader :password, :current_password
@@ -132,6 +137,12 @@ module Devise
132
137
  encrypted_password[0,29] if encrypted_password
133
138
  end
134
139
 
140
+ # Send notification to user when email changes.
141
+ def send_email_changed_notification
142
+ send_devise_notification(:email_changed, to: email_was)
143
+ end
144
+
145
+ # Send notification to user when password changes.
135
146
  def send_password_change_notification
136
147
  send_devise_notification(:password_change)
137
148
  end
@@ -147,12 +158,16 @@ module Devise
147
158
  Devise::Encryptor.digest(self.class, password)
148
159
  end
149
160
 
161
+ def send_email_changed_notification?
162
+ self.class.send_email_changed_notification && email_changed?
163
+ end
164
+
150
165
  def send_password_change_notification?
151
166
  self.class.send_password_change_notification && encrypted_password_changed?
152
167
  end
153
168
 
154
169
  module ClassMethods
155
- Devise::Models.config(self, :pepper, :stretches, :send_password_change_notification)
170
+ Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification)
156
171
 
157
172
  # We assume this method already gets the sanitized values from the
158
173
  # DatabaseAuthenticatable strategy. If you are using this method on
@@ -33,10 +33,14 @@ module Devise
33
33
  # Update password saving the record and clearing token. Returns true if
34
34
  # the passwords are valid and the record was saved, false otherwise.
35
35
  def reset_password(new_password, new_password_confirmation)
36
- self.password = new_password
37
- self.password_confirmation = new_password_confirmation
38
-
39
- save
36
+ if new_password.present?
37
+ self.password = new_password
38
+ self.password_confirmation = new_password_confirmation
39
+ save
40
+ else
41
+ errors.add(:password, :blank)
42
+ false
43
+ end
40
44
  end
41
45
 
42
46
  # Resets reset password token and send reset password instructions by email.
@@ -74,7 +74,7 @@ module Devise
74
74
  elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt.presence)
75
75
  salt
76
76
  else
77
- raise "authenticable_salt returned nil for the #{self.class.name} model. " \
77
+ raise "authenticatable_salt returned nil for the #{self.class.name} model. " \
78
78
  "In order to use rememberable, you must ensure a password is always set " \
79
79
  "or have a remember_token column in your model or implement your own " \
80
80
  "rememberable_value in the model with custom logic."
@@ -338,7 +338,7 @@ module ActionDispatch::Routing
338
338
 
339
339
  # Sets the devise scope to be used in the controller. If you have custom routes,
340
340
  # you are required to call this method (also aliased as :as) in order to specify
341
- # to which controller it is targetted.
341
+ # to which controller it is targeted.
342
342
  #
343
343
  # as :user do
344
344
  # get "sign_in", to: "devise/sessions#new"
@@ -65,7 +65,7 @@ module Devise
65
65
  scope = resource
66
66
  resource = deprecated
67
67
 
68
- ActiveSupport::Deprecation.warn <<-DEPRECATION
68
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
69
69
  [Devise] sign_in(:#{scope}, resource) on controller tests is deprecated and will be removed from Devise.
70
70
  Please use sign_in(resource, scope: :#{scope}) instead.
71
71
  DEPRECATION
@@ -2,7 +2,7 @@ module Devise
2
2
  module TestHelpers
3
3
  def self.included(base)
4
4
  base.class_eval do
5
- ActiveSupport::Deprecation.warn <<-DEPRECATION
5
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
6
6
  [Devise] including `Devise::TestHelpers` is deprecated and will be removed from Devise.
7
7
  For controller tests, please include `Devise::Test::ControllerHelpers` instead.
8
8
  DEPRECATION
@@ -1,3 +1,3 @@
1
1
  module Devise
2
- VERSION = "4.2.0".freeze
2
+ VERSION = "4.2.1".freeze
3
3
  end
@@ -1,6 +1,6 @@
1
1
  class <%= @scope_prefix %>RegistrationsController < Devise::RegistrationsController
2
- # before_action :configure_sign_up_params, only: [:create]
3
- # before_action :configure_account_update_params, only: [:update]
2
+ # before_action :configure_sign_up_params, only: [:create]
3
+ # before_action :configure_account_update_params, only: [:update]
4
4
 
5
5
  # GET /resource/sign_up
6
6
  # def new
@@ -1,5 +1,5 @@
1
1
  class <%= @scope_prefix %>SessionsController < Devise::SessionsController
2
- # before_action :configure_sign_in_params, only: [:create]
2
+ # before_action :configure_sign_in_params, only: [:create]
3
3
 
4
4
  # GET /resource/sign_in
5
5
  # def new
@@ -110,7 +110,10 @@ Devise.setup do |config|
110
110
  # Set up a pepper to generate the hashed password.
111
111
  # config.pepper = '<%= SecureRandom.hex(64) %>'
112
112
 
113
- # Send a notification email when the user's password is changed
113
+ # Send a notification to the original email when the user's email is changed.
114
+ # config.send_email_changed_notification = false
115
+
116
+ # Send a notification email when the user's password is changed.
114
117
  # config.send_password_change_notification = false
115
118
 
116
119
  # ==> Configuration for :confirmable
@@ -0,0 +1,7 @@
1
+ Hello <%= @email %>!
2
+
3
+ <% if @resource.try(:unconfirmed_email?) %>
4
+ We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.
5
+ <% else %>
6
+ We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
7
+ <% end %>
@@ -1,3 +1,3 @@
1
- <p>Hello <%= @resource.email %>!</p>
1
+ Hello <%= @resource.email %>!
2
2
 
3
- <p>We're contacting you to notify you that your password has been changed.</p>
3
+ We're contacting you to notify you that your password has been changed.
@@ -164,8 +164,8 @@ class ControllerAuthenticatableTest < Devise::ControllerTestCase
164
164
  @controller.instance_variable_set(:@current_user, user)
165
165
  @controller.instance_variable_set(:@current_admin, user)
166
166
  @controller.sign_out
167
- assert_equal nil, @controller.instance_variable_get(:@current_user)
168
- assert_equal nil, @controller.instance_variable_get(:@current_admin)
167
+ assert_nil @controller.instance_variable_get(:@current_user)
168
+ assert_nil @controller.instance_variable_get(:@current_admin)
169
169
  end
170
170
 
171
171
  test 'sign out logs out and clears up any signed in user by scope' do
@@ -175,7 +175,7 @@ class ControllerAuthenticatableTest < Devise::ControllerTestCase
175
175
  @mock_warden.expects(:clear_strategies_cache!).with(scope: :user).returns(true)
176
176
  @controller.instance_variable_set(:@current_user, user)
177
177
  @controller.sign_out(:user)
178
- assert_equal nil, @controller.instance_variable_get(:@current_user)
178
+ assert_nil @controller.instance_variable_get(:@current_user)
179
179
  end
180
180
 
181
181
  test 'sign out accepts a resource as argument' do
@@ -245,7 +245,7 @@ class AuthenticationRoutesRestrictions < Devise::IntegrationTest
245
245
  end
246
246
  end
247
247
 
248
- test 'not signed in users should see unautheticated page (unauthenticated accepted)' do
248
+ test 'not signed in users should see unauthenticated page (unauthenticated accepted)' do
249
249
  get join_path
250
250
 
251
251
  assert_response :success
@@ -0,0 +1,130 @@
1
+ require 'test_helper'
2
+
3
+ class EmailChangedTest < ActionMailer::TestCase
4
+ def setup
5
+ setup_mailer
6
+ Devise.mailer = 'Devise::Mailer'
7
+ Devise.mailer_sender = 'test@example.com'
8
+ Devise.send_email_changed_notification = true
9
+ end
10
+
11
+ def teardown
12
+ Devise.mailer = 'Devise::Mailer'
13
+ Devise.mailer_sender = 'please-change-me@config-initializers-devise.com'
14
+ Devise.send_email_changed_notification = false
15
+ end
16
+
17
+ def user
18
+ @user ||= create_user.tap { |u|
19
+ @original_user_email = u.email
20
+ u.update_attributes!(email: 'new-email@example.com')
21
+ }
22
+ end
23
+
24
+ def mail
25
+ @mail ||= begin
26
+ user
27
+ ActionMailer::Base.deliveries.last
28
+ end
29
+ end
30
+
31
+ test 'email sent after changing the user email' do
32
+ assert_not_nil mail
33
+ end
34
+
35
+ test 'content type should be set to html' do
36
+ assert mail.content_type.include?('text/html')
37
+ end
38
+
39
+ test 'send email changed to the original user email' do
40
+ mail
41
+ assert_equal [@original_user_email], mail.to
42
+ end
43
+
44
+ test 'set up sender from configuration' do
45
+ assert_equal ['test@example.com'], mail.from
46
+ end
47
+
48
+ test 'set up sender from custom mailer defaults' do
49
+ Devise.mailer = 'Users::Mailer'
50
+ assert_equal ['custom@example.com'], mail.from
51
+ end
52
+
53
+ test 'set up sender from custom mailer defaults with proc' do
54
+ Devise.mailer = 'Users::FromProcMailer'
55
+ assert_equal ['custom@example.com'], mail.from
56
+ end
57
+
58
+ test 'custom mailer renders parent mailer template' do
59
+ Devise.mailer = 'Users::Mailer'
60
+ assert_present mail.body.encoded
61
+ end
62
+
63
+ test 'set up reply to as copy from sender' do
64
+ assert_equal ['test@example.com'], mail.reply_to
65
+ end
66
+
67
+ test 'set up reply to as different if set in defaults' do
68
+ Devise.mailer = 'Users::ReplyToMailer'
69
+ assert_equal ['custom@example.com'], mail.from
70
+ assert_equal ['custom_reply_to@example.com'], mail.reply_to
71
+ end
72
+
73
+ test 'set up subject from I18n' do
74
+ store_translations :en, devise: { mailer: { email_changed: { subject: 'Email Has Changed' } } } do
75
+ assert_equal 'Email Has Changed', mail.subject
76
+ end
77
+ end
78
+
79
+ test 'subject namespaced by model' do
80
+ store_translations :en, devise: { mailer: { email_changed: { user_subject: 'User Email Has Changed' } } } do
81
+ assert_equal 'User Email Has Changed', mail.subject
82
+ end
83
+ end
84
+
85
+ test 'body should have user info' do
86
+ body = mail.body.encoded
87
+ assert_match "Hello #{@original_user_email}", body
88
+ assert_match "has been changed to #{user.email}", body
89
+ end
90
+ end
91
+
92
+ class EmailChangedReconfirmationTest < ActionMailer::TestCase
93
+ def setup
94
+ setup_mailer
95
+ Devise.mailer = 'Devise::Mailer'
96
+ Devise.mailer_sender = 'test@example.com'
97
+ Devise.send_email_changed_notification = true
98
+ end
99
+
100
+ def teardown
101
+ Devise.mailer = 'Devise::Mailer'
102
+ Devise.mailer_sender = 'please-change-me@config-initializers-devise.com'
103
+ Devise.send_email_changed_notification = false
104
+ end
105
+
106
+ def admin
107
+ @admin ||= create_admin.tap { |u|
108
+ @original_admin_email = u.email
109
+ u.update_attributes!(email: 'new-email@example.com')
110
+ }
111
+ end
112
+
113
+ def mail
114
+ @mail ||= begin
115
+ admin
116
+ ActionMailer::Base.deliveries[-2]
117
+ end
118
+ end
119
+
120
+ test 'send email changed to the original user email' do
121
+ mail
122
+ assert_equal [@original_admin_email], mail.to
123
+ end
124
+
125
+ test 'body should have unconfirmed user info' do
126
+ body = mail.body.encoded
127
+ assert_match admin.email, body
128
+ assert_match "is being changed to #{admin.unconfirmed_email}", body
129
+ end
130
+ end