devise 2.0.6 → 2.1.0.rc

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 (68) hide show
  1. data/.travis.yml +0 -1
  2. data/CHANGELOG.rdoc +12 -6
  3. data/Gemfile.lock +3 -3
  4. data/README.md +9 -15
  5. data/app/controllers/devise_controller.rb +2 -3
  6. data/app/views/devise/_links.erb +3 -25
  7. data/app/views/devise/confirmations/new.html.erb +1 -1
  8. data/app/views/devise/passwords/edit.html.erb +1 -1
  9. data/app/views/devise/passwords/new.html.erb +1 -1
  10. data/app/views/devise/registrations/new.html.erb +1 -1
  11. data/app/views/devise/sessions/new.html.erb +1 -1
  12. data/app/views/devise/shared/_links.erb +25 -3
  13. data/app/views/devise/unlocks/new.html.erb +1 -1
  14. data/config/locales/en.yml +2 -2
  15. data/gemfiles/Gemfile.rails-3.1.x.lock +3 -3
  16. data/lib/devise.rb +1 -0
  17. data/lib/devise/controllers/helpers.rb +1 -1
  18. data/lib/devise/encryptors/base.rb +5 -1
  19. data/lib/devise/encryptors/bcrypt.rb +14 -0
  20. data/lib/devise/failure_app.rb +1 -0
  21. data/lib/devise/models.rb +32 -2
  22. data/lib/devise/models/authenticatable.rb +15 -10
  23. data/lib/devise/models/confirmable.rb +21 -1
  24. data/lib/devise/models/database_authenticatable.rb +11 -5
  25. data/lib/devise/models/encryptable.rb +17 -9
  26. data/lib/devise/models/lockable.rb +17 -1
  27. data/lib/devise/models/omniauthable.rb +4 -0
  28. data/lib/devise/models/recoverable.rb +4 -0
  29. data/lib/devise/models/registerable.rb +4 -0
  30. data/lib/devise/models/rememberable.rb +6 -2
  31. data/lib/devise/models/timeoutable.rb +4 -0
  32. data/lib/devise/models/token_authenticatable.rb +5 -0
  33. data/lib/devise/models/trackable.rb +4 -0
  34. data/lib/devise/models/validatable.rb +4 -0
  35. data/lib/devise/param_filter.rb +2 -1
  36. data/lib/devise/rails/routes.rb +3 -2
  37. data/lib/devise/strategies/authenticatable.rb +10 -4
  38. data/lib/devise/version.rb +1 -1
  39. data/lib/generators/devise/views_generator.rb +15 -6
  40. data/lib/generators/templates/devise.rb +1 -1
  41. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +1 -1
  42. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +1 -1
  43. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +1 -1
  44. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +1 -1
  45. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +1 -1
  46. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +1 -1
  47. data/test/generators/views_generator_test.rb +1 -1
  48. data/test/integration/omniauthable_test.rb +2 -2
  49. data/test/integration/recoverable_test.rb +9 -0
  50. data/test/models/authenticatable_test.rb +3 -5
  51. data/test/models/confirmable_test.rb +26 -0
  52. data/test/models/database_authenticatable_test.rb +29 -7
  53. data/test/models/encryptable_test.rb +6 -0
  54. data/test/models/lockable_test.rb +34 -0
  55. data/test/models/omniauthable_test.rb +7 -0
  56. data/test/models/recoverable_test.rb +8 -1
  57. data/test/models/registerable_test.rb +7 -0
  58. data/test/models/rememberable_test.rb +8 -1
  59. data/test/models/timeoutable_test.rb +4 -0
  60. data/test/models/token_authenticatable_test.rb +6 -0
  61. data/test/models/trackable_test.rb +9 -1
  62. data/test/models/validatable_test.rb +5 -1
  63. data/test/models_test.rb +70 -0
  64. data/test/rails_app/config/routes.rb +4 -0
  65. data/test/routes_test.rb +4 -0
  66. data/test/support/assertions.rb +15 -0
  67. data/test/support/helpers.rb +21 -0
  68. metadata +19 -34
