devise 3.5.2 → 3.5.3

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -3
  3. data/CHANGELOG.md +17 -1
  4. data/CODE_OF_CONDUCT.md +22 -0
  5. data/CONTRIBUTING.md +2 -0
  6. data/Gemfile.lock +2 -2
  7. data/app/controllers/devise/passwords_controller.rb +1 -0
  8. data/app/mailers/devise/mailer.rb +4 -0
  9. data/app/views/devise/mailer/password_change.html.erb +3 -0
  10. data/app/views/devise/shared/_links.html.erb +1 -1
  11. data/config/locales/en.yml +2 -0
  12. data/gemfiles/Gemfile.rails-3.2-stable.lock +5 -2
  13. data/gemfiles/Gemfile.rails-4.0-stable.lock +5 -2
  14. data/gemfiles/Gemfile.rails-4.1-stable.lock +5 -2
  15. data/gemfiles/Gemfile.rails-4.2-stable.lock +5 -2
  16. data/lib/devise.rb +12 -3
  17. data/lib/devise/controllers/helpers.rb +12 -6
  18. data/lib/devise/failure_app.rb +17 -3
  19. data/lib/devise/hooks/timeoutable.rb +2 -1
  20. data/lib/devise/models.rb +1 -1
  21. data/lib/devise/models/confirmable.rb +2 -2
  22. data/lib/devise/models/database_authenticatable.rb +12 -2
  23. data/lib/devise/models/recoverable.rb +2 -6
  24. data/lib/devise/rails/routes.rb +17 -3
  25. data/lib/devise/strategies/authenticatable.rb +1 -1
  26. data/lib/devise/version.rb +1 -1
  27. data/lib/generators/devise/views_generator.rb +14 -3
  28. data/lib/generators/templates/devise.rb +3 -0
  29. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  30. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  31. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  32. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  33. data/test/controllers/helper_methods_test.rb +21 -0
  34. data/test/failure_app_test.rb +17 -0
  35. data/test/generators/views_generator_test.rb +7 -0
  36. data/test/integration/omniauthable_test.rb +11 -9
  37. data/test/integration/timeoutable_test.rb +12 -0
  38. data/test/models/confirmable_test.rb +10 -0
  39. data/test/models/database_authenticatable_test.rb +20 -0
  40. data/test/models/lockable_test.rb +1 -1
  41. data/test/models/recoverable_test.rb +23 -0
  42. data/test/models_test.rb +15 -6
  43. data/test/rails_app/app/active_record/user_without_email.rb +8 -0
  44. data/test/rails_app/app/mongoid/user_without_email.rb +33 -0
  45. data/test/rails_app/config/routes.rb +5 -0
  46. data/test/rails_app/lib/shared_user_without_email.rb +26 -0
  47. data/test/support/helpers.rb +4 -0
  48. metadata +33 -22
@@ -16,10 +16,6 @@ module Devise
16
16
  # # resets the user password and save the record, true if valid passwords are given, otherwise false
17
17
  # User.find(1).reset_password('password123', 'password123')
18
18
  #
19
- # # only resets the user password, without saving the record
20
- # user = User.find(1)
21
- # user.reset_password('password123', 'password123')
22
- #
23
19
  # # creates a new token and send it with instructions about how to reset the password
24
20
  # User.find(1).send_reset_password_instructions
25
21
  #
@@ -31,8 +27,8 @@ module Devise
31
27
  end
32
28
 
33
29
  included do
34
- before_save do
35
- if email_changed? || encrypted_password_changed?
30
+ before_update do
31
+ if (respond_to?(:email_changed?) && email_changed?) || encrypted_password_changed?
36
32
  clear_reset_password_token
37
33
  end
38
34
  end
@@ -94,10 +94,24 @@ module ActionDispatch::Routing
94
94
  #
95
95
  # devise_for :users, path: 'accounts'
96
96
  #
97
- # * singular: setup the singular name for the given resource. This is used as the instance variable
98
- # name in controller, as the name in routes and the scope given to warden.
97
+ # * singular: setup the singular name for the given resource. This is used as the helper methods
98
+ # names in controller ("authenticate_#{singular}!", "#{singular}_signed_in?", "current_#{singular}"
99
+ # and "#{singular}_session"), as the scope name in routes and as the scope given to warden.
99
100
  #
