devise 3.2.4 → 4.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +33 -17
- data/CHANGELOG.md +57 -1033
- data/CODE_OF_CONDUCT.md +22 -0
- data/CONTRIBUTING.md +2 -0
- data/Gemfile +5 -5
- data/Gemfile.lock +138 -115
- data/MIT-LICENSE +1 -1
- data/README.md +124 -65
- data/Rakefile +2 -1
- data/app/controllers/devise/confirmations_controller.rb +7 -3
- data/app/controllers/devise/omniauth_callbacks_controller.rb +8 -4
- data/app/controllers/devise/passwords_controller.rb +16 -6
- data/app/controllers/devise/registrations_controller.rb +22 -10
- data/app/controllers/devise/sessions_controller.rb +42 -14
- data/app/controllers/devise/unlocks_controller.rb +5 -2
- data/app/controllers/devise_controller.rb +63 -29
- data/app/mailers/devise/mailer.rb +4 -0
- data/app/views/devise/confirmations/new.html.erb +7 -3
- data/app/views/devise/mailer/password_change.html.erb +3 -0
- data/app/views/devise/passwords/edit.html.erb +14 -5
- data/app/views/devise/passwords/new.html.erb +7 -3
- data/app/views/devise/registrations/edit.html.erb +19 -9
- data/app/views/devise/registrations/new.html.erb +18 -7
- data/app/views/devise/sessions/new.html.erb +16 -7
- data/app/views/devise/shared/{_links.erb → _links.html.erb} +2 -2
- data/app/views/devise/unlocks/new.html.erb +7 -3
- data/bin/test +13 -0
- data/config/locales/en.yml +19 -16
- data/devise.gemspec +3 -4
- data/gemfiles/{Gemfile.rails-3.2-stable → Gemfile.rails-4.1-stable} +6 -6
- data/gemfiles/Gemfile.rails-4.1-stable.lock +167 -0
- data/gemfiles/{Gemfile.rails-head → Gemfile.rails-4.2-stable} +6 -6
- data/gemfiles/Gemfile.rails-4.2-stable.lock +189 -0
- data/gemfiles/Gemfile.rails-5.0-beta +37 -0
- data/gemfiles/Gemfile.rails-5.0-beta.lock +199 -0
- data/lib/devise/controllers/helpers.rb +94 -27
- data/lib/devise/controllers/rememberable.rb +9 -2
- data/lib/devise/controllers/sign_in_out.rb +2 -9
- data/lib/devise/controllers/store_location.rb +11 -3
- data/lib/devise/controllers/url_helpers.rb +7 -7
- data/lib/devise/encryptor.rb +22 -0
- data/lib/devise/failure_app.rb +72 -23
- data/lib/devise/hooks/activatable.rb +3 -4
- data/lib/devise/hooks/csrf_cleaner.rb +3 -1
- data/lib/devise/hooks/timeoutable.rb +13 -8
- data/lib/devise/mailers/helpers.rb +1 -1
- data/lib/devise/mapping.rb +6 -2
- data/lib/devise/models/authenticatable.rb +32 -28
- data/lib/devise/models/confirmable.rb +55 -22
- data/lib/devise/models/database_authenticatable.rb +32 -19
- data/lib/devise/models/lockable.rb +5 -5
- data/lib/devise/models/recoverable.rb +44 -20
- data/lib/devise/models/rememberable.rb +54 -27
- data/lib/devise/models/timeoutable.rb +0 -6
- data/lib/devise/models/trackable.rb +5 -3
- data/lib/devise/models/validatable.rb +3 -3
- data/lib/devise/models.rb +1 -1
- data/lib/devise/omniauth/url_helpers.rb +62 -4
- data/lib/devise/parameter_sanitizer.rb +176 -61
- data/lib/devise/rails/routes.rb +76 -59
- data/lib/devise/rails/warden_compat.rb +1 -10
- data/lib/devise/rails.rb +2 -11
- data/lib/devise/strategies/authenticatable.rb +15 -6
- data/lib/devise/strategies/database_authenticatable.rb +5 -4
- data/lib/devise/strategies/rememberable.rb +13 -3
- data/lib/devise/test_helpers.rb +12 -7
- data/lib/devise/token_generator.rb +1 -41
- data/lib/devise/version.rb +1 -1
- data/lib/devise.rb +150 -58
- data/lib/generators/active_record/devise_generator.rb +28 -4
- data/lib/generators/active_record/templates/migration.rb +3 -3
- data/lib/generators/active_record/templates/migration_existing.rb +3 -3
- data/lib/generators/devise/controllers_generator.rb +44 -0
- data/lib/generators/devise/install_generator.rb +15 -0
- data/lib/generators/devise/orm_helpers.rb +1 -18
- data/lib/generators/devise/views_generator.rb +14 -3
- data/lib/generators/templates/README +1 -1
- data/lib/generators/templates/controllers/README +14 -0
- data/lib/generators/templates/controllers/confirmations_controller.rb +28 -0
- data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +28 -0
- data/lib/generators/templates/controllers/passwords_controller.rb +32 -0
- data/lib/generators/templates/controllers/registrations_controller.rb +60 -0
- data/lib/generators/templates/controllers/sessions_controller.rb +25 -0
- data/lib/generators/templates/controllers/unlocks_controller.rb +28 -0
- data/lib/generators/templates/devise.rb +36 -28
- data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
- data/lib/generators/templates/markerb/password_change.markerb +3 -0
- data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
- data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
- data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +1 -1
- data/lib/generators/templates/simple_form_for/registrations/new.html.erb +1 -1
- data/lib/generators/templates/simple_form_for/sessions/new.html.erb +2 -2
- data/test/controllers/custom_registrations_controller_test.rb +40 -0
- data/test/controllers/custom_strategy_test.rb +7 -5
- data/test/controllers/helper_methods_test.rb +22 -0
- data/test/controllers/helpers_test.rb +41 -1
- data/test/controllers/inherited_controller_i18n_messages_test.rb +51 -0
- data/test/controllers/internal_helpers_test.rb +19 -15
- data/test/controllers/load_hooks_controller_test.rb +19 -0
- data/test/controllers/passwords_controller_test.rb +5 -4
- data/test/controllers/sessions_controller_test.rb +24 -21
- data/test/controllers/url_helpers_test.rb +7 -1
- data/test/devise_test.rb +48 -8
- data/test/failure_app_test.rb +107 -19
- data/test/generators/active_record_generator_test.rb +6 -26
- data/test/generators/controllers_generator_test.rb +48 -0
- data/test/generators/install_generator_test.rb +14 -3
- data/test/generators/views_generator_test.rb +8 -1
- data/test/helpers/devise_helper_test.rb +10 -12
- data/test/integration/authenticatable_test.rb +37 -21
- data/test/integration/confirmable_test.rb +54 -14
- data/test/integration/database_authenticatable_test.rb +12 -1
- data/test/integration/http_authenticatable_test.rb +4 -5
- data/test/integration/lockable_test.rb +10 -9
- data/test/integration/omniauthable_test.rb +13 -11
- data/test/integration/recoverable_test.rb +28 -15
- data/test/integration/registerable_test.rb +41 -33
- data/test/integration/rememberable_test.rb +51 -7
- data/test/integration/timeoutable_test.rb +23 -22
- data/test/integration/trackable_test.rb +3 -3
- data/test/mailers/confirmation_instructions_test.rb +10 -10
- data/test/mailers/reset_password_instructions_test.rb +8 -8
- data/test/mailers/unlock_instructions_test.rb +8 -8
- data/test/mapping_test.rb +7 -0
- data/test/models/authenticatable_test.rb +11 -1
- data/test/models/confirmable_test.rb +91 -42
- data/test/models/database_authenticatable_test.rb +26 -6
- data/test/models/lockable_test.rb +29 -17
- data/test/models/recoverable_test.rb +74 -7
- data/test/models/rememberable_test.rb +68 -94
- data/test/models/trackable_test.rb +28 -0
- data/test/models/validatable_test.rb +9 -17
- data/test/models_test.rb +15 -6
- data/test/omniauth/url_helpers_test.rb +4 -7
- data/test/orm/active_record.rb +6 -1
- data/test/parameter_sanitizer_test.rb +103 -53
- data/test/rails_app/app/active_record/user.rb +1 -0
- data/test/rails_app/app/active_record/user_on_engine.rb +7 -0
- data/test/rails_app/app/active_record/user_on_main_app.rb +7 -0
- data/test/rails_app/app/active_record/user_without_email.rb +8 -0
- data/test/rails_app/app/controllers/admins_controller.rb +1 -6
- data/test/rails_app/app/controllers/application_controller.rb +5 -2
- data/test/rails_app/app/controllers/application_with_fake_engine.rb +30 -0
- data/test/rails_app/app/controllers/custom/registrations_controller.rb +31 -0
- data/test/rails_app/app/controllers/home_controller.rb +5 -1
- data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +3 -3
- data/test/rails_app/app/controllers/users_controller.rb +6 -6
- data/test/rails_app/app/mailers/users/from_proc_mailer.rb +3 -0
- data/test/rails_app/app/mailers/users/mailer.rb +0 -9
- data/test/rails_app/app/mailers/users/reply_to_mailer.rb +4 -0
- data/test/rails_app/app/mongoid/user_on_engine.rb +39 -0
- data/test/rails_app/app/mongoid/user_on_main_app.rb +39 -0
- data/test/rails_app/app/mongoid/user_without_email.rb +33 -0
- data/test/rails_app/config/application.rb +3 -3
- data/test/rails_app/config/boot.rb +4 -4
- data/test/rails_app/config/environments/production.rb +6 -2
- data/test/rails_app/config/environments/test.rb +13 -3
- data/test/rails_app/config/initializers/devise.rb +15 -16
- data/test/rails_app/config/initializers/secret_token.rb +1 -6
- data/test/rails_app/config/routes.rb +23 -3
- data/test/rails_app/db/migrate/20100401102949_create_tables.rb +2 -2
- data/test/rails_app/lib/shared_user.rb +1 -1
- data/test/rails_app/lib/shared_user_without_email.rb +26 -0
- data/test/rails_app/lib/shared_user_without_omniauth.rb +13 -0
- data/test/rails_test.rb +9 -0
- data/test/routes_test.rb +33 -16
- data/test/support/assertions.rb +2 -3
- data/test/support/helpers.rb +13 -6
- data/test/support/http_method_compatibility.rb +51 -0
- data/test/support/integration.rb +4 -4
- data/test/support/webrat/integrations/rails.rb +9 -0
- data/test/test_helper.rb +7 -0
- data/test/test_helpers_test.rb +43 -38
- data/test/test_models.rb +3 -3
- metadata +77 -23
- data/gemfiles/Gemfile.rails-4.0-stable +0 -29
data/lib/devise/failure_app.rb
CHANGED
@@ -6,7 +6,6 @@ module Devise
|
|
6
6
|
# page based on current scope and mapping. If no scope is given, redirect
|
7
7
|
# to the default_url.
|
8
8
|
class FailureApp < ActionController::Metal
|
9
|
-
include ActionController::RackDelegation
|
10
9
|
include ActionController::UrlFor
|
11
10
|
include ActionController::Redirecting
|
12
11
|
|
@@ -22,9 +21,12 @@ module Devise
|
|
22
21
|
@respond.call(env)
|
23
22
|
end
|
24
23
|
|
24
|
+
# Try retrieving the URL options from the parent controller (usually
|
25
|
+
# ApplicationController). Instance methods are not supported at the moment,
|
26
|
+
# so only the class-level attribute is used.
|
25
27
|
def self.default_url_options(*args)
|
26
|
-
if defined?(
|
27
|
-
|
28
|
+
if defined?(Devise.parent_controller.constantize)
|
29
|
+
Devise.parent_controller.constantize.try(:default_url_options) || {}
|
28
30
|
else
|
29
31
|
{}
|
30
32
|
end
|
@@ -48,18 +50,40 @@ module Devise
|
|
48
50
|
end
|
49
51
|
|
50
52
|
def recall
|
51
|
-
|
52
|
-
|
53
|
-
|
53
|
+
config = Rails.application.config
|
54
|
+
|
55
|
+
header_info = if config.try(:relative_url_root)
|
56
|
+
base_path = Pathname.new(config.relative_url_root)
|
57
|
+
full_path = Pathname.new(attempted_path)
|
58
|
+
|
59
|
+
{ "SCRIPT_NAME" => config.relative_url_root,
|
60
|
+
"PATH_INFO" => '/' + full_path.relative_path_from(base_path).to_s }
|
61
|
+
else
|
62
|
+
{ "PATH_INFO" => attempted_path }
|
63
|
+
end
|
64
|
+
|
65
|
+
header_info.each do | var, value|
|
66
|
+
if request.respond_to?(:set_header)
|
67
|
+
request.set_header(var, value)
|
68
|
+
else
|
69
|
+
env[var] = value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
flash.now[:alert] = i18n_message(:invalid) if is_flashing_format?
|
74
|
+
# self.response = recall_app(warden_options[:recall]).call(env)
|
75
|
+
self.response = recall_app(warden_options[:recall]).call(request.env)
|
54
76
|
end
|
55
77
|
|
56
78
|
def redirect
|
57
79
|
store_location!
|
58
|
-
if
|
59
|
-
flash
|
60
|
-
|
61
|
-
|
62
|
-
|
80
|
+
if is_flashing_format?
|
81
|
+
if flash[:timedout] && flash[:alert]
|
82
|
+
flash.keep(:timedout)
|
83
|
+
flash.keep(:alert)
|
84
|
+
else
|
85
|
+
flash[:alert] = i18n_message
|
86
|
+
end
|
63
87
|
end
|
64
88
|
redirect_to redirect_url
|
65
89
|
end
|
@@ -78,6 +102,9 @@ module Devise
|
|
78
102
|
options[:resource_name] = scope
|
79
103
|
options[:scope] = "devise.failure"
|
80
104
|
options[:default] = [message]
|
105
|
+
auth_keys = scope_class.authentication_keys
|
106
|
+
keys = auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys
|
107
|
+
options[:authentication_keys] = keys.join(I18n.translate(:"support.array.words_connector"))
|
81
108
|
options = i18n_options(options)
|
82
109
|
|
83
110
|
I18n.t(:"#{scope}.#{message}", options)
|
@@ -88,7 +115,7 @@ module Devise
|
|
88
115
|
|
89
116
|
def redirect_url
|
90
117
|
if warden_message == :timeout
|
91
|
-
flash[:timedout] = true
|
118
|
+
flash[:timedout] = true if is_flashing_format?
|
92
119
|
|
93
120
|
path = if request.get?
|
94
121
|
attempted_path
|
@@ -96,26 +123,38 @@ module Devise
|
|
96
123
|
request.referrer
|
97
124
|
end
|
98
125
|
|
99
|
-
path ||
|
126
|
+
path || scope_url
|
100
127
|
else
|
101
|
-
|
128
|
+
scope_url
|
102
129
|
end
|
103
130
|
end
|
104
131
|
|
105
|
-
def
|
132
|
+
def route(scope)
|
133
|
+
:"new_#{scope}_session_url"
|
134
|
+
end
|
135
|
+
|
136
|
+
def scope_url
|
106
137
|
opts = {}
|
107
|
-
route =
|
138
|
+
route = route(scope)
|
108
139
|
opts[:format] = request_format unless skip_format?
|
109
140
|
|
110
141
|
config = Rails.application.config
|
111
|
-
opts[:script_name] = (config.relative_url_root if config.respond_to?(:relative_url_root))
|
112
142
|
|
113
|
-
|
143
|
+
if config.respond_to?(:relative_url_root)
|
144
|
+
# Rails 4.2 goes into an infinite loop if opts[:script_name] is unset
|
145
|
+
rails_4_2 = (Rails::VERSION::MAJOR >= 4) && (Rails::VERSION::MINOR >= 2)
|
146
|
+
if config.relative_url_root.present? || rails_4_2
|
147
|
+
opts[:script_name] = config.relative_url_root
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
router_name = Devise.mappings[scope].router_name || Devise.available_router_name
|
152
|
+
context = send(router_name)
|
114
153
|
|
115
154
|
if context.respond_to?(route)
|
116
155
|
context.send(route, opts)
|
117
|
-
elsif respond_to?(:
|
118
|
-
|
156
|
+
elsif respond_to?(:root_url)
|
157
|
+
root_url(opts)
|
119
158
|
else
|
120
159
|
"/"
|
121
160
|
end
|
@@ -144,7 +183,7 @@ module Devise
|
|
144
183
|
# It does not make sense to send authenticate headers in ajax requests
|
145
184
|
# or if the user disabled them.
|
146
185
|
def http_auth_header?
|
147
|
-
|
186
|
+
scope_class.http_authenticatable && !request.xhr?
|
148
187
|
end
|
149
188
|
|
150
189
|
def http_auth_body
|
@@ -167,11 +206,11 @@ module Devise
|
|
167
206
|
end
|
168
207
|
|
169
208
|
def warden
|
170
|
-
env[
|
209
|
+
request.respond_to?(:get_header) ? request.get_header("warden") : env["warden"]
|
171
210
|
end
|
172
211
|
|
173
212
|
def warden_options
|
174
|
-
env[
|
213
|
+
request.respond_to?(:get_header) ? request.get_header("warden.options") : env["warden.options"]
|
175
214
|
end
|
176
215
|
|
177
216
|
def warden_message
|
@@ -182,6 +221,10 @@ module Devise
|
|
182
221
|
@scope ||= warden_options[:scope] || Devise.default_scope
|
183
222
|
end
|
184
223
|
|
224
|
+
def scope_class
|
225
|
+
@scope_class ||= Devise.mappings[scope].to
|
226
|
+
end
|
227
|
+
|
185
228
|
def attempted_path
|
186
229
|
warden_options[:attempted_path]
|
187
230
|
end
|
@@ -198,6 +241,12 @@ module Devise
|
|
198
241
|
Devise.navigational_formats.include?(request_format)
|
199
242
|
end
|
200
243
|
|
244
|
+
# Check if flash messages should be emitted. Default is to do it on
|
245
|
+
# navigational formats
|
246
|
+
def is_flashing_format?
|
247
|
+
is_navigational_format?
|
248
|
+
end
|
249
|
+
|
201
250
|
def request_format
|
202
251
|
@request_format ||= request.format.try(:ref)
|
203
252
|
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
# Deny user access whenever their account is not active yet.
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# in each request and in case the user is using other strategies beside Devise ones.
|
1
|
+
# Deny user access whenever their account is not active yet.
|
2
|
+
# We need this as hook to validate the user activity on each request
|
3
|
+
# and in case the user is using other strategies beside Devise ones.
|
5
4
|
Warden::Manager.after_set_user do |record, warden, options|
|
6
5
|
if record && record.respond_to?(:active_for_authentication?) && !record.active_for_authentication?
|
7
6
|
scope = options[:scope]
|
@@ -1,5 +1,7 @@
|
|
1
1
|
Warden::Manager.after_authentication do |record, warden, options|
|
2
|
-
|
2
|
+
clean_up_for_winning_strategy = !warden.winning_strategy.respond_to?(:clean_up_csrf?) ||
|
3
|
+
warden.winning_strategy.clean_up_csrf?
|
4
|
+
if Devise.clean_up_csrf_token_on_authentication && clean_up_for_winning_strategy
|
3
5
|
warden.request.session.try(:delete, :_csrf_token)
|
4
6
|
end
|
5
7
|
end
|
@@ -7,22 +7,27 @@ Warden::Manager.after_set_user do |record, warden, options|
|
|
7
7
|
scope = options[:scope]
|
8
8
|
env = warden.request.env
|
9
9
|
|
10
|
-
if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) &&
|
10
|
+
if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) &&
|
11
|
+
options[:store] != false && !env['devise.skip_timeoutable']
|
11
12
|
last_request_at = warden.session(scope)['last_request_at']
|
12
|
-
proxy = Devise::Hooks::Proxy.new(warden)
|
13
13
|
|
14
|
-
if
|
15
|
-
|
14
|
+
if last_request_at.is_a? Integer
|
15
|
+
last_request_at = Time.at(last_request_at).utc
|
16
|
+
elsif last_request_at.is_a? String
|
17
|
+
last_request_at = Time.parse(last_request_at)
|
18
|
+
end
|
16
19
|
|
17
|
-
|
18
|
-
record.reset_authentication_token!
|
19
|
-
end
|
20
|
+
proxy = Devise::Hooks::Proxy.new(warden)
|
20
21
|
|
22
|
+
if record.timedout?(last_request_at) &&
|
23
|
+
!env['devise.skip_timeout'] &&
|
24
|
+
!proxy.remember_me_is_active?(record)
|
25
|
+
Devise.sign_out_all_scopes ? proxy.sign_out : proxy.sign_out(scope)
|
21
26
|
throw :warden, scope: scope, message: :timeout
|
22
27
|
end
|
23
28
|
|
24
29
|
unless env['devise.skip_trackable']
|
25
|
-
warden.session(scope)['last_request_at'] = Time.now.utc
|
30
|
+
warden.session(scope)['last_request_at'] = Time.now.utc.to_i
|
26
31
|
end
|
27
32
|
end
|
28
33
|
end
|
data/lib/devise/mapping.rb
CHANGED
@@ -23,16 +23,18 @@ 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
|
+
:failure_app, :router_name
|
27
28
|
|
28
29
|
alias :name :singular
|
29
30
|
|
30
31
|
# Receives an object and find a scope for it. If a scope cannot be found,
|
31
32
|
# raises an error. If a symbol is given, it's considered to be the scope.
|
32
33
|
def self.find_scope!(obj)
|
34
|
+
obj = obj.devise_scope if obj.respond_to?(:devise_scope)
|
33
35
|
case obj
|
34
36
|
when String, Symbol
|
35
|
-
return obj
|
37
|
+
return obj.to_sym
|
36
38
|
when Class
|
37
39
|
Devise.mappings.each_value { |m| return m.name if obj <= m.to }
|
38
40
|
else
|
@@ -60,6 +62,8 @@ module Devise
|
|
60
62
|
@sign_out_via = options[:sign_out_via] || Devise.sign_out_via
|
61
63
|
@format = options[:format]
|
62
64
|
|
65
|
+
@router_name = options[:router_name]
|
66
|
+
|
63
67
|
default_failure_app(options)
|
64
68
|
default_controllers(options)
|
65
69
|
default_path_names(options)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'active_model/version'
|
1
2
|
require 'devise/hooks/activatable'
|
2
3
|
require 'devise/hooks/csrf_cleaner'
|
3
4
|
|
@@ -37,7 +38,7 @@ module Devise
|
|
37
38
|
# calling model.active_for_authentication?. This method is overwritten by other devise modules. For instance,
|
38
39
|
# :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
|
39
40
|
#
|
40
|
-
# You overwrite this method yourself, but if you do, don't forget to call super:
|
41
|
+
# You can overwrite this method yourself, but if you do, don't forget to call super:
|
41
42
|
#
|
42
43
|
# def active_for_authentication?
|
43
44
|
# super && special_condition_is_valid?
|
@@ -95,29 +96,22 @@ module Devise
|
|
95
96
|
def authenticatable_salt
|
96
97
|
end
|
97
98
|
|
98
|
-
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
options[:except].concat Array(options[:force_except])
|
115
|
-
else
|
116
|
-
options[:except].concat BLACKLIST_FOR_SERIALIZATION
|
117
|
-
end
|
118
|
-
super(options)
|
119
|
-
end
|
120
|
-
RUBY
|
99
|
+
# Redefine serializable_hash in models for more secure defaults.
|
100
|
+
# By default, it removes from the serializable model all attributes that
|
101
|
+
# are *not* accessible. You can remove this default by using :force_except
|
102
|
+
# and passing a new list of attributes you want to exempt. All attributes
|
103
|
+
# given to :except will simply add names to exempt to Devise internal list.
|
104
|
+
def serializable_hash(options = nil)
|
105
|
+
options ||= {}
|
106
|
+
options[:except] = Array(options[:except])
|
107
|
+
|
108
|
+
if options[:force_except]
|
109
|
+
options[:except].concat Array(options[:force_except])
|
110
|
+
else
|
111
|
+
options[:except].concat BLACKLIST_FOR_SERIALIZATION
|
112
|
+
end
|
113
|
+
|
114
|
+
super(options)
|
121
115
|
end
|
122
116
|
|
123
117
|
protected
|
@@ -170,7 +164,13 @@ module Devise
|
|
170
164
|
# end
|
171
165
|
#
|
172
166
|
def send_devise_notification(notification, *args)
|
173
|
-
devise_mailer.send(notification, self, *args)
|
167
|
+
message = devise_mailer.send(notification, self, *args)
|
168
|
+
# Remove once we move to Rails 4.2+ only.
|
169
|
+
if message.respond_to?(:deliver_now)
|
170
|
+
message.deliver_now
|
171
|
+
else
|
172
|
+
message.deliver
|
173
|
+
end
|
174
174
|
end
|
175
175
|
|
176
176
|
def downcase_keys
|
@@ -246,14 +246,18 @@ module Devise
|
|
246
246
|
to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts))
|
247
247
|
end
|
248
248
|
|
249
|
-
# Find
|
249
|
+
# Find or initialize a record setting an error if it can't be found.
|
250
250
|
def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
|
251
251
|
find_or_initialize_with_errors([attribute], { attribute => value }, error)
|
252
252
|
end
|
253
253
|
|
254
|
-
# Find
|
254
|
+
# Find or initialize a record with group of attributes based on a list of required attributes.
|
255
255
|
def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
|
256
|
-
attributes = attributes.
|
256
|
+
attributes = if attributes.respond_to? :permit
|
257
|
+
attributes.slice(*required_attributes).permit!.to_h.with_indifferent_access
|
258
|
+
else
|
259
|
+
attributes.with_indifferent_access.slice(*required_attributes)
|
260
|
+
end
|
257
261
|
attributes.delete_if { |key, value| value.blank? }
|
258
262
|
|
259
263
|
if attributes.size == required_attributes.size
|
@@ -5,6 +5,14 @@ module Devise
|
|
5
5
|
# Confirmation instructions are sent to the user email after creating a
|
6
6
|
# record and when manually requested by a new confirmation instruction request.
|
7
7
|
#
|
8
|
+
# Confirmable tracks the following columns:
|
9
|
+
#
|
10
|
+
# * confirmation_token - A unique random token
|
11
|
+
# * confirmed_at - A timestamp when the user clicked the confirmation link
|
12
|
+
# * confirmation_sent_at - A timestamp when the confirmation_token was generated (not sent)
|
13
|
+
# * unconfirmed_email - An email address copied from the email attr. After confirmation
|
14
|
+
# this value is copied to the email attr then cleared
|
15
|
+
#
|
8
16
|
# == Options
|
9
17
|
#
|
10
18
|
# Confirmable adds the following options to +devise+:
|
@@ -16,21 +24,22 @@ module Devise
|
|
16
24
|
# By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.
|
17
25
|
# * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
|
18
26
|
# initial account confirmation) to be applied. Requires additional unconfirmed_email
|
19
|
-
# db field to be
|
27
|
+
# db field to be set up (t.reconfirmable in migrations). Until confirmed, new email is
|
20
28
|
# stored in unconfirmed email column, and copied to email column on successful
|
21
29
|
# confirmation.
|
22
30
|
# * +confirm_within+: the time before a sent confirmation token becomes invalid.
|
23
31
|
# You can use this to force the user to confirm within a set period of time.
|
32
|
+
# Confirmable will not generate a new token if a repeat confirmation is requested
|
33
|
+
# during this time frame, unless the user's email changed too.
|
24
34
|
#
|
25
35
|
# == Examples
|
26
36
|
#
|
27
|
-
# User.find(1).confirm
|
37
|
+
# User.find(1).confirm # returns true unless it's already confirmed
|
28
38
|
# User.find(1).confirmed? # true/false
|
29
39
|
# User.find(1).send_confirmation_instructions # manually send instructions
|
30
40
|
#
|
31
41
|
module Confirmable
|
32
42
|
extend ActiveSupport::Concern
|
33
|
-
include ActionView::Helpers::DateHelper
|
34
43
|
|
35
44
|
included do
|
36
45
|
before_create :generate_confirmation_token, if: :confirmation_required?
|
@@ -56,7 +65,7 @@ module Devise
|
|
56
65
|
# Confirm a user by setting it's confirmed_at to actual time. If the user
|
57
66
|
# is already confirmed, add an error to email field. If the user is invalid
|
58
67
|
# add errors
|
59
|
-
def confirm
|
68
|
+
def confirm(args={})
|
60
69
|
pending_any_confirmation do
|
61
70
|
if confirmation_period_expired?
|
62
71
|
self.errors.add(:email, :confirmation_period_expired,
|
@@ -64,10 +73,9 @@ module Devise
|
|
64
73
|
return false
|
65
74
|
end
|
66
75
|
|
67
|
-
self.confirmation_token = nil
|
68
76
|
self.confirmed_at = Time.now.utc
|
69
77
|
|
70
|
-
saved = if
|
78
|
+
saved = if pending_reconfirmation?
|
71
79
|
skip_reconfirmation!
|
72
80
|
self.email = unconfirmed_email
|
73
81
|
self.unconfirmed_email = nil
|
@@ -75,7 +83,7 @@ module Devise
|
|
75
83
|
# We need to validate in such cases to enforce e-mail uniqueness
|
76
84
|
save(validate: true)
|
77
85
|
else
|
78
|
-
save(validate:
|
86
|
+
save(validate: args[:ensure_valid] == true)
|
79
87
|
end
|
80
88
|
|
81
89
|
after_confirmation if saved
|
@@ -83,6 +91,11 @@ module Devise
|
|
83
91
|
end
|
84
92
|
end
|
85
93
|
|
94
|
+
def confirm!(args={})
|
95
|
+
ActiveSupport::Deprecation.warn "confirm! is deprecated in favor of confirm"
|
96
|
+
confirm(args)
|
97
|
+
end
|
98
|
+
|
86
99
|
# Verifies whether a user is confirmed or not
|
87
100
|
def confirmed?
|
88
101
|
!!confirmed_at
|
@@ -166,7 +179,7 @@ module Devise
|
|
166
179
|
# Checks if the confirmation for the user is within the limit time.
|
167
180
|
# We do this by calculating if the difference between today and the
|
168
181
|
# confirmation sent date does not exceed the confirm in time configured.
|
169
|
-
#
|
182
|
+
# allow_unconfirmed_access_for is a model configuration, must always be an integer value.
|
170
183
|
#
|
171
184
|
# Example:
|
172
185
|
#
|
@@ -202,7 +215,7 @@ module Devise
|
|
202
215
|
# confirmation_period_expired? # will always return false
|
203
216
|
#
|
204
217
|
def confirmation_period_expired?
|
205
|
-
self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within
|
218
|
+
self.class.confirm_within && self.confirmation_sent_at && (Time.now > self.confirmation_sent_at + self.class.confirm_within)
|
206
219
|
end
|
207
220
|
|
208
221
|
# Checks whether the record requires any confirmation.
|
@@ -216,12 +229,15 @@ module Devise
|
|
216
229
|
end
|
217
230
|
|
218
231
|
# Generates a new random token for confirmation, and stores
|
219
|
-
# the time this token is being generated
|
232
|
+
# the time this token is being generated in confirmation_sent_at
|
220
233
|
def generate_confirmation_token
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
234
|
+
if self.confirmation_token && !confirmation_period_expired?
|
235
|
+
@raw_confirmation_token = self.confirmation_token
|
236
|
+
else
|
237
|
+
raw, _ = Devise.token_generator.generate(self.class, :confirmation_token)
|
238
|
+
self.confirmation_token = @raw_confirmation_token = raw
|
239
|
+
self.confirmation_sent_at = Time.now.utc
|
240
|
+
end
|
225
241
|
end
|
226
242
|
|
227
243
|
def generate_confirmation_token!
|
@@ -232,23 +248,34 @@ module Devise
|
|
232
248
|
@reconfirmation_required = true
|
233
249
|
self.unconfirmed_email = self.email
|
234
250
|
self.email = self.email_was
|
251
|
+
self.confirmation_token = nil
|
235
252
|
generate_confirmation_token
|
236
253
|
end
|
237
254
|
|
238
255
|
def postpone_email_change?
|
239
|
-
postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone &&
|
256
|
+
postpone = self.class.reconfirmable && email_changed? && email_was.present? && !@bypass_confirmation_postpone && self.email.present?
|
240
257
|
@bypass_confirmation_postpone = false
|
241
258
|
postpone
|
242
259
|
end
|
243
260
|
|
244
261
|
def reconfirmation_required?
|
245
|
-
self.class.reconfirmable && @reconfirmation_required &&
|
262
|
+
self.class.reconfirmable && @reconfirmation_required && self.email.present?
|
246
263
|
end
|
247
264
|
|
248
265
|
def send_confirmation_notification?
|
249
|
-
confirmation_required? && !@skip_confirmation_notification &&
|
266
|
+
confirmation_required? && !@skip_confirmation_notification && self.email.present?
|
250
267
|
end
|
251
268
|
|
269
|
+
# A callback initiated after successfully confirming. This can be
|
270
|
+
# used to insert your own logic that is only run after the user successfully
|
271
|
+
# confirms.
|
272
|
+
#
|
273
|
+
# Example:
|
274
|
+
#
|
275
|
+
# def after_confirmation
|
276
|
+
# self.update_attribute(:invite_code, nil)
|
277
|
+
# end
|
278
|
+
#
|
252
279
|
def after_confirmation
|
253
280
|
end
|
254
281
|
|
@@ -271,17 +298,23 @@ module Devise
|
|
271
298
|
# If the user is already confirmed, create an error for the user
|
272
299
|
# Options must have the confirmation_token
|
273
300
|
def confirm_by_token(confirmation_token)
|
274
|
-
|
275
|
-
|
301
|
+
confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
|
302
|
+
unless confirmable
|
303
|
+
confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
|
304
|
+
confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
|
305
|
+
end
|
306
|
+
|
307
|
+
# TODO: replace above lines with
|
308
|
+
# confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
|
309
|
+
# after enough time has passed that Devise clients do not use digested tokens
|
276
310
|
|
277
|
-
confirmable
|
278
|
-
confirmable.confirm! if confirmable.persisted?
|
279
|
-
confirmable.confirmation_token = original_token
|
311
|
+
confirmable.confirm if confirmable.persisted?
|
280
312
|
confirmable
|
281
313
|
end
|
282
314
|
|
283
315
|
# Find a record for confirmation by unconfirmed email field
|
284
316
|
def find_by_unconfirmed_email_with_errors(attributes = {})
|
317
|
+
attributes = attributes.slice(*confirmation_keys).permit!.to_h if attributes.respond_to? :permit
|
285
318
|
unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
|
286
319
|
unconfirmed_attributes = attributes.symbolize_keys
|
287
320
|
unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
|
@@ -1,19 +1,18 @@
|
|
1
1
|
require 'devise/strategies/database_authenticatable'
|
2
|
-
require 'bcrypt'
|
3
2
|
|
4
3
|
module Devise
|
5
|
-
# Digests the password using bcrypt.
|
6
4
|
def self.bcrypt(klass, password)
|
7
|
-
::
|
5
|
+
ActiveSupport::Deprecation.warn "Devise.bcrypt is deprecated; use Devise::Encryptor.digest instead"
|
6
|
+
Devise::Encryptor.digest(klass, password)
|
8
7
|
end
|
9
8
|
|
10
9
|
module Models
|
11
|
-
# Authenticatable Module, responsible for
|
12
|
-
# authenticity of a user while signing in.
|
10
|
+
# Authenticatable Module, responsible for hashing the password and
|
11
|
+
# validating the authenticity of a user while signing in.
|
13
12
|
#
|
14
13
|
# == Options
|
15
14
|
#
|
16
|
-
#
|
15
|
+
# DatabaseAuthenticatable adds the following options to devise_for:
|
17
16
|
#
|
18
17
|
# * +pepper+: a random string used to provide a more secure hash. Use
|
19
18
|
# `rake secret` to generate new keys.
|
@@ -28,6 +27,8 @@ module Devise
|
|
28
27
|
extend ActiveSupport::Concern
|
29
28
|
|
30
29
|
included do
|
30
|
+
after_update :send_password_change_notification, if: :send_password_change_notification?
|
31
|
+
|
31
32
|
attr_reader :password, :current_password
|
32
33
|
attr_accessor :password_confirmation
|
33
34
|
end
|
@@ -36,18 +37,18 @@ module Devise
|
|
36
37
|
[:encrypted_password] + klass.authentication_keys
|
37
38
|
end
|
38
39
|
|
39
|
-
# Generates password
|
40
|
+
# Generates a hashed password based on the given value.
|
41
|
+
# For legacy reasons, we use `encrypted_password` to store
|
42
|
+
# the hashed password.
|
40
43
|
def password=(new_password)
|
44
|
+
attribute_will_change! 'password'
|
41
45
|
@password = new_password
|
42
46
|
self.encrypted_password = password_digest(@password) if @password.present?
|
43
47
|
end
|
44
48
|
|
45
|
-
# Verifies whether
|
49
|
+
# Verifies whether a password (ie from sign in) is the user password.
|
46
50
|
def valid_password?(password)
|
47
|
-
|
48
|
-
bcrypt = ::BCrypt::Password.new(encrypted_password)
|
49
|
-
password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
|
50
|
-
Devise.secure_compare(password, encrypted_password)
|
51
|
+
Devise::Encryptor.compare(self.class, encrypted_password, password)
|
51
52
|
end
|
52
53
|
|
53
54
|
# Set password and password confirmation to nil
|
@@ -55,9 +56,13 @@ module Devise
|
|
55
56
|
self.password = self.password_confirmation = nil
|
56
57
|
end
|
57
58
|
|
58
|
-
# Update record attributes when :current_password matches, otherwise
|
59
|
-
# error on :current_password.
|
60
|
-
#
|
59
|
+
# Update record attributes when :current_password matches, otherwise
|
60
|
+
# returns error on :current_password.
|
61
|
+
#
|
62
|
+
# This method also rejects the password field if it is blank (allowing
|
63
|
+
# users to change relevant information like the e-mail without changing
|
64
|
+
# their password). In case the password field is rejected, the confirmation
|
65
|
+
# is also rejected as long as it is also blank.
|
61
66
|
def update_with_password(params, *options)
|
62
67
|
current_password = params.delete(:current_password)
|
63
68
|
|
@@ -133,19 +138,27 @@ module Devise
|
|
133
138
|
encrypted_password[0,29] if encrypted_password
|
134
139
|
end
|
135
140
|
|
141
|
+
def send_password_change_notification
|
142
|
+
send_devise_notification(:password_change)
|
143
|
+
end
|
144
|
+
|
136
145
|
protected
|
137
146
|
|
138
|
-
#
|
147
|
+
# Hashes the password using bcrypt. Custom hash functions should override
|
139
148
|
# this method to apply their own algorithm.
|
140
149
|
#
|
141
150
|
# See https://github.com/plataformatec/devise-encryptable for examples
|
142
|
-
# of other
|
151
|
+
# of other hashing engines.
|
143
152
|
def password_digest(password)
|
144
|
-
Devise.
|
153
|
+
Devise::Encryptor.digest(self.class, password)
|
154
|
+
end
|
155
|
+
|
156
|
+
def send_password_change_notification?
|
157
|
+
self.class.send_password_change_notification && encrypted_password_changed?
|
145
158
|
end
|
146
159
|
|
147
160
|
module ClassMethods
|
148
|
-
Devise::Models.config(self, :pepper, :stretches)
|
161
|
+
Devise::Models.config(self, :pepper, :stretches, :send_password_change_notification)
|
149
162
|
|
150
163
|
# We assume this method already gets the sanitized values from the
|
151
164
|
# DatabaseAuthenticatable strategy. If you are using this method on
|