@@ -36,6 +36,15 @@ module Devise
36
36
  after_update :send_confirmation_instructions, :if => :reconfirmation_required?
37
37
  end
38
38
 
39
+ def self.required_fields(klass)
40
+ required_methods = [:confirmation_token, :confirmed_at, :confirmation_sent_at]
41
+ if klass.reconfirmable
42
+ required_methods << :unconfirmed_email
43
+ end
44
+
45
+ required_methods
46
+ end
47
+
39
48
  # Confirm a user by setting it's confirmed_at to actual time. If the user
40
49
  # is already confirmed, add an error to email field. If the user is invalid
41
50
  # add errors
@@ -45,7 +54,7 @@ module Devise
45
54
  self.confirmed_at = Time.now.utc
46
55
 
47
56
  if self.class.reconfirmable && unconfirmed_email.present?
48
- @bypass_postpone = true
57
+ skip_reconfirmation!
49
58
  self.email = unconfirmed_email
50
59
  self.unconfirmed_email = nil
51
60
 
@@ -99,6 +108,12 @@ module Devise
99
108
  self.confirmed_at = Time.now.utc
100
109
  end
101
110
 
111
+ # If you don't want reconfirmation to be sent, neither a code
112
+ # to be generated, call skip_reconfirmation!
113
+ def skip_reconfirmation!
114
+ @bypass_postpone = true
115
+ end
116
+
102
117
  def headers_for(action)
103
118
  headers = super
104
119
  if action == :confirmation_instructions && pending_reconfirmation?
@@ -165,6 +180,11 @@ module Devise
165
180
  generate_confirmation_token && save(:validate => false)
166
181
  end
167
182
 
183
+ def after_password_reset
184
+ super
185
+ confirm! unless confirmed?
186
+ end
187
+
168
188
  def postpone_email_change_until_confirmation
169
189
  @reconfirmation_required = true
170
190
  self.unconfirmed_email = self.email
@@ -27,6 +27,10 @@ module Devise
27
27
  attr_accessor :password_confirmation
28
28
  end
29
29
 
30
+ def self.required_fields(klass)
31
+ [:encrypted_password] + klass.authentication_keys
32
+ end
33
+
30
34
  # Generates password encryption based on the given value.
31
35
  def password=(new_password)
32
36
  @password = new_password
@@ -36,9 +40,7 @@ module Devise
36
40
  # Verifies whether an password (ie from sign in) is the user password.
37
41
  def valid_password?(password)
38
42
  return false if encrypted_password.blank?
39
- bcrypt = ::BCrypt::Password.new(self.encrypted_password)
40
- password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
41
- Devise.secure_compare(password, self.encrypted_password)
43
+ encryptor_class.compare(encrypted_password, password, self.class.stretches, authenticatable_salt, self.class.pepper)
42
44
  end
43
45
 
44
46
  # Set password and password confirmation to nil
@@ -96,14 +98,18 @@ module Devise
96
98
 
97
99
  # A reliable way to expose the salt regardless of the implementation.
98
100
  def authenticatable_salt
99
- self.encrypted_password[0,29] if self.encrypted_password
101
+ encrypted_password[0,29] if encrypted_password
100
102
  end
101
103
 
102
104
  protected
103
105
 
104
106
  # Digests the password using bcrypt.
105
107
  def password_digest(password)
106
- ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
108
+ encryptor_class.digest(password, self.class.stretches, ::BCrypt::Engine.generate_salt, self.class.pepper)
109
+ end
110
+
111
+ def encryptor_class
112
+ Devise::Encryptors::BCrypt
107
113
  end
108
114
 
109
115
  module ClassMethods
@@ -2,7 +2,8 @@ require 'devise/strategies/database_authenticatable'
2
2
 
3
3
  module Devise
4
4
  module Models
5
- # Encryptable Module adds support to several encryptors.
5
+ # Encryptable module adds support to several encryptors wrapping
6
+ # them in a salt and pepper mechanism to increase security.
6
7
  #
7
8
  # == Options
8
9
  #
@@ -24,30 +25,37 @@ module Devise
24
25
  attr_accessor :password_confirmation
25
26
  end
26
27
 