100
- # devise_for :users, singular: :user
101
+ # devise_for :admins, singular: :manager
102
+ #
103
+ # devise_scope :manager do
104
+ # ...
105
+ # end
106
+ #
107
+ # class ManagerController < ApplicationController
108
+ # before_filter authenticate_manager!
109
+ #
110
+ # def show
111
+ # @manager = current_manager
112
+ # ...
113
+ # end
114
+ # end
101
115
  #
102
116
  # * path_names: configure different path names to overwrite defaults :sign_in, :sign_out, :sign_up,
103
117
  # :password, :confirmation, :unlock.
@@ -27,7 +27,7 @@ module Devise
27
27
 
28
28
  # Receives a resource and check if it is valid by calling valid_for_authentication?
29
29
  # An optional block that will be triggered while validating can be optionally
30
- # given as parameter. Check Devise::Models::Authenticable.valid_for_authentication?
30
+ # given as parameter. Check Devise::Models::Authenticatable.valid_for_authentication?
31
31
  # for more information.
32
32
  #
33
33
  # In case the resource can't be validated, it will fail with the given
@@ -1,3 +1,3 @@
1
1
  module Devise
2
- VERSION = "3.5.2".freeze
2
+ VERSION = "3.5.3".freeze
3
3
  end
@@ -47,7 +47,7 @@ module Devise
47
47
  def view_directory(name, _target_path = nil)
48
48
  directory name.to_s, _target_path || "#{target_path}/#{name}" do |content|
49
49
  if scope
50
- content.gsub "devise/shared/links", "#{scope}/shared/links"
50
+ content.gsub "devise/shared/links", "#{plural_scope}/shared/links"
51
51
  else
52
52
  content
53
53
  end
@@ -55,7 +55,11 @@ module Devise
55
55
  end
56
56
 
57
57
  def target_path
58
- @target_path ||= "app/views/#{scope || :devise}"
58
+ @target_path ||= "app/views/#{plural_scope || :devise}"
59
+ end
60
+
61
+ def plural_scope
62
+ @plural_scope ||= scope.presence && scope.underscore.pluralize
59
63
  end
60
64
  end
61
65
 
@@ -83,6 +87,13 @@ module Devise
83
87
  source_root File.expand_path("../../templates/simple_form_for", __FILE__)
84
88
  desc "Copies simple form enabled views to your application."
85
89
  hide!
90
+
91
+ def copy_views
92
+ if options[:views]
93
+ options[:views].delete('mailer')
94
+ end
95
+ super
96
+ end
86
97
  end
87
98
 
88
99
  class ErbGenerator < Rails::Generators::Base #:nodoc:
@@ -111,7 +122,7 @@ module Devise
111
122
  end
112
123
 
113
124
  def target_path
114
- "app/views/#{scope || :devise}/mailer"
125
+ "app/views/#{plural_scope || :devise}/mailer"
115
126
  end
116
127
  end
117
128
 
@@ -105,6 +105,9 @@ Devise.setup do |config|
105
105
  # Setup a pepper to generate the encrypted password.
106
106
  # config.pepper = '<%= SecureRandom.hex(64) %>'
107
107
 
108
+ # Send a notification email when the user's password is changed
109
+ # config.send_password_change_notification = false
110
+
108
111
  # ==> Configuration for :confirmable
109
112
  # A period that the user is allowed to access the website even without
110
113
  # confirming their account. For instance, if set to 2.days, the user will be
@@ -2,4 +2,4 @@ Welcome <%= @email %>!
2
2
 
3
3
  You can confirm your account through the link below:
4
4
 
5
- <%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
5
+ [Confirm my account](<%= confirmation_url(@resource, confirmation_token: @token) %>)
@@ -0,0 +1,3 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>We're contacting you to notify you that your password has been changed.</p>
@@ -2,7 +2,7 @@ Hello <%= @resource.email %>!
2
2
 
3
3
  Someone has requested a link to change your password, and you can do this through the link below.
4
4
 
5
- <%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
5
+ [Change my password](<%= edit_password_url(@resource, reset_password_token: @token) %>)
6
6
 
7
7
  If you didn't request this, please ignore this email.
8
8
  Your password won't change until you access the link above and create a new one.
@@ -4,4 +4,4 @@ Your account has been locked due to an excessive number of unsuccessful sign in
4
4
 
5
5
  Click the link below to unlock your account:
6
6
 
