devise-security 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +3 -1
- data/README.md +18 -7
- data/app/controllers/devise/paranoid_verification_code_controller.rb +26 -12
- data/app/controllers/devise/password_expired_controller.rb +22 -5
- data/config/locales/bg.yml +42 -0
- data/config/locales/by.yml +1 -0
- data/config/locales/cs.yml +5 -0
- data/config/locales/de.yml +3 -0
- data/config/locales/en.yml +2 -1
- data/config/locales/es.yml +12 -0
- data/config/locales/fa.yml +1 -0
- data/config/locales/fr.yml +14 -2
- data/config/locales/hi.yml +1 -0
- data/config/locales/it.yml +1 -0
- data/config/locales/ja.yml +12 -0
- data/config/locales/nl.yml +1 -0
- data/config/locales/pt.yml +1 -0
- data/config/locales/ru.yml +1 -0
- data/config/locales/tr.yml +25 -1
- data/config/locales/uk.yml +1 -0
- data/config/locales/zh_CN.yml +1 -0
- data/config/locales/zh_TW.yml +1 -0
- data/lib/devise-security/controllers/helpers.rb +23 -11
- data/lib/devise-security/hooks/expirable.rb +3 -3
- data/lib/devise-security/hooks/paranoid_verification.rb +1 -3
- data/lib/devise-security/hooks/password_expirable.rb +1 -3
- data/lib/devise-security/hooks/session_limitable.rb +4 -4
- data/lib/devise-security/models/compatibility/active_record_patch.rb +4 -3
- data/lib/devise-security/models/compatibility/mongoid_patch.rb +3 -2
- data/lib/devise-security/models/database_authenticatable_patch.rb +18 -10
- data/lib/devise-security/models/expirable.rb +6 -5
- data/lib/devise-security/models/paranoid_verification.rb +2 -2
- data/lib/devise-security/models/password_archivable.rb +3 -3
- data/lib/devise-security/models/secure_validatable.rb +57 -20
- data/lib/devise-security/orm/mongoid.rb +1 -1
- data/lib/devise-security/patches.rb +14 -8
- data/lib/devise-security/routes.rb +2 -3
- data/lib/devise-security/validators/password_complexity_validator.rb +53 -26
- data/lib/devise-security/version.rb +1 -1
- data/lib/devise-security.rb +9 -3
- data/lib/generators/devise_security/install_generator.rb +3 -5
- data/lib/generators/templates/devise_security.rb +6 -1
- data/test/controllers/test_paranoid_verification_code_controller.rb +133 -0
- data/test/controllers/test_password_expired_controller.rb +87 -33
- data/test/controllers/test_security_question_controller.rb +25 -19
- data/test/dummy/app/controllers/overrides/paranoid_verification_code_controller.rb +7 -0
- data/test/dummy/app/controllers/overrides/password_expired_controller.rb +17 -0
- data/test/dummy/app/controllers/widgets_controller.rb +3 -0
- data/test/dummy/app/models/application_user_record.rb +2 -1
- data/test/dummy/app/models/mongoid/confirmable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +4 -3
- data/test/dummy/app/models/mongoid/expirable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/lockable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/mappings.rb +4 -2
- data/test/dummy/app/models/mongoid/omniauthable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/password_archivable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/password_expirable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/recoverable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/registerable_fields.rb +4 -2
- data/test/dummy/app/models/mongoid/rememberable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/security_questionable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/session_limitable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/timeoutable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/trackable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/validatable_fields.rb +2 -0
- data/test/dummy/app/models/paranoid_verification_user.rb +26 -0
- data/test/dummy/app/models/password_expired_user.rb +26 -0
- data/test/dummy/app/models/user.rb +5 -5
- data/test/dummy/app/models/widget.rb +1 -3
- data/test/dummy/app/mongoid/one_user.rb +5 -5
- data/test/dummy/app/mongoid/user_on_engine.rb +2 -2
- data/test/dummy/app/mongoid/user_on_main_app.rb +2 -2
- data/test/dummy/app/mongoid/user_with_validations.rb +3 -3
- data/test/dummy/app/mongoid/user_without_email.rb +7 -4
- data/test/dummy/config/application.rb +3 -7
- data/test/dummy/config/boot.rb +1 -1
- data/test/dummy/config/environment.rb +1 -1
- data/test/dummy/config/environments/test.rb +1 -0
- data/test/dummy/config/initializers/devise.rb +1 -5
- data/test/dummy/config/locales/en.yml +10 -0
- data/test/dummy/config/routes.rb +3 -1
- data/test/dummy/config.ru +1 -1
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +5 -5
- data/test/dummy/lib/shared_expirable_columns.rb +1 -0
- data/test/dummy/lib/shared_security_questions_fields.rb +1 -0
- data/test/dummy/lib/shared_user.rb +17 -6
- data/test/dummy/lib/shared_user_without_omniauth.rb +12 -3
- data/test/dummy/lib/shared_verification_fields.rb +1 -0
- data/test/dummy/log/test.log +39637 -16086
- data/test/i18n_test.rb +22 -0
- data/test/integration/test_paranoid_verification_code_workflow.rb +53 -0
- data/test/integration/test_password_expirable_workflow.rb +2 -2
- data/test/integration/test_session_limitable_workflow.rb +5 -3
- data/test/orm/active_record.rb +7 -7
- data/test/support/integration_helpers.rb +18 -12
- data/test/test_compatibility.rb +2 -0
- data/test/test_complexity_validator.rb +247 -37
- data/test/test_database_authenticatable_patch.rb +146 -0
- data/test/test_helper.rb +7 -8
- data/test/test_install_generator.rb +1 -1
- data/test/test_paranoid_verification.rb +8 -9
- data/test/test_password_archivable.rb +34 -11
- data/test/test_password_expirable.rb +27 -27
- data/test/test_secure_validatable.rb +265 -107
- data/test/test_secure_validatable_overrides.rb +185 -0
- data/test/test_session_limitable.rb +9 -9
- data/test/tmp/config/initializers/{devise-security.rb → devise_security.rb} +6 -1
- data/test/tmp/config/locales/devise.security_extension.by.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.cs.yml +5 -0
- data/test/tmp/config/locales/devise.security_extension.de.yml +3 -0
- data/test/tmp/config/locales/devise.security_extension.en.yml +2 -1
- data/test/tmp/config/locales/devise.security_extension.es.yml +12 -0
- data/test/tmp/config/locales/devise.security_extension.fa.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.fr.yml +14 -2
- data/test/tmp/config/locales/devise.security_extension.hi.yml +21 -20
- data/test/tmp/config/locales/devise.security_extension.it.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.ja.yml +12 -0
- data/test/tmp/config/locales/devise.security_extension.nl.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.pt.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.ru.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.tr.yml +25 -1
- data/test/tmp/config/locales/devise.security_extension.uk.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +1 -0
- metadata +82 -41
- data/lib/devise-security/patches/confirmations_controller_captcha.rb +0 -23
- data/lib/devise-security/patches/confirmations_controller_security_question.rb +0 -26
- data/lib/devise-security/patches/passwords_controller_captcha.rb +0 -22
- data/lib/devise-security/patches/passwords_controller_security_question.rb +0 -25
- data/lib/devise-security/patches/registrations_controller_captcha.rb +0 -35
- data/lib/devise-security/patches/sessions_controller_captcha.rb +0 -26
- data/lib/devise-security/patches/unlocks_controller_captcha.rb +0 -22
- data/lib/devise-security/patches/unlocks_controller_security_question.rb +0 -25
- data/test/dummy/app/controllers/foos_controller.rb +0 -0
- data/test/dummy/app/models/secure_user.rb +0 -9
- data/test/dummy/lib/shared_user_without_email.rb +0 -28
- data/test/dummy/log/development.log +0 -883
@@ -1,161 +1,319 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
|
-
require 'rails_email_validator'
|
5
4
|
|
6
5
|
class TestSecureValidatable < ActiveSupport::TestCase
|
7
6
|
class User < ApplicationRecord
|
8
|
-
devise :database_authenticatable, :
|
9
|
-
:paranoid_verification, :password_expirable, :secure_validatable
|
7
|
+
devise :database_authenticatable, :secure_validatable
|
10
8
|
include ::Mongoid::Mappings if DEVISE_ORM == :mongoid
|
11
9
|
end
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
user = User.create password: 'passWord1', password_confirmation: 'passWord1'
|
11
|
+
class EmailNotRequiredUser < User
|
12
|
+
protected
|
16
13
|
|
17
|
-
|
18
|
-
|
19
|
-
assert_raises(ORMInvalidRecordException) do
|
20
|
-
user.save!
|
14
|
+
def email_required?
|
15
|
+
false
|
21
16
|
end
|
22
17
|
end
|
23
18
|
|
19
|
+
test 'email cannot be blank upon creation' do
|
20
|
+
user = User.new(
|
21
|
+
password: 'Password1!', password_confirmation: 'Password1!'
|
22
|
+
)
|
23
|
+
|
24
|
+
assert user.invalid?
|
25
|
+
assert_equal(["Email can't be blank"], user.errors.full_messages)
|
26
|
+
end
|
27
|
+
|
28
|
+
test 'email can be blank upon creation if email not required' do
|
29
|
+
user = EmailNotRequiredUser.new(
|
30
|
+
password: 'Password1!', password_confirmation: 'Password1!'
|
31
|
+
)
|
32
|
+
|
33
|
+
assert user.valid?
|
34
|
+
end
|
35
|
+
|
36
|
+
test 'email cannot be updated to be blank' do
|
37
|
+
user = User.new(
|
38
|
+
email: 'bob@microsoft.com',
|
39
|
+
password: 'Password1!',
|
40
|
+
password_confirmation: 'Password1!'
|
41
|
+
)
|
42
|
+
|
43
|
+
assert user.valid?
|
44
|
+
|
45
|
+
user.email = nil
|
46
|
+
|
47
|
+
assert user.invalid?
|
48
|
+
assert_equal(["Email can't be blank"], user.errors.full_messages)
|
49
|
+
end
|
50
|
+
|
51
|
+
test 'email can be updated to be blank if email not required' do
|
52
|
+
user = EmailNotRequiredUser.new(
|
53
|
+
email: 'bob@microsoft.com',
|
54
|
+
password: 'Password1!',
|
55
|
+
password_confirmation: 'Password1!'
|
56
|
+
)
|
57
|
+
|
58
|
+
assert user.valid?
|
59
|
+
|
60
|
+
user.email = nil
|
61
|
+
|
62
|
+
assert user.valid?
|
63
|
+
end
|
64
|
+
|
24
65
|
test 'email must be valid' do
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
66
|
+
user = User.new(
|
67
|
+
email: 'bob', password: 'Password1!', password_confirmation: 'Password1!'
|
68
|
+
)
|
69
|
+
|
70
|
+
assert user.invalid?
|
71
|
+
assert_equal(['Email is invalid'], user.errors.full_messages)
|
32
72
|
end
|
33
73
|
|
34
74
|
test 'validate both email and password' do
|
35
|
-
|
36
|
-
|
37
|
-
|
75
|
+
user = User.new(
|
76
|
+
email: 'bob',
|
77
|
+
password: 'password1!',
|
78
|
+
password_confirmation: 'password1!'
|
79
|
+
)
|
80
|
+
|
81
|
+
assert user.invalid?
|
82
|
+
assert_equal(
|
83
|
+
[
|
84
|
+
'Email is invalid',
|
85
|
+
'Password must contain at least one upper-case letter'
|
86
|
+
],
|
87
|
+
user.errors.full_messages
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
test 'password cannot be blank upon creation' do
|
92
|
+
user = User.new(email: 'bob@microsoft.com')
|
93
|
+
|
94
|
+
msgs = ["Password can't be blank"]
|
95
|
+
|
96
|
+
msgs << "Encrypted password can't be blank" if DEVISE_ORM == :mongoid
|
97
|
+
|
98
|
+
assert user.invalid?
|
38
99
|
assert_equal(msgs, user.errors.full_messages)
|
39
|
-
|
100
|
+
end
|
101
|
+
|
102
|
+
test 'password cannot be updated to be blank' do
|
103
|
+
user = User.new(
|
104
|
+
email: 'bob@microsoft.com',
|
105
|
+
password: 'Password1!',
|
106
|
+
password_confirmation: 'Password1!'
|
107
|
+
)
|
108
|
+
|
109
|
+
assert user.valid?
|
110
|
+
|
111
|
+
user.password = nil
|
112
|
+
user.password_confirmation = nil
|
113
|
+
|
114
|
+
assert user.invalid?
|
115
|
+
assert_equal(["Password can't be blank"], user.errors.full_messages)
|
116
|
+
end
|
117
|
+
|
118
|
+
test 'password_confirmation must match password' do
|
119
|
+
user = User.new(
|
120
|
+
email: 'bob@microsoft.com',
|
121
|
+
password: 'Password1!',
|
122
|
+
password_confirmation: 'not the same password'
|
123
|
+
)
|
124
|
+
|
125
|
+
assert user.invalid?
|
126
|
+
assert_equal(
|
127
|
+
["Password confirmation doesn't match Password"],
|
128
|
+
user.errors.full_messages
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
test 'password_confirmation cannot be blank' do
|
133
|
+
user = User.new(
|
134
|
+
email: 'bob@microsoft.com',
|
135
|
+
password: 'Password1!',
|
136
|
+
password_confirmation: ''
|
137
|
+
)
|
138
|
+
|
139
|
+
assert user.invalid?
|
140
|
+
assert_equal(
|
141
|
+
["Password confirmation doesn't match Password"],
|
142
|
+
user.errors.full_messages
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
test 'password_confirmation can be skipped' do
|
147
|
+
user = User.new(
|
148
|
+
email: 'bob@microsoft.com',
|
149
|
+
password: 'Password1!',
|
150
|
+
password_confirmation: nil
|
151
|
+
)
|
152
|
+
|
153
|
+
assert user.valid?
|
40
154
|
end
|
41
155
|
|
42
156
|
test 'password must have capital letter' do
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
157
|
+
user = User.new(
|
158
|
+
email: 'bob@microsoft.com',
|
159
|
+
password: 'password1',
|
160
|
+
password_confirmation: 'password1'
|
161
|
+
)
|
162
|
+
|
163
|
+
assert user.invalid?
|
164
|
+
assert_equal(
|
165
|
+
['Password must contain at least one upper-case letter'],
|
166
|
+
user.errors.full_messages
|
167
|
+
)
|
48
168
|
end
|
49
169
|
|
50
170
|
test 'password must have lowercase letter' do
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
171
|
+
user = User.new(
|
172
|
+
email: 'bob@microsoft.com',
|
173
|
+
password: 'PASSWORD1',
|
174
|
+
password_confirmation: 'PASSWORD1'
|
175
|
+
)
|
176
|
+
|
177
|
+
assert user.invalid?
|
178
|
+
assert_equal(
|
179
|
+
['Password must contain at least one lower-case letter'],
|
180
|
+
user.errors.full_messages
|
181
|
+
)
|
56
182
|
end
|
57
183
|
|
58
184
|
test 'password must have number' do
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
185
|
+
user = User.new(
|
186
|
+
email: 'bob@microsoft.com',
|
187
|
+
password: 'PASSword',
|
188
|
+
password_confirmation: 'PASSword'
|
189
|
+
)
|
190
|
+
|
191
|
+
assert user.invalid?
|
192
|
+
assert_equal(
|
193
|
+
['Password must contain at least one digit'],
|
194
|
+
user.errors.full_messages
|
195
|
+
)
|
64
196
|
end
|
65
197
|
|
66
|
-
test 'password must
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
198
|
+
test 'password must meet minimum length' do
|
199
|
+
user = User.new(
|
200
|
+
email: 'bob@microsoft.com',
|
201
|
+
password: 'Pa3zZ',
|
202
|
+
password_confirmation: 'Pa3zZ'
|
203
|
+
)
|
204
|
+
|
205
|
+
assert user.invalid?
|
206
|
+
assert_equal(
|
207
|
+
['Password is too short (minimum is 7 characters)'],
|
208
|
+
user.errors.full_messages
|
209
|
+
)
|
72
210
|
end
|
73
211
|
|
74
|
-
test
|
212
|
+
test "new user can't use existing user's email" do
|
75
213
|
options = {
|
76
|
-
email: '
|
77
|
-
password: '
|
78
|
-
password_confirmation: '
|
214
|
+
email: 'bob@microsoft.com',
|
215
|
+
password: 'Password1!',
|
216
|
+
password_confirmation: 'Password1!'
|
79
217
|
}
|
80
|
-
|
81
|
-
user =
|
82
|
-
refute user.valid?
|
83
|
-
assert_equal DEVISE_ORM == :active_record ? ['Email has already been taken'] : ['Email is already taken'], user.errors.full_messages
|
84
|
-
end
|
218
|
+
User.create!(options)
|
219
|
+
user = User.new(options)
|
85
220
|
|
86
|
-
|
87
|
-
|
88
|
-
user = User.create email: 'bob@microsoft.com', password: 'bob@microsoft.com', password_confirmation: 'bob@microsoft.com'
|
89
|
-
refute user.valid?
|
90
|
-
assert_includes(user.errors.full_messages, msg)
|
91
|
-
assert_raises(ORMInvalidRecordException) { user.save! }
|
221
|
+
assert user.invalid?
|
222
|
+
assert_equal(['Email has already been taken'], user.errors.full_messages)
|
92
223
|
end
|
93
224
|
|
94
|
-
test
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
225
|
+
test "new user can't use existing user's email with different casing" do
|
226
|
+
options = {
|
227
|
+
email: 'bob@microsoft.com',
|
228
|
+
password: 'Password1!',
|
229
|
+
password_confirmation: 'Password1!'
|
230
|
+
}
|
231
|
+
User.create!(options)
|
232
|
+
options[:email] = 'BOB@MICROSOFT.COM'
|
233
|
+
user = User.new(options)
|
234
|
+
|
235
|
+
assert user.invalid?
|
236
|
+
assert_equal(['Email has already been taken'], user.errors.full_messages)
|
100
237
|
end
|
101
238
|
|
102
|
-
test 'password
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
239
|
+
test 'password cannot equal email for new user' do
|
240
|
+
user = User.new(
|
241
|
+
email: 'Bob1@microsoft.com',
|
242
|
+
password: 'Bob1@microsoft.com',
|
243
|
+
password_confirmation: 'Bob1@microsoft.com'
|
244
|
+
)
|
245
|
+
|
246
|
+
assert user.invalid?
|
247
|
+
assert_equal(
|
248
|
+
['Password must be different than the email.'],
|
249
|
+
user.errors.full_messages
|
250
|
+
)
|
108
251
|
end
|
109
252
|
|
110
|
-
test 'password
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
253
|
+
test 'password cannot equal case sensitive version of email for new user' do
|
254
|
+
user = User.new(
|
255
|
+
email: 'bob1@microsoft.com',
|
256
|
+
password: 'BoB1@microsoft.com',
|
257
|
+
password_confirmation: 'BoB1@microsoft.com'
|
258
|
+
)
|
259
|
+
|
260
|
+
assert user.invalid?
|
261
|
+
assert_equal(
|
262
|
+
['Password must be different than the email.'],
|
263
|
+
user.errors.full_messages
|
264
|
+
)
|
116
265
|
end
|
117
266
|
|
118
|
-
test 'password
|
119
|
-
user = User.
|
267
|
+
test 'password cannot equal email with spaces for new user' do
|
268
|
+
user = User.new(
|
269
|
+
email: 'Bob1@microsoft.com',
|
270
|
+
password: 'Bob1@microsoft.com ',
|
271
|
+
password_confirmation: 'Bob1@microsoft.com '
|
272
|
+
)
|
120
273
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
assert_raises(ORMInvalidRecordException) { user.save! }
|
274
|
+
assert user.invalid?
|
275
|
+
assert_equal(
|
276
|
+
['Password must be different than the email.'],
|
277
|
+
user.errors.full_messages
|
278
|
+
)
|
127
279
|
end
|
128
280
|
|
129
|
-
test 'password
|
130
|
-
|
281
|
+
test 'password cannot equal case sensitive version of email with spaces '\
|
282
|
+
'for new user' do
|
283
|
+
user = User.new(
|
284
|
+
email: 'Bob1@microsoft.com',
|
285
|
+
password: ' boB1@microsoft.com ',
|
286
|
+
password_confirmation: ' boB1@microsoft.com '
|
287
|
+
)
|
131
288
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
assert_raises(ORMInvalidRecordException) { user.save! }
|
289
|
+
assert user.invalid?
|
290
|
+
assert_equal(
|
291
|
+
['Password must be different than the email.'],
|
292
|
+
user.errors.full_messages
|
293
|
+
)
|
138
294
|
end
|
139
295
|
|
140
|
-
test 'password
|
141
|
-
user = User.create
|
296
|
+
test 'new password cannot equal current password' do
|
297
|
+
user = User.create(
|
298
|
+
email: 'bob@microsoft.com',
|
299
|
+
password: 'Password1!',
|
300
|
+
password_confirmation: 'Password1!'
|
301
|
+
)
|
302
|
+
|
303
|
+
user.password = 'Password1!'
|
142
304
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
assert_raises(ORMInvalidRecordException) { user.save! }
|
305
|
+
assert user.invalid?
|
306
|
+
assert_equal(
|
307
|
+
['Password must be different than the current password.'],
|
308
|
+
user.errors.full_messages
|
309
|
+
)
|
149
310
|
end
|
150
311
|
|
151
|
-
test '
|
152
|
-
|
312
|
+
test 'should not be included in objects with invalid API' do
|
313
|
+
error = assert_raise RuntimeError do
|
314
|
+
class ::Dog; include Devise::Models::SecureValidatable; end
|
315
|
+
end
|
153
316
|
|
154
|
-
|
155
|
-
user.password = ' BoB@microsoft.com '
|
156
|
-
user.password_confirmation = ' BoB@microsoft.com '
|
157
|
-
refute user.valid?
|
158
|
-
assert_includes(user.errors.full_messages, msg)
|
159
|
-
assert_raises(ORMInvalidRecordException) { user.save! }
|
317
|
+
assert_equal('Could not use SecureValidatable on Dog', error.message)
|
160
318
|
end
|
161
319
|
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestSecureValidatableOverrides < ActiveSupport::TestCase
|
6
|
+
class ::CustomClassPasswordValidator < DeviseSecurity::PasswordComplexityValidator
|
7
|
+
def patterns
|
8
|
+
super.merge(letter: /\p{Alpha}/)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ::CustomInstancePasswordValidator < DeviseSecurity::PasswordComplexityValidator
|
13
|
+
# Add a pattern for alphanumeric characters. See
|
14
|
+
# [en.yml](file:///./test/dummy/config/locales/en.yml) for translations used in
|
15
|
+
# tests.
|
16
|
+
def patterns
|
17
|
+
super.merge(alnum: /\p{Alnum}/)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class User < ApplicationRecord
|
22
|
+
devise :database_authenticatable, :secure_validatable
|
23
|
+
include ::Mongoid::Mappings if DEVISE_ORM == :mongoid
|
24
|
+
end
|
25
|
+
|
26
|
+
class ClassLevelOverrideUser < User
|
27
|
+
self.allow_passwords_equal_to_email = true
|
28
|
+
self.email_validation = false
|
29
|
+
self.password_complexity = { symbol: 1, letter: 1 }
|
30
|
+
self.password_complexity_validator = 'custom_class_password_validator'
|
31
|
+
self.password_length = 10..100
|
32
|
+
end
|
33
|
+
|
34
|
+
class InstanceLevelOverrideUser < ClassLevelOverrideUser
|
35
|
+
def allow_passwords_equal_to_email
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def email_validation
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def password_complexity
|
44
|
+
{ symbol: 2, alnum: 1 }
|
45
|
+
end
|
46
|
+
|
47
|
+
def password_length
|
48
|
+
11..100
|
49
|
+
end
|
50
|
+
|
51
|
+
def password_complexity_validator
|
52
|
+
'CustomInstancePasswordValidator'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
test 'email equal to password can be overridden at the class level' do
|
57
|
+
user = ClassLevelOverrideUser.new(
|
58
|
+
email: 'bob1!@microsoft.com',
|
59
|
+
password: 'bob1!@microsoft.com',
|
60
|
+
password_confirmation: 'bob1!@microsoft.com'
|
61
|
+
)
|
62
|
+
|
63
|
+
assert user.valid?
|
64
|
+
end
|
65
|
+
|
66
|
+
test 'email equal to password can be overridden at the instance level' do
|
67
|
+
user = InstanceLevelOverrideUser.new(
|
68
|
+
email: 'bob1!@microsoft.com',
|
69
|
+
password: 'bob1!@microsoft.com',
|
70
|
+
password_confirmation: 'bob1!@microsoft.com'
|
71
|
+
)
|
72
|
+
|
73
|
+
assert user.valid?
|
74
|
+
end
|
75
|
+
|
76
|
+
test 'email validation can be overridden at the class level' do
|
77
|
+
user = ClassLevelOverrideUser.new(
|
78
|
+
email: 'bob1!@f.com',
|
79
|
+
password: 'Pa3zZ1!!aaaaaa',
|
80
|
+
password_confirmation: 'Pa3zZ1!!aaaaaa'
|
81
|
+
)
|
82
|
+
|
83
|
+
assert user.valid?
|
84
|
+
end
|
85
|
+
|
86
|
+
test 'email validation can be overridden at the instance level' do
|
87
|
+
user = InstanceLevelOverrideUser.new(
|
88
|
+
email: 'bob1!@f.com',
|
89
|
+
password: 'Pa3zZ1!!aaaaaa',
|
90
|
+
password_confirmation: 'Pa3zZ1!!aaaaaa'
|
91
|
+
)
|
92
|
+
|
93
|
+
assert user.valid?
|
94
|
+
end
|
95
|
+
|
96
|
+
test 'password complexity can be overridden at the class level' do
|
97
|
+
user = ClassLevelOverrideUser.new(
|
98
|
+
email: 'bob@microsoft.com',
|
99
|
+
password: 'PASSwordddd',
|
100
|
+
password_confirmation: 'PASSwordddd'
|
101
|
+
)
|
102
|
+
|
103
|
+
assert user.invalid?
|
104
|
+
assert_equal(
|
105
|
+
['Password must contain at least one punctuation mark or symbol'],
|
106
|
+
user.errors.full_messages
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
test 'password complexity can be overridden at the instance level' do
|
111
|
+
user = InstanceLevelOverrideUser.new(
|
112
|
+
email: 'bob@microsoft.com',
|
113
|
+
password: 'PASSwordddd',
|
114
|
+
password_confirmation: 'PASSwordddd'
|
115
|
+
)
|
116
|
+
|
117
|
+
assert user.invalid?
|
118
|
+
assert_equal(
|
119
|
+
['Password must contain at least 2 punctuation marks or symbols'],
|
120
|
+
user.errors.full_messages
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
test 'password length can be overridden at the class level' do
|
125
|
+
user = ClassLevelOverrideUser.new(
|
126
|
+
email: 'bob@microsoft.com',
|
127
|
+
password: 'Pa3zZ1!',
|
128
|
+
password_confirmation: 'Pa3zZ1!'
|
129
|
+
)
|
130
|
+
|
131
|
+
assert user.invalid?
|
132
|
+
assert_equal(
|
133
|
+
['Password is too short (minimum is 10 characters)'],
|
134
|
+
user.errors.full_messages
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
test 'password length can be overridden at the instance level' do
|
139
|
+
user = InstanceLevelOverrideUser.new(
|
140
|
+
email: 'bob@microsoft.com',
|
141
|
+
password: 'Pa3zZ1!!',
|
142
|
+
password_confirmation: 'Pa3zZ1!!'
|
143
|
+
)
|
144
|
+
|
145
|
+
assert user.invalid?
|
146
|
+
assert_equal(
|
147
|
+
['Password is too short (minimum is 11 characters)'],
|
148
|
+
user.errors.full_messages
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
test 'password validator can be overridden at the instance level' do
|
153
|
+
password = '!' * 11 # 11 characters, all symbols
|
154
|
+
user = InstanceLevelOverrideUser.new(
|
155
|
+
email: 'bob@microsoft.com',
|
156
|
+
password: password,
|
157
|
+
password_confirmation: password
|
158
|
+
)
|
159
|
+
|
160
|
+
assert user.invalid?
|
161
|
+
# This validation error only occurs when the CustomInstancePasswordValidator
|
162
|
+
# is used.
|
163
|
+
assert_equal(
|
164
|
+
['Password must contain at least one letter or number'],
|
165
|
+
user.errors.full_messages
|
166
|
+
)
|
167
|
+
end
|
168
|
+
|
169
|
+
test 'password validator can be overridden at the class level' do
|
170
|
+
password = '!' * 10 # 10 characters, all symbols
|
171
|
+
user = ClassLevelOverrideUser.new(
|
172
|
+
email: 'bob@microsoft.com',
|
173
|
+
password: password,
|
174
|
+
password_confirmation: password
|
175
|
+
)
|
176
|
+
|
177
|
+
assert user.invalid?
|
178
|
+
# This validation error only occurs when the CustomClassPasswordValidator
|
179
|
+
# is used.
|
180
|
+
assert_equal(
|
181
|
+
['Password must contain at least one letter'],
|
182
|
+
user.errors.full_messages
|
183
|
+
)
|
184
|
+
end
|
185
|
+
end
|
@@ -10,15 +10,15 @@ class TestSessionLimitable < ActiveSupport::TestCase
|
|
10
10
|
end
|
11
11
|
|
12
12
|
test 'check is not skipped by default' do
|
13
|
-
user = User.
|
14
|
-
|
13
|
+
user = User.new(email: 'bob@microsoft.com', password: 'password1', password_confirmation: 'password1')
|
14
|
+
assert_not(user.skip_session_limitable?)
|
15
15
|
end
|
16
16
|
|
17
17
|
test 'default check can be overridden by record instance' do
|
18
|
-
modified_user = ModifiedUser.
|
19
|
-
|
18
|
+
modified_user = ModifiedUser.new(email: 'bob2@microsoft.com', password: 'password1', password_confirmation: 'password1')
|
19
|
+
assert(modified_user.skip_session_limitable?)
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
class SessionLimitableUser < User
|
23
23
|
devise :session_limitable
|
24
24
|
include ::Mongoid::Mappings if DEVISE_ORM == :mongoid
|
@@ -34,7 +34,7 @@ class TestSessionLimitable < ActiveSupport::TestCase
|
|
34
34
|
assert_nil user.unique_session_id
|
35
35
|
user.update_unique_session_id!('unique_value')
|
36
36
|
user.reload
|
37
|
-
assert_equal user.unique_session_id
|
37
|
+
assert_equal('unique_value', user.unique_session_id)
|
38
38
|
end
|
39
39
|
|
40
40
|
test '#update_unique_session_id!(value) updates invalid record atomically' do
|
@@ -45,13 +45,13 @@ class TestSessionLimitable < ActiveSupport::TestCase
|
|
45
45
|
assert_nil user.unique_session_id
|
46
46
|
user.update_unique_session_id!('unique_value')
|
47
47
|
user.reload
|
48
|
-
assert_equal
|
49
|
-
assert_equal user.unique_session_id
|
48
|
+
assert_equal('bob@microsoft.com', user.email)
|
49
|
+
assert_equal('unique_value', user.unique_session_id)
|
50
50
|
end
|
51
51
|
|
52
52
|
test '#update_unique_session_id!(value) raises an exception on an unpersisted record' do
|
53
53
|
user = User.create
|
54
|
-
|
54
|
+
assert_not user.persisted?
|
55
55
|
assert_raises(Devise::Models::Compatibility::NotPersistedError) { user.update_unique_session_id!('unique_value') }
|
56
56
|
end
|
57
57
|
end
|
@@ -7,7 +7,9 @@ Devise.setup do |config|
|
|
7
7
|
# Should the password expire (e.g 3.months)
|
8
8
|
# config.expire_password_after = false
|
9
9
|
|
10
|
-
# Need 1 char of A-Z, a-z
|
10
|
+
# Need 1 char each of: A-Z, a-z, 0-9, and a punctuation mark or symbol
|
11
|
+
# You may use "digits" in place of "digit" and "symbols" in place of
|
12
|
+
# "symbol" based on your preference
|
11
13
|
# config.password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
|
12
14
|
|
13
15
|
# How many passwords to keep in archive
|
@@ -44,4 +46,7 @@ Devise.setup do |config|
|
|
44
46
|
|
45
47
|
# Allow password to equal the email
|
46
48
|
# config.allow_passwords_equal_to_email = false
|
49
|
+
|
50
|
+
# paranoid_verification will regenerate verification code after failed attempt
|
51
|
+
# config.paranoid_code_regenerate_after_attempt = 10
|
47
52
|
end
|