devise 1.4.9 → 1.5.0.rc1

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 (70) hide show
  1. data/.travis.yml +1 -1
  2. data/CHANGELOG.rdoc +21 -0
  3. data/Gemfile +5 -3
  4. data/README.rdoc +25 -13
  5. data/app/controllers/devise/confirmations_controller.rb +2 -3
  6. data/app/controllers/devise/passwords_controller.rb +2 -3
  7. data/app/controllers/devise/registrations_controller.rb +2 -13
  8. data/app/controllers/devise/sessions_controller.rb +2 -2
  9. data/app/controllers/devise/unlocks_controller.rb +2 -3
  10. data/config/locales/en.yml +1 -1
  11. data/devise.gemspec +1 -1
  12. data/lib/devise.rb +6 -4
  13. data/lib/devise/controllers/helpers.rb +43 -27
  14. data/lib/devise/controllers/internal_helpers.rb +14 -8
  15. data/lib/devise/delegator.rb +16 -0
  16. data/lib/devise/encryptors/authlogic_sha512.rb +1 -1
  17. data/lib/devise/encryptors/clearance_sha1.rb +1 -1
  18. data/lib/devise/encryptors/restful_authentication_sha1.rb +1 -1
  19. data/lib/devise/encryptors/sha1.rb +1 -1
  20. data/lib/devise/encryptors/sha512.rb +1 -1
  21. data/lib/devise/failure_app.rb +2 -1
  22. data/lib/devise/hooks/timeoutable.rb +3 -1
  23. data/lib/devise/mailers/helpers.rb +0 -5
  24. data/lib/devise/mapping.rb +70 -44
  25. data/lib/devise/models/authenticatable.rb +14 -24
  26. data/lib/devise/models/confirmable.rb +3 -3
  27. data/lib/devise/models/database_authenticatable.rb +11 -1
  28. data/lib/devise/models/lockable.rb +7 -11
  29. data/lib/devise/models/recoverable.rb +3 -3
  30. data/lib/devise/models/trackable.rb +2 -2
  31. data/lib/devise/omniauth.rb +5 -4
  32. data/lib/devise/omniauth/config.rb +27 -5
  33. data/lib/devise/param_filter.rb +41 -0
  34. data/lib/devise/rails.rb +0 -11
  35. data/lib/devise/rails/routes.rb +10 -7
  36. data/lib/devise/strategies/authenticatable.rb +1 -11
  37. data/lib/devise/version.rb +1 -1
  38. data/lib/generators/active_record/templates/migration.rb +7 -1
  39. data/lib/generators/active_record/templates/migration_existing.rb +3 -3
  40. data/lib/generators/devise/views_generator.rb +30 -4
  41. data/lib/generators/templates/devise.rb +0 -1
  42. data/lib/generators/templates/markerb/confirmation_instructions.markerb +5 -0
  43. data/lib/generators/templates/markerb/reset_password_instructions.markerb +8 -0
  44. data/lib/generators/templates/markerb/unlock_instructions.markerb +7 -0
  45. data/test/controllers/helpers_test.rb +20 -11
  46. data/test/devise_test.rb +1 -1
  47. data/test/generators/active_record_generator_test.rb +16 -6
  48. data/test/generators/views_generator_test.rb +11 -4
  49. data/test/integration/authenticatable_test.rb +25 -3
  50. data/test/integration/confirmable_test.rb +27 -3
  51. data/test/integration/lockable_test.rb +17 -6
  52. data/test/integration/omniauthable_test.rb +6 -9
  53. data/test/integration/recoverable_test.rb +21 -2
  54. data/test/integration/registerable_test.rb +18 -1
  55. data/test/integration/timeoutable_test.rb +9 -0
  56. data/test/integration/trackable_test.rb +11 -0
  57. data/test/mailers/confirmation_instructions_test.rb +5 -0
  58. data/test/mailers/reset_password_instructions_test.rb +5 -0
  59. data/test/mailers/unlock_instructions_test.rb +5 -0
  60. data/test/models/database_authenticatable_test.rb +2 -19
  61. data/test/omniauth/config_test.rb +56 -0
  62. data/test/omniauth/my_other_strategy.rb +5 -0
  63. data/test/omniauth/omniauth-my_strategy.rb +5 -0
  64. data/test/omniauth/url_helpers_test.rb +4 -4
  65. data/test/rails_app/config/environments/development.rb +0 -1
  66. data/test/rails_app/config/initializers/devise.rb +2 -2
  67. data/test/rails_app/config/routes.rb +4 -4
  68. data/test/rails_app/lib/shared_admin.rb +1 -0
  69. data/test/support/helpers.rb +27 -0
  70. metadata +54 -77