7
- <%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
7
+ [Unlock my account](<%= unlock_url(@resource, unlock_token: @token) %>)
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+
3
+ class ApiController < ActionController::Metal
4
+ include Devise::Controllers::Helpers
5
+ end
6
+
7
+ class HelperMethodsTest < ActionController::TestCase
8
+ tests ApiController
9
+
10
+ test 'includes Devise::Controllers::Helpers' do
11
+ assert_includes @controller.class.ancestors, Devise::Controllers::Helpers
12
+ end
13
+
14
+ test 'does not respond_to helper_method' do
15
+ refute_respond_to @controller.class, :helper_method
16
+ end
17
+
18
+ test 'defines methods like current_user' do
19
+ assert_respond_to @controller, :current_user
20
+ end
21
+ end
@@ -294,5 +294,22 @@ class FailureTest < ActiveSupport::TestCase
294
294
  assert @response.third.body.include?('<h2>Log in</h2>')
295
295
  assert @response.third.body.include?('Your account is not activated yet.')
296
296
  end
297
+
298
+ if Rails.application.config.respond_to?(:relative_url_root)
299
+ test 'calls the original controller with the proper environment considering the relative url root' do
300
+ swap Rails.application.config, relative_url_root: "/sample" do
301
+ env = {
302
+ "warden.options" => { recall: "devise/sessions#new", attempted_path: "/sample/users/sign_in"},
303
+ "devise.mapping" => Devise.mappings[:user],
304
+ "warden" => stub_everything
305
+ }
306
+ call_failure(env)
307
+ assert @response.third.body.include?('<h2>Log in</h2>')
308
+ assert @response.third.body.include?('Invalid email or password.')
309
+ assert_equal @request.env["SCRIPT_NAME"], '/sample'
310
+ assert_equal @request.env["PATH_INFO"], '/users/sign_in'
311
+ end
312
+ end
313
+ end
297
314
  end
298
315
  end
@@ -46,6 +46,13 @@ class ViewsGeneratorTest < Rails::Generators::TestCase
46
46
  assert_no_file "app/views/devise/mailer/confirmation_instructions.html.erb"
47
47
  end
48
48
 
49
+ test "Assert mailer specific directory with simple form" do
50
+ run_generator %w(-v mailer -b simple_form_for)
51
+ assert_file "app/views/devise/mailer/confirmation_instructions.html.erb"
52
+ assert_file "app/views/devise/mailer/reset_password_instructions.html.erb"
53
+ assert_file "app/views/devise/mailer/unlock_instructions.html.erb"
54
+ end
55
+
49
56
  test "Assert specified directories with scope" do
50
57
  run_generator %w(users -v sessions)
51
58
  assert_file "app/views/users/sessions/new.html.erb"
@@ -20,9 +20,11 @@ class OmniauthableIntegrationTest < ActionDispatch::IntegrationTest
20
20
  "credentials" => {"token" => 'plataformatec'},
21
21
  "extra" => {"user_hash" => FACEBOOK_INFO}
22
22
  }
23
+ OmniAuth.config.add_camelization 'facebook', 'FaceBook'
23
24
  end
24
25
 
25
26
  teardown do
27
+ OmniAuth.config.camelizations.delete('facebook')
26
28
  OmniAuth.config.test_mode = false
27
29
  end
28
30
 
@@ -40,7 +42,7 @@ class OmniauthableIntegrationTest < ActionDispatch::IntegrationTest
40
42
 
41
43
  test "can access omniauth.auth in the env hash" do
42
44
  visit "/users/sign_in"
43
- click_link "Sign in with Facebook"
45
+ click_link "Sign in with FaceBook"
44
46
 
45
47
  json = ActiveSupport::JSON.decode(response.body)
46
48
 
@@ -54,7 +56,7 @@ class OmniauthableIntegrationTest < ActionDispatch::IntegrationTest
54
56
  test "cleans up session on sign up" do
55
57
  assert_no_difference "User.count" do
56
58
  visit "/users/sign_in"
57
- click_link "Sign in with Facebook"
59
+ click_link "Sign in with FaceBook"
58
60
  end
59
61
 
60
62
  assert session["devise.facebook_data"]
@@ -75,7 +77,7 @@ class OmniauthableIntegrationTest < ActionDispatch::IntegrationTest
75
77
  test "cleans up session on cancel" do
76
78
  assert_no_difference "User.count" do
77
79
  visit "/users/sign_in"
78
- click_link "Sign in with Facebook"
80
+ click_link "Sign in with FaceBook"
79
81
  end
80
82
 