27
- # Generates password salt.
28
+ def self.required_fields(klass)
29
+ [:password_salt]
30
+ end
31
+
32
+ # Generates password salt when setting the password.
28
33
  def password=(new_password)
29
34
  self.password_salt = self.class.password_salt if new_password.present?
30
35
  super
31
36
  end
32
37
 
38
+ # Overrides authenticatable salt to use the new password_salt
39
+ # column. authenticatable_salt is used by `valid_password?`
40
+ # and by other modules whenever there is a need for a random
41
+ # token based on the user password.
33
42
  def authenticatable_salt
34
43
  self.password_salt
35
44
  end
36
45
 
37
- # Verifies whether an incoming_password (ie from sign in) is the user password.
38
- def valid_password?(incoming_password)
39
- Devise.secure_compare(password_digest(incoming_password), self.encrypted_password)
40
- end
41
-
42
46
  protected
43
47
 
44
48
  # Digests the password using the configured encryptor.
45
49
  def password_digest(password)
46
- if self.password_salt.present?
47
- self.class.encryptor_class.digest(password, self.class.stretches, self.password_salt, self.class.pepper)
50
+ if password_salt.present?
51
+ encryptor_class.digest(password, self.class.stretches, authenticatable_salt, self.class.pepper)
48
52
  end
49
53
  end
50
54
 
55
+ def encryptor_class
56
+ self.class.encryptor_class
57
+ end
58
+
51
59
  module ClassMethods
52
60
  Devise::Models.config(self, :encryptor)
53
61
 
@@ -22,6 +22,15 @@ module Devise
22
22
 
23
23
  delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :to => "self.class"
24
24
 
25
+ def self.required_fields(klass)
26
+ attributes = []
27
+ attributes << :failed_attempts if klass.lock_strategy_enabled?(:failed_attempts)
28
+ attributes << :unlock_at if klass.unlock_strategy_enabled?(:time)
29
+ attributes << :unlock_token if klass.unlock_strategy_enabled?(:email)
30
+
31
+ attributes
32
+ end
33
+
25
34
  # Lock a user setting its locked_at to actual time.
26
35
  def lock_access!
27
36
  self.locked_at = Time.now.utc
@@ -88,7 +97,6 @@ module Devise
88
97
  self.failed_attempts += 1
89
98
  if attempts_exceeded?
90
99
  lock_access! unless access_locked?
91
- return :locked
92
100
  else
93
101
  save(:validate => false)
94
102
  end
@@ -96,6 +104,14 @@ module Devise
96
104
  end
97
105
  end
98
106
 
107
+ def unauthenticated_message
108
+ if lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?
109
+ :locked
110
+ else
111
+ super
112
+ end
113
+ end
114
+
99
115
  protected
100
116
 
101
117
  def attempts_exceeded?
@@ -15,6 +15,10 @@ module Devise
15
15
  module Omniauthable
16
16
  extend ActiveSupport::Concern
17
17
 
18
+ def self.required_fields(klass)
19
+ []
20
+ end
21
+
18
22
  module ClassMethods
19
23
  Devise::Models.config(self, :omniauth_providers)
20
24
  end
@@ -24,6 +24,10 @@ module Devise
24
24
  module Recoverable
25
25
  extend ActiveSupport::Concern
26
26
 
27
+ def self.required_fields(klass)
28
+ [:reset_password_sent_at, :reset_password_token]
29
+ end
30
+
27
31
  # Update password saving the record and clearing token. Returns true if
28
32
  # the passwords are valid and the record was saved, false otherwise.
29
33
  def reset_password!(new_password, new_password_confirmation)
@@ -5,6 +5,10 @@ module Devise
5
5
  module Registerable
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ def self.required_fields(klass)
9
+ []
10
+ end
11
+
8
12
  module ClassMethods
9
13
  # A convenience method that receives both parameters and session to
10
14
  # initialize a user. This can be used by OAuth, for example, to send
@@ -24,7 +24,7 @@ module Devise
24
24
  # * +extend_remember_period+: if true, extends the user's remember period
25
25
  # when remembered via cookie. False by default.
26
26
  #