@@ -91,6 +91,7 @@ MESSAGE
91
91
  # Example:
92
92
  # before_filter :require_no_authentication, :only => :new
93
93
  def require_no_authentication
94
+ return unless is_navigational_format?
94
95
  no_input = devise_mapping.no_input_strategies
95
96
  args = no_input.dup.push :scope => resource_name
96
97
  if no_input.present? && warden.authenticate?(*args)
@@ -100,15 +101,20 @@ MESSAGE
100
101
  end
101
102
  end
102
103
 
103
- # Helper for use to validate if an resource is errorless. If we are on paranoid mode, we always should assume it is
104
- # and return false.
105
- def successful_and_sane?(resource)
106
- if Devise.paranoid
107
- set_flash_message :notice, :send_paranoid_instructions if is_navigational_format?
104
+ # Helper for use after calling send_*_instructions methods on a resource.
105
+ # If we are in paranoid mode, we always act as if the resource was valid
106
+ # and instructions were sent.
107
+ def successfully_sent?(resource)
108
+ notice = if Devise.paranoid
108
109
  resource.errors.clear
109
- false
110
- else
111
- resource.errors.empty?
110
+ :send_paranoid_instructions
111
+ elsif resource.errors.empty?
112
+ :send_instructions
113
+ end
114
+
115
+ if notice
116
+ set_flash_message :notice, notice if is_navigational_format?
117
+ true
112
118
  end
113
119
  end
114
120
 
@@ -0,0 +1,16 @@
1
+ module Devise
2
+ # Checks the scope in the given environment and returns the associated failure app.
3
+ class Delegator
4
+ def call(env)
5
+ failure_app(env).call(env)
6
+ end
7
+
8
+ def failure_app(env)
9
+ app = env["warden.options"] &&
10
+ (scope = env["warden.options"][:scope]) &&
11
+ Devise.mappings[scope].failure_app
12
+
13
+ app || Devise::FailureApp
14
+ end
15
+ end
16
+ end
@@ -7,7 +7,7 @@ module Devise
7
7
  # Warning: it uses Devise's stretches configuration to port Authlogic's one. Should be set to 20 in the initializer to simulate
8
8
  # the default behavior.
9
9
  class AuthlogicSha512 < Base
10
- # Gererates a default password digest based on salt, pepper and the
10
+ # Generates a default password digest based on salt, pepper and the
11
11
  # incoming password.
12
12
  def self.digest(password, stretches, salt, pepper)
13
13
  digest = [password, salt].flatten.join('')
@@ -7,7 +7,7 @@ module Devise
7
7
  # Warning: it uses Devise's pepper to port the concept of REST_AUTH_SITE_KEY
8
8
  # Warning: it uses Devise's stretches configuration to port the concept of REST_AUTH_DIGEST_STRETCHES
9
9
  class ClearanceSha1 < Base
10
- # Gererates a default password digest based on salt, pepper and the
10
+ # Generates a default password digest based on salt, pepper and the
11
11
  # incoming password.
12
12
  def self.digest(password, stretches, salt, pepper)
13
13
  Digest::SHA1.hexdigest("--#{salt}--#{password}--")