81
83
  assert session["devise.facebook_data"]
@@ -86,7 +88,7 @@ class OmniauthableIntegrationTest < ActionDispatch::IntegrationTest
86
88
  test "cleans up session on sign in" do
87
89
  assert_no_difference "User.count" do
88
90
  visit "/users/sign_in"
89
- click_link "Sign in with Facebook"
91
+ click_link "Sign in with FaceBook"
90
92
  end
91
93
 
92
94
  assert session["devise.facebook_data"]
@@ -96,13 +98,13 @@ class OmniauthableIntegrationTest < ActionDispatch::IntegrationTest
96
98
 
97
99
  test "sign in and send remember token if configured" do
98
100
  visit "/users/sign_in"
99
- click_link "Sign in with Facebook"
101
+ click_link "Sign in with FaceBook"
100
102
  assert_nil warden.cookies["remember_user_token"]
101
103
 
102
104
  stub_action!(:sign_in_facebook) do
103
105
  create_user
104
106
  visit "/users/sign_in"
105
- click_link "Sign in with Facebook"
107
+ click_link "Sign in with FaceBook"
106
108
  assert warden.authenticated?(:user)
107
109
  assert warden.cookies["remember_user_token"]
108
110
  end
@@ -118,16 +120,16 @@ class OmniauthableIntegrationTest < ActionDispatch::IntegrationTest
118
120
  OmniAuth.config.mock_auth[:facebook] = :access_denied
119
121
  visit "/users/auth/facebook/callback?error=access_denied"
120
122
  assert_current_url "/users/sign_in"
121
- assert_contain 'Could not authenticate you from Facebook because "Access denied".'
123
+ assert_contain 'Could not authenticate you from FaceBook because "Access denied".'
122
124
  end
123
125
 
124
126
  test "handles other exceptions from OmniAuth" do
125
127
  OmniAuth.config.mock_auth[:facebook] = :invalid_credentials
126
128
 
127
129
  visit "/users/sign_in"
128
- click_link "Sign in with Facebook"
130
+ click_link "Sign in with FaceBook"
129
131
 
130
132
  assert_current_url "/users/sign_in"
131
- assert_contain 'Could not authenticate you from Facebook because "Invalid credentials".'
133
+ assert_contain 'Could not authenticate you from FaceBook because "Invalid credentials".'
132
134
  end
133
135
  end
@@ -24,6 +24,18 @@ class SessionTimeoutTest < ActionDispatch::IntegrationTest
24
24
  assert_equal old_last_request, last_request_at
25
25
  end
26
26
 
27
+ test 'does not set last request at in user session after each request if timeoutable is disabled' do
28
+ sign_in_as_user
29
+ old_last_request = last_request_at
30
+ assert_not_nil last_request_at
31
+
32
+ new_time = 2.seconds.from_now
33
+ Time.stubs(:now).returns(new_time)
34
+
35
+ get users_path, {}, 'devise.skip_timeoutable' => true
36
+ assert_equal old_last_request, last_request_at
37
+ end
38
+
27
39
  test 'does not time out user session before default limit time' do
28
40
  sign_in_as_user
29
41
  assert_response :success
@@ -250,6 +250,16 @@ class ConfirmableTest < ActiveSupport::TestCase
250
250
  assert user.reload.active_for_authentication?
251
251
  end
252
252
 
253
+ test 'should not break when a user tries to reset their password in the case where confirmation is not required and confirm_within is set' do
254
+ swap Devise, confirm_within: 3.days do
255
+ user = create_user
256
+ user.instance_eval { def confirmation_required?; false end }
257
+ user.confirmation_sent_at = nil
258
+ user.save
259
+ assert user.reload.confirm!
260
+ end
261
+ end
262
+
253
263
  test 'should find a user to send email instructions for the user confirm its email by authentication_keys' do
254
264
  swap Devise, authentication_keys: [:username, :email] do
255
265
  user = create_user
@@ -3,6 +3,10 @@ require 'test_models'
3
3
  require 'digest/sha1'
4
4
 
5
5
  class DatabaseAuthenticatableTest < ActiveSupport::TestCase
6
+ def setup
7
+ setup_mailer
8
+ end
9
+
6
10
  test 'should downcase case insensitive keys when saving' do
7
11
  # case_insensitive_keys is set to :email by default.
8
12
  email = 'Foo@Bar.com'
@@ -225,6 +229,22 @@ class DatabaseAuthenticatableTest < ActiveSupport::TestCase
225
229
  assert_match "can't be blank", user.errors[:current_password].join