27
- # * +cookie_options+: configuration options passed to the created cookie.
27
+ # * +rememberable_options+: configuration options passed to the created cookie.
28
28
  #
29
29
  # == Examples
30
30
  #
@@ -41,6 +41,10 @@ module Devise
41
41
 
42
42
  attr_accessor :remember_me, :extend_remember_period
43
43
 
44
+ def self.required_fields(klass)
45
+ [:remember_created_at, :remember_token]
46
+ end
47
+
44
48
  # Generate a new remember token and save the record without validations
45
49
  # unless remember_across_browsers is true and the user already has a valid token.
46
50
  def remember_me!(extend_period=false)
@@ -71,7 +75,7 @@ module Devise
71
75
  def rememberable_value
72
76
  if respond_to?(:remember_token)
73
77
  remember_token
74
- elsif salt = authenticatable_salt
78
+ elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt)
75
79
  salt
76
80
  else
77
81
  raise "authenticable_salt returned nil for the #{self.class.name} model. " \
@@ -20,6 +20,10 @@ module Devise
20
20
  module Timeoutable
21
21
  extend ActiveSupport::Concern
22
22
 
23
+ def self.required_fields(klass)
24
+ []
25
+ end
26
+
23
27
  # Checks whether the user session has expired based on configured time.
24
28
  def timedout?(last_access)
25
29
  return false if remember_exists_and_not_expired?
@@ -27,6 +27,10 @@ module Devise
27
27
  module TokenAuthenticatable
28
28
  extend ActiveSupport::Concern
29
29
 
30
+ def self.required_fields(klass)
31
+ [:authentication_token]
32
+ end
33
+
30
34
  # Generate new authentication token (a.k.a. "single access token").
31
35
  def reset_authentication_token
32
36
  self.authentication_token = self.class.authentication_token
@@ -52,6 +56,7 @@ module Devise
52
56
  def after_token_authentication
53
57
  end
54
58
 
59
+
55
60
  module ClassMethods
56
61
  def find_for_token_authentication(conditions)
57
62
  find_for_authentication(:authentication_token => conditions[token_authentication_key])
@@ -11,6 +11,10 @@ module Devise
11
11
  # * last_sign_in_ip - Holds the remote ip of the previous sign in
12
12
  #
13
13
  module Trackable
14
+ def self.required_fields(klass)
15
+ [:current_sign_in_at, :current_sign_in_ip, :last_sign_in_at, :last_sign_in_ip, :sign_in_count]
16
+ end
17
+
14
18
  def update_tracked_fields!(request)
15
19
  old_current, new_current = self.current_sign_in_at, Time.now.utc
16
20
  self.last_sign_in_at = old_current || new_current
@@ -17,6 +17,10 @@ module Devise
17
17
  VALIDATIONS = [ :validates_presence_of, :validates_uniqueness_of, :validates_format_of,
18
18
  :validates_confirmation_of, :validates_length_of ].freeze
19
19
 
20
+ def self.required_fields(klass)
21
+ []
22
+ end
23
+
20
24
  def self.included(base)
21
25
  base.extend ClassMethods
22
26
  assert_validations_api!(base)
@@ -33,8 +33,9 @@ module Devise
33
33
 
34
34
  private
35
35
 
36
+ # Determine which values should be transformed to string or passed as-is to the query builder underneath
36
37
  def param_requires_string_conversion?(value)
37
- true
38
+ [Fixnum, TrueClass, FalseClass, Regexp].none? {|clz| value.is_a? clz }
38
39
  end
39
40
  end
40
41
  end
@@ -184,7 +184,7 @@ module ActionDispatch::Routing
184
184
  #
185
185
  # In order to get Devise to recognize the deactivate action, your devise_for entry should look like this,
186
186
  #
187
- # devise_for :owners, :controllers => { :registrations => "registrations" } do
187
+ # devise_scope :owner do
188
188
  # post "deactivate", :to => "registrations#deactivate", :as => "deactivate_registration"
189
189
  # end
190
190
  #
@@ -198,7 +198,8 @@ module ActionDispatch::Routing
198
198
  options[:path_names] = (@scope[:path_names] || {}).merge(options[:path_names] || {})