@@ -9,7 +9,7 @@ module Devise
9
9
  # the initializer to simulate the default behavior.
10
10
  class RestfulAuthenticationSha1 < Base
11
11
 
12
- # Gererates a default password digest based on salt, pepper and the
12
+ # Generates a default password digest based on salt, pepper and the
13
13
  # incoming password.
14
14
  def self.digest(password, stretches, salt, pepper)
15
15
  digest = pepper
@@ -5,7 +5,7 @@ module Devise
5
5
  # = Sha1
6
6
  # Uses the Sha1 hash algorithm to encrypt passwords.
7
7
  class Sha1 < Base
8
- # Gererates a default password digest based on stretches, salt, pepper and the
8
+ # Generates a default password digest based on stretches, salt, pepper and the
9
9
  # incoming password.
10
10
  def self.digest(password, stretches, salt, pepper)
11
11
  digest = pepper
@@ -5,7 +5,7 @@ module Devise
5
5
  # = Sha512
6
6
  # Uses the Sha512 hash algorithm to encrypt passwords.
7
7
  class Sha512 < Base
8
- # Gererates a default password digest based on salt, pepper and the
8
+ # Generates a default password digest based on salt, pepper and the
9
9
  # incoming password.
10
10
  def self.digest(password, stretches, salt, pepper)
11
11
  digest = pepper
@@ -15,7 +15,8 @@ module Devise
15
15
  delegate :flash, :to => :request
16
16
 
17
17
  def self.call(env)
18
- action(:respond).call(env)
18
+ @respond ||= action(:respond)
19
+ @respond.call(env)
19
20
  end
20
21
 
21
22
  def self.default_url_options(*args)
@@ -17,6 +17,8 @@ Warden::Manager.after_set_user do |record, warden, options|
17
17
  end
18
18
  end
19
19
 
20
- warden.session(scope)['last_request_at'] = Time.now.utc
20
+ unless warden.request.env['devise.skip_trackable']
21
+ warden.session(scope)['last_request_at'] = Time.now.utc
22
+ end
21
23
  end
22
24
  end
@@ -10,11 +10,6 @@ module Devise
10
10
 
11
11
  protected
12
12
 
13
- def setup_mail(*args)
14
- ActiveSupport::Deprecation.warn "setup_mail is deprecated, please use devise_mail instead", caller
15
- devise_mail(*args)
16
- end
17
-
18
13
  # Configure default email options
19
14
  def devise_mail(record, action)
20
15
  initialize_from_record(record)
@@ -23,7 +23,9 @@ module Devise
23
23
  #
24
24
  class Mapping #:nodoc:
25
25
  attr_reader :singular, :scoped_path, :path, :controllers, :path_names,
26
- :class_name, :sign_out_via, :format, :used_routes, :used_helpers
26
+ :class_name, :sign_out_via, :format, :used_routes, :used_helpers,
27
+ :constraints, :defaults, :failure_app
28
+
27
29
  alias :name :singular
28
30
 
29
31
  # Receives an object and find a scope for it. If a scope cannot be found,
@@ -51,46 +53,21 @@ module Devise
51
53
  @singular = (options[:singular] || @scoped_path.tr('/', '_').singularize).to_sym
52
54
 
53
55
  @class_name = (options[:class_name] || name.to_s.classify).to_s
54
- @ref = Devise.ref(@class_name)
56
+ @klass = Devise.ref(@class_name)
55
57
 
56
58
  @path = (options[:path] || name).to_s
57
59
  @path_prefix = options[:path_prefix]
58
60
 