226
230
  end
227
231
 
232
+ test 'should not email on password change' do
233
+ user = create_user
234
+ assert_email_not_sent do
235
+ assert user.update_attributes(password: 'newpass', password_confirmation: 'newpass')
236
+ end
237
+ end
238
+
239
+ test 'should email on password change when configured' do
240
+ swap Devise, send_password_change_notification: true do
241
+ user = create_user
242
+ assert_email_sent user.email do
243
+ assert user.update_attributes(password: 'newpass', password_confirmation: 'newpass')
244
+ end
245
+ end
246
+ end
247
+
228
248
  test 'downcase_keys with validation' do
229
249
  User.create(email: "HEllO@example.com", password: "123456")
230
250
  user = User.create(email: "HEllO@example.com", password: "123456")
@@ -14,7 +14,7 @@ class LockableTest < ActiveSupport::TestCase
14
14
  end
15
15
  end
16
16
 
17
- test "should increment failed_attempts on successfull validation if the user is already locked" do
17
+ test "should increment failed_attempts on successful validation if the user is already locked" do
18
18
  user = create_user
19
19
  user.confirm
20
20
 
@@ -42,6 +42,17 @@ class RecoverableTest < ActiveSupport::TestCase
42
42
  assert_nil user.reset_password_token
43
43
  end
44
44
 
45
+ test 'should not clear reset password token for new user' do
46
+ user = new_user
47
+ assert_nil user.reset_password_token
48
+
49
+ user.send_reset_password_instructions
50
+ assert_present user.reset_password_token
51
+
52
+ user.save
53
+ assert_present user.reset_password_token
54
+ end
55
+
45
56
  test 'should clear reset password token if changing password' do
46
57
  user = create_user
47
58
  assert_nil user.reset_password_token
@@ -65,6 +76,18 @@ class RecoverableTest < ActiveSupport::TestCase
65
76
  assert_nil user.reset_password_token
66
77
  end
67
78
 
79
+ test 'should clear reset password successfully even if there is no email' do
80
+ user = create_user_without_email
81
+ assert_nil user.reset_password_token
82
+
83
+ user.send_reset_password_instructions
84
+ assert_present user.reset_password_token
85
+ user.password = "123456678"
86
+ user.password_confirmation = "123456678"
87
+ user.save!
88
+ assert_nil user.reset_password_token
89
+ end
90
+
68
91
  test 'should not clear reset password token if record is invalid' do
69
92
  user = create_user
70
93
  user.send_reset_password_instructions
@@ -92,13 +92,20 @@ class ActiveRecordTest < ActiveSupport::TestCase
92
92
  end
93
93
  end
94
94
 
95
+ module StubModelFilters
96
+ def stub_filter(name)
97
+ define_singleton_method(name) { |*| nil }
98
+ end
99
+ end
100
+
95
101
  class CheckFieldsTest < ActiveSupport::TestCase
96
102
  test 'checks if the class respond_to the required fields' do
97
103
  Player = Class.new do
98
104
  extend Devise::Models
105
+ extend StubModelFilters
99
106
 
100
- def self.before_validation(instance)
101
- end
107
+ stub_filter :before_validation
108
+ stub_filter :after_update
102
109
 
103
110
  devise :database_authenticatable
104
111
 
@@ -113,9 +120,10 @@ class CheckFieldsTest < ActiveSupport::TestCase
113
120
  test 'raises Devise::Models::MissingAtrribute and shows the missing attribute if the class doesn\'t respond_to one of the attributes' do
114
121
  Clown = Class.new do
115
122
  extend Devise::Models
123
+ extend StubModelFilters
116
124
 
117
- def self.before_validation(instance)
118
- end
125
+ stub_filter :before_validation
126
+ stub_filter :after_update
119
127
 
120
128
  devise :database_authenticatable
121
129
 
@@ -130,9 +138,10 @@ class CheckFieldsTest < ActiveSupport::TestCase
130
138
  test 'raises Devise::Models::MissingAtrribute with all the missing attributes if there is more than one' do
131
139
  Magician = Class.new do
132
140
  extend Devise::Models
141
+ extend StubModelFilters
133
142
 
134
- def self.before_validation(instance)
135
- end
143
+ stub_filter :before_validation
144
+ stub_filter :after_update
136
145
 
137
146
  devise :database_authenticatable
138
147
  end