199
199
  options[:constraints] = (@scope[:constraints] || {}).merge(options[:constraints] || {})
200
200
  options[:defaults] = (@scope[:defaults] || {}).merge(options[:defaults] || {})
201
- options[:options] = (@scope[:options] || {}).merge({:format => false}) if options[:format] == false
201
+ options[:options] = @scope[:options] || {}
202
+ options[:options][:format] = false if options[:format] == false
202
203
 
203
204
  resources.map!(&:to_sym)
204
205
 
@@ -23,14 +23,20 @@ module Devise
23
23
  result = resource && resource.valid_for_authentication?(&block)
24
24
 
25
25
  case result
26
- when String, Symbol
26
+ when Symbol, String
27
+ ActiveSupport::Deprecation.warn "valid_for_authentication should return a boolean value"
27
28
  fail!(result)
28
- false
29
- when TrueClass
29
+ return false
30
+ end
31
+
32
+ if result
30
33
  decorate(resource)
31
34
  true
32
35
  else
33
- result
36
+ if resource
37
+ fail!(resource.unauthenticated_message)
38
+ end
39
+ false
34
40
  end
35
41
  end
36
42
 
@@ -1,3 +1,3 @@
1
1
  module Devise
2
- VERSION = "2.0.6".freeze
2
+ VERSION = "2.1.0.rc".freeze
3
3
  end
@@ -39,6 +39,18 @@ module Devise
39
39
  end
40
40
  end
41
41
 
42
+ class SharedViewsGenerator < Rails::Generators::Base #:nodoc:
43
+ include ViewPathTemplates
44
+ source_root File.expand_path("../../../../app/views/devise", __FILE__)
45
+ desc "Copies shared Devise views to your application."
46
+ hide!
47
+
48
+ # Override copy_views to just copy mailer and shared.
49
+ def copy_views
50
+ view_directory :shared
51
+ end
52
+ end
53
+
42
54
  class FormForGenerator < Rails::Generators::Base #:nodoc:
43
55
  include ViewPathTemplates
44
56
  source_root File.expand_path("../../../../app/views/devise", __FILE__)
@@ -80,15 +92,12 @@ module Devise
80
92
  end
81
93
 
82
94
  class ViewsGenerator < Rails::Generators::Base
83
- include ViewPathTemplates
84
-
85
- source_root File.expand_path("../../../../app/views/devise", __FILE__)
86
95
  desc "Copies Devise views to your application."
87
96
 
88
- def copy_views
89
- copy_file "_links.erb", "#{target_path}/_links.erb"
90
- end
97
+ argument :scope, :required => false, :default => nil,
98
+ :desc => "The scope to copy views to"
91
99
 
100
+ invoke SharedViewsGenerator
92
101
  hook_for :form_builder, :aliases => "-b",
93
102
  :desc => "Form builder to be used",
94
103
  :default => defined?(SimpleForm) ? "simple_form_for" : "form_for"
@@ -117,7 +117,7 @@ Devise.setup do |config|
117
117
 
118
118
  # Options to be passed to the created cookie. For instance, you can set
119
119
  # :secure => true in order to force SSL only cookies.
120
- # config.cookie_options = {}
120
+ # config.rememberable_options = {}
121
121
 
122
122
  # ==> Configuration for :validatable
123
123
  # Range for password length. Default is 6..128.
@@ -12,4 +12,4 @@
12
12
  </div>
13
13
  <% end %>
14
14
 
15
- <%= render "links" %>
15
+ <%= render :partial => "devise/shared/links" %>
@@ -16,4 +16,4 @@
16
16
  </div>
17
17
  <% end %>
18
18
 
19
- <%= render "links" %>
19
+ <%= render :partial => "devise/shared/links" %>
@@ -12,4 +12,4 @@
12
12
  </div>
13
13
  <% end %>
14
14
 
15
- <%= render "links" %>
15
+ <%= render :partial => "devise/shared/links" %>
@@ -14,4 +14,4 @@
14
14
  </div>
15
15
  <% end %>
16
16
 
17
- <%= render "links" %>
17
+ <%= render :partial => "devise/shared/links" %>