59
- mod = options[:module] || "devise"
60
- @controllers = Hash.new { |h,k| h[k] = "#{mod}/#{k}" }
61
- @controllers.merge!(options[:controllers] || {})
62
- @controllers.each { |k,v| @controllers[k] = v.to_s }
63
-
64
- @path_names = Hash.new { |h,k| h[k] = k.to_s }
65
- @path_names.merge!(:registration => "")
66
- @path_names.merge!(options[:path_names] || {})
67
-
68
- @constraints = Hash.new { |h,k| h[k] = k.to_s }
69
- @constraints.merge!(options[:constraints] || {})
70
-
71
- @defaults = Hash.new { |h,k| h[k] = k.to_s }
72
- @defaults.merge!(options[:defaults] || {})
73
-
74
61
  @sign_out_via = options[:sign_out_via] || Devise.sign_out_via
75
62
  @format = options[:format]
76
63
 
77
- singularizer = lambda { |s| s.to_s.singularize.to_sym }
78
-
79
- if options.has_key?(:only)
80
- @used_routes = self.routes & Array(options[:only]).map(&singularizer)
81
- elsif options[:skip] == :all
82
- @used_routes = []
83
- else
84
- @used_routes = self.routes - Array(options[:skip]).map(&singularizer)
85
- end
86
-
87
- if options[:skip_helpers] == true
88
- @used_helpers = @used_routes
89
- elsif skip = options[:skip_helpers]
90
- @used_helpers = self.routes - Array(skip).map(&singularizer)
91
- else
92
- @used_helpers = self.routes
93
- end
64
+ default_failure_app(options)
65
+ default_controllers(options)
66
+ default_path_names(options)
67
+ default_constraints(options)
68
+ default_defaults(options)
69
+ default_used_route(options)
70
+ default_used_helpers(options)
94
71
  end
95
72
 
96
73
  # Return modules for the mapping.
@@ -100,7 +77,7 @@ module Devise
100
77
 
101
78
  # Gives the class the mapping points to.
102
79
  def to
103
- @ref.get
80
+ @klass.get
104
81
  end
105
82
 
106
83
  def strategies
@@ -122,15 +99,7 @@ module Devise
122
99
  def fullpath
123
100
  "/#{@path_prefix}/#{@path}".squeeze("/")
124
101
  end
125
-
126
- def constraints
127
- @constraints
128
- end
129
-
130
- def defaults
131
- @defaults
132
- end
133
-
102
+
134
103
  # Create magic predicates for verifying what module is activated by this map.
135
104
  # Example:
136
105
  #
@@ -145,5 +114,62 @@ module Devise
145
114
  end
146
115
  METHOD
147
116
  end
117
+
118
+ private
119
+
120
+ def default_failure_app(options)
121
+ @failure_app = options[:failure_app] || Devise::FailureApp
122
+ if @failure_app.is_a?(String)
123
+ ref = Devise.ref(@failure_app)
124
+ @failure_app = lambda { |env| ref.get.call(env) }
125
+ end
126
+ end
127
+
128
+ def default_controllers(options)
129
+ mod = options[:module] || "devise"
130
+ @controllers = Hash.new { |h,k| h[k] = "#{mod}/#{k}" }
131
+ @controllers.merge!(options[:controllers]) if options[:controllers]
132
+ @controllers.each { |k,v| @controllers[k] = v.to_s }
133
+ end
134
+
135
+ def default_path_names(options)
136
+ @path_names = Hash.new { |h,k| h[k] = k.to_s }
137
+ @path_names[:registration] = ""
138
+ @path_names.merge!(options[:path_names]) if options[:path_names]
139
+ end
140
+
141
+ def default_constraints(options)
142
+ @constraints = Hash.new
143
+ @constraints.merge!(options[:constraints]) if options[:constraints]
144
+ end
145
+
146
+ def default_defaults(options)
147
+ @defaults = Hash.new
148
+ @defaults.merge!(options[:defaults]) if options[:defaults]
149
+ end
150
+
151
+ def default_used_route(options)
152
+ singularizer = lambda { |s| s.to_s.singularize.to_sym }
153
+
154
+ if options.has_key?(:only)
155
+ @used_routes = self.routes & Array(options[:only]).map(&singularizer)
156
+ elsif options[:skip] == :all
157
+ @used_routes = []
158
+ else
159
+ @used_routes = self.routes - Array(options[:skip]).map(&singularizer)
160
+ end
161
+ end
162
+
163
+ def default_used_helpers(options)
164
+ singularizer = lambda { |s| s.to_s.singularize.to_sym }
165
+
166
+ if options[:skip_helpers] == true
167
+ @used_helpers = @used_routes
168
+ elsif skip = options[:skip_helpers]
169
+ @used_helpers = self.routes - Array(skip).map(&singularizer)
170
+ else
171
+ @used_helpers = self.routes
172
+ end
173
+ end
148
174
  end
149
175
  end
@@ -27,7 +27,7 @@ module Devise
27
27
  #
28
28
  # == active_for_authentication?
29
29
  #
30
- # Before authenticating a user and in each request, Devise checks if your model is active by
30
+ # After authenticating a user and in each request, Devise checks if your model is active by
31
31
  # calling model.active_for_authentication?. This method is overwriten by other devise modules. For instance,
32
32
  # :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
33
33
  #
@@ -61,11 +61,7 @@ module Devise
61
61
  # However, you should not overwrite this method, you should overwrite active_for_authentication?
62
62
  # and inactive_message instead.
63
63
  def valid_for_authentication?
64
- if active_for_authentication?
65
- block_given? ? yield : true
66
- else
67
- inactive_message
68
- end
64
+ block_given? ? yield : true
69
65
  end
70
66
 
71
67
  def active_for_authentication?
@@ -79,6 +75,10 @@ module Devise
79
75
  def authenticatable_salt
80
76
  end
81
77
 
78
+ def devise_mailer
79
+ Devise.mailer
80
+ end
81
+
82
82
  module ClassMethods
83
83
  Devise::Models.config(self, :authentication_keys, :request_keys, :strip_whitespace_keys, :case_insensitive_keys, :http_authenticatable, :params_authenticatable)
84
84
 
@@ -112,10 +112,11 @@ module Devise
112
112
  # end
113
113
  #
114
114
  def find_for_authentication(conditions)
115
- conditions = filter_auth_params(conditions.dup)
116
- (case_insensitive_keys || []).each { |k| conditions[k].try(:downcase!) }
117
- (strip_whitespace_keys || []).each { |k| conditions[k].try(:strip!) }
118
- to_adapter.find_first(conditions)
115
+ find_first_by_auth_conditions(conditions)
116
+ end
117
+
118
+ def find_first_by_auth_conditions(conditions)
119
+ to_adapter.find_first devise_param_filter.filter(conditions)
119
120
  end
120
121
 
121
122
  # Find an initialize a record setting an error if it can't be found.
@@ -125,14 +126,11 @@ module Devise
125
126
 
126
127
  # Find an initialize a group of attributes based on a list of required attributes.
127
128
  def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
128
- (case_insensitive_keys || []).each { |k| attributes[k].try(:downcase!) }
129
- (strip_whitespace_keys || []).each { |k| attributes[k].try(:strip!) }
130
-
131
129
  attributes = attributes.slice(*required_attributes)
132
130
  attributes.delete_if { |key, value| value.blank? }
133
131
 
134
132
  if attributes.size == required_attributes.size
135
- record = to_adapter.find_first(filter_auth_params(attributes))
133
+ record = find_first_by_auth_conditions(attributes)
136
134
  end
137
135
 
138
136
  unless record
@@ -150,16 +148,8 @@ module Devise
150
148
 
151
149
  protected
152
150
 
153
- # Force keys to be string to avoid injection on mongoid related database.
154
- def filter_auth_params(conditions)
155
- conditions.each do |k, v|
156
- conditions[k] = v.to_s if auth_param_requires_string_conversion?(v)
157
- end if conditions.is_a?(Hash)
158
- end
159
-
160
- # Determine which values should be transformed to string or passed as-is to the query builder underneath
161
- def auth_param_requires_string_conversion?(value)
162
- true unless value.is_a?(TrueClass) || value.is_a?(FalseClass) || value.is_a?(Fixnum)
151
+ def devise_param_filter
152
+ @devise_param_filter ||= Devise::ParamFilter.new(case_insensitive_keys, strip_whitespace_keys)
163
153
  end
164
154
 
165
155
  # Generate a token by looping and ensuring does not already exist.
@@ -34,7 +34,7 @@ module Devise
34
34
  def confirm!
35
35
  unless_confirmed do
36
36
  self.confirmation_token = nil
37
- self.confirmed_at = Time.now
37
+ self.confirmed_at = Time.now.utc
38
38
  save(:validate => false)
39
39
  end
40
40
  end
@@ -47,7 +47,7 @@ module Devise
47
47
  # Send confirmation instructions by email
48
48
  def send_confirmation_instructions
49
49
  generate_confirmation_token! if self.confirmation_token.nil?
50
- ::Devise.mailer.confirmation_instructions(self).deliver
50
+ self.devise_mailer.confirmation_instructions(self).deliver
51
51
  end
52
52
 
53
53
  # Resend confirmation token. This method does not need to generate a new token.
@@ -71,7 +71,7 @@ module Devise
71
71
  # If you don't want confirmation to be sent on create, neither a code
72
72
  # to be generated, call skip_confirmation!
73
73
  def skip_confirmation!
74
- self.confirmed_at = Time.now
74
+ self.confirmed_at = Time.now.utc
75
75
  end
76
76
 
77
77
  protected
@@ -73,7 +73,17 @@ module Devise
73
73
  end
74
74
 
75
75
  # Updates record attributes without asking for the current password.
76
- # Never allows to change the current password
76
+ # Never allows to change the current password. If you are using this
77
+ # method, you should probably override this method to protect other
78
+ # attributes you would not like to be updated without a password.
79
+ #
80
+ # Example:
81
+ #
82
+ # def update_without_password(params={})
83
+ # params.delete(:email)
84
+ # super(params)
85
+ # end
86
+ #
77
87
  def update_without_password(params={})
78
88
  params.delete(:password)
79
89
  params.delete(:password_confirmation)
@@ -24,7 +24,7 @@ module Devise
24
24
 
25
25
  # Lock a user setting its locked_at to actual time.
26
26
  def lock_access!
27
- self.locked_at = Time.now
27
+ self.locked_at = Time.now.utc
28
28
 
29
29
  if unlock_strategy_enabled?(:email)
30
30
  generate_unlock_token
@@ -49,7 +49,7 @@ module Devise
49
49
 
50
50
  # Send unlock instructions by email
51
51
  def send_unlock_instructions
52
- ::Devise.mailer.unlock_instructions(self).deliver
52
+ self.devise_mailer.unlock_instructions(self).deliver
53
53
  end
54
54
 
55
55
  # Resend the unlock instructions if the user is locked.
@@ -79,25 +79,21 @@ module Devise
79
79
  # if the user can login or not (wrong password, etc)
80
80
  unlock_access! if lock_expired?
81
81
 
82
- case (result = super)
83
- when Symbol
84
- return result
85
- when TrueClass
82
+ if super
86
83
  self.failed_attempts = 0
87
84
  save(:validate => false)
88
- when FalseClass
89
- # PostgreSQL uses nil as the default value for integer columns set to 0
85
+ true
86
+ else
90
87
  self.failed_attempts ||= 0
91
88
  self.failed_attempts += 1
92
89
  if attempts_exceeded?
93
- lock_access!
90
+ lock_access! unless access_locked?
94
91
  return :locked
95
92
  else
96
93
  save(:validate => false)
97
94
  end
95
+ false
98
96
  end
99
-
100
- result
101
97
  end
102
98
 
103
99
  protected