password_strength 0.2.0 → 0.3.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.
- data/CHANGELOG.rdoc +11 -1
- data/README.rdoc +24 -0
- data/javascripts/jquery.strength.js +6 -6
- data/javascripts/password_strength.js +40 -40
- data/lib/password_strength.rb +1 -0
- data/lib/password_strength/active_record/ar2.rb +25 -2
- data/lib/password_strength/active_record/ar3.rb +25 -2
- data/lib/password_strength/base.rb +36 -12
- data/lib/password_strength/validators/windows2008.rb +37 -0
- data/lib/password_strength/version.rb +1 -1
- data/test/password_strength_test.rb +2 -2
- data/test/validators/windows2008_test.rb +68 -0
- metadata +5 -2
data/CHANGELOG.rdoc
CHANGED
@@ -28,4 +28,14 @@
|
|
28
28
|
|
29
29
|
== 0.1.6 - March 31 2010
|
30
30
|
|
31
|
-
* Added :exclude option
|
31
|
+
* Added :exclude option
|
32
|
+
|
33
|
+
== 0.2.0 - July 27 2010
|
34
|
+
|
35
|
+
* PasswordStrength can be disabled; development mode says hello!
|
36
|
+
|
37
|
+
== 0.3.0 - August 11 2010
|
38
|
+
|
39
|
+
* Increased required password length to 6 characters
|
40
|
+
* Added support for custom validators
|
41
|
+
* Added Windows 2008 validator
|
data/README.rdoc
CHANGED
@@ -64,6 +64,30 @@ If you want to compare your password against other field, you have to set the <t
|
|
64
64
|
|
65
65
|
The available levels are: <tt>:weak</tt>, <tt>:good</tt> and <tt>:strong</tt>
|
66
66
|
|
67
|
+
You can also provide a custom class/module that will test that password.
|
68
|
+
|
69
|
+
validates_strength_of :password, :using => CustomPasswordTester
|
70
|
+
|
71
|
+
Your +CustomPasswordTester+ class should override the default implementation. In practice, you're
|
72
|
+
going to override only the +test+ method that must call one of the following methods:
|
73
|
+
<tt>invalid!</tt>, <tt>weak!</tt>, <tt>good!</tt> or <tt>strong!</tt>.
|
74
|
+
|
75
|
+
class CustomPasswordTester < PasswordStrength::Base
|
76
|
+
def test
|
77
|
+
if password != "mypass"
|
78
|
+
invalid!
|
79
|
+
else
|
80
|
+
strong!
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
The tester above will accept only +mypass+ as password.
|
86
|
+
|
87
|
+
PasswordStrength implements two validators: <tt>PasswordStrength::Base</tt> and <tt>PasswordStrength::Validators::Windows2008</tt>.
|
88
|
+
|
89
|
+
<b>ATTENTION:</b> Custom validators are not supported by JavaScript yet!
|
90
|
+
|
67
91
|
= JavaScript
|
68
92
|
|
69
93
|
The PasswordStrength also implements the algorithm in the JavaScript.
|
@@ -1,11 +1,11 @@
|
|
1
1
|
(function($){
|
2
2
|
$.strength = function(username, password, options, callback) {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
if (typeof(options) == "function") {
|
4
|
+
callback = options;
|
5
|
+
options = {};
|
6
|
+
} else if (!options) {
|
7
|
+
options = {};
|
8
|
+
}
|
9
9
|
|
10
10
|
var usernameField = $(username);
|
11
11
|
var passwordField = $(password);
|
@@ -13,40 +13,40 @@ var PasswordStrength = function() {
|
|
13
13
|
this.score = 0;
|
14
14
|
|
15
15
|
if (this.containInvalidMatches()) {
|
16
|
-
|
16
|
+
this.status = "invalid";
|
17
17
|
} else {
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
18
|
+
this.score += this.scoreFor("password_size");
|
19
|
+
this.score += this.scoreFor("numbers");
|
20
|
+
this.score += this.scoreFor("symbols");
|
21
|
+
this.score += this.scoreFor("uppercase_lowercase");
|
22
|
+
this.score += this.scoreFor("numbers_chars");
|
23
|
+
this.score += this.scoreFor("numbers_symbols");
|
24
|
+
this.score += this.scoreFor("symbols_chars");
|
25
|
+
this.score += this.scoreFor("only_chars");
|
26
|
+
this.score += this.scoreFor("only_numbers");
|
27
|
+
this.score += this.scoreFor("username");
|
28
|
+
this.score += this.scoreFor("sequences");
|
29
|
+
this.score += this.scoreFor("repetitions");
|
30
|
+
|
31
|
+
if (this.score < 0) {
|
32
|
+
this.score = 0;
|
33
|
+
}
|
34
|
+
|
35
|
+
if (this.score > 100) {
|
36
|
+
this.score = 100;
|
37
|
+
}
|
38
|
+
|
39
|
+
if (this.score < 35) {
|
40
|
+
this.status = "weak";
|
41
|
+
}
|
42
|
+
|
43
|
+
if (this.score >= 35 && this.score < 70) {
|
44
|
+
this.status = "good";
|
45
|
+
}
|
46
|
+
|
47
|
+
if (this.score >= 70) {
|
48
|
+
this.status = "strong";
|
49
|
+
}
|
50
50
|
}
|
51
51
|
|
52
52
|
return this.score;
|
@@ -162,15 +162,15 @@ var PasswordStrength = function() {
|
|
162
162
|
};
|
163
163
|
|
164
164
|
this.containInvalidMatches = function() {
|
165
|
-
|
166
|
-
|
167
|
-
|
165
|
+
if (!this.exclude) {
|
166
|
+
return false;
|
167
|
+
}
|
168
168
|
|
169
|
-
|
170
|
-
|
171
|
-
|
169
|
+
if (!this.exclude.test) {
|
170
|
+
return false;
|
171
|
+
}
|
172
172
|
|
173
|
-
|
173
|
+
return this.exclude.test(this.password.toString());
|
174
174
|
};
|
175
175
|
|
176
176
|
this.sequences = function(text) {
|
data/lib/password_strength.rb
CHANGED
@@ -14,16 +14,39 @@ module PasswordStrength
|
|
14
14
|
#
|
15
15
|
# The available levels are: <tt>:weak</tt>, <tt>:good</tt> and <tt>:strong</tt>
|
16
16
|
#
|
17
|
+
# You can also provide a custom class/module that will test that password.
|
18
|
+
#
|
19
|
+
# validates_strength_of :password, :using => CustomPasswordTester
|
20
|
+
#
|
21
|
+
# Your +CustomPasswordTester+ class should override the default implementation. In practice, you're
|
22
|
+
# going to override only the +test+ method that must call one of the following methods:
|
23
|
+
# <tt>invalid!</tt>, <tt>weak!</tt>, <tt>good!</tt> or <tt>strong!</tt>.
|
24
|
+
#
|
25
|
+
# class CustomPasswordTester < PasswordStrength::Base
|
26
|
+
# def test
|
27
|
+
# if password != "mypass"
|
28
|
+
# invalid!
|
29
|
+
# else
|
30
|
+
# strong!
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# The tester above will accept only +mypass+ as password.
|
36
|
+
#
|
37
|
+
# PasswordStrength implements two validators: <tt>PasswordStrength::Base</tt> and <tt>PasswordStrength::Validators::Windows2008</tt>.
|
38
|
+
#
|
17
39
|
def validates_strength_of(*attr_names)
|
18
40
|
options = attr_names.extract_options!
|
19
|
-
options.reverse_merge!(:level => :good, :with => :username)
|
41
|
+
options.reverse_merge!(:level => :good, :with => :username, :using => PasswordStrength::Base)
|
20
42
|
|
21
43
|
raise ArgumentError, "The :with option must be supplied" unless options.include?(:with)
|
22
44
|
raise ArgumentError, "The :exclude options must be an array of string or regular expression" if options[:exclude] && !options[:exclude].kind_of?(Array) && !options[:exclude].kind_of?(Regexp)
|
23
45
|
raise ArgumentError, "The :level option must be one of [:weak, :good, :strong]" unless [:weak, :good, :strong].include?(options[:level])
|
24
46
|
|
25
47
|
validates_each(attr_names, options) do |record, attr_name, value|
|
26
|
-
strength =
|
48
|
+
strength = options[:using].new(record.send(options[:with]), value, :exclude => options[:exclude])
|
49
|
+
strength.test
|
27
50
|
record.errors.add(attr_name, :too_weak, options) unless PasswordStrength.enabled && strength.valid?(options[:level])
|
28
51
|
end
|
29
52
|
end
|
@@ -2,11 +2,12 @@ module ActiveModel # :nodoc:
|
|
2
2
|
module Validations # :nodoc:
|
3
3
|
class StrengthValidator < EachValidator # :nodoc: all
|
4
4
|
def initialize(options)
|
5
|
-
super(options.reverse_merge(:level => :good, :with => :username))
|
5
|
+
super(options.reverse_merge(:level => :good, :with => :username, :using => PasswordStrength::Base))
|
6
6
|
end
|
7
7
|
|
8
8
|
def validate_each(record, attribute, value)
|
9
|
-
strength =
|
9
|
+
strength = options[:using].new(record.send(options[:with]), value, :exclude => options[:exclude])
|
10
|
+
strength.test
|
10
11
|
record.errors.add(attribute, :too_weak, options) unless PasswordStrength.enabled && strength.valid?(options[:level])
|
11
12
|
end
|
12
13
|
|
@@ -33,6 +34,28 @@ module ActiveModel # :nodoc:
|
|
33
34
|
#
|
34
35
|
# The available levels are: <tt>:weak</tt>, <tt>:good</tt> and <tt>:strong</tt>
|
35
36
|
#
|
37
|
+
# You can also provide a custom class/module that will test that password.
|
38
|
+
#
|
39
|
+
# validates_strength_of :password, :using => CustomPasswordTester
|
40
|
+
#
|
41
|
+
# Your +CustomPasswordTester+ class should override the default implementation. In practice, you're
|
42
|
+
# going to override only the +test+ method that must call one of the following methods:
|
43
|
+
# <tt>invalid!</tt>, <tt>weak!</tt>, <tt>good!</tt> or <tt>strong!</tt>.
|
44
|
+
#
|
45
|
+
# class CustomPasswordTester < PasswordStrength::Base
|
46
|
+
# def test
|
47
|
+
# if password != "mypass"
|
48
|
+
# invalid!
|
49
|
+
# else
|
50
|
+
# strong!
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# The tester above will accept only +mypass+ as password.
|
56
|
+
#
|
57
|
+
# PasswordStrength implements two validators: <tt>PasswordStrength::Base</tt> and <tt>PasswordStrength::Validators::Windows2008</tt>.
|
58
|
+
#
|
36
59
|
def validates_strength_of(*attr_names)
|
37
60
|
validates_with StrengthValidator, _merge_attributes(attr_names)
|
38
61
|
end
|
@@ -4,6 +4,10 @@ module PasswordStrength
|
|
4
4
|
MULTIPLE_SYMBOLS_RE = /[!@#\$%^&*?_~-].*?[!@#\$%^&*?_~-]/
|
5
5
|
SYMBOL_RE = /[!@#\$%^&*?_~-]/
|
6
6
|
UPPERCASE_LOWERCASE_RE = /([a-z].*[A-Z])|([A-Z].*[a-z])/
|
7
|
+
INVALID = :invalid
|
8
|
+
WEAK = :weak
|
9
|
+
STRONG = :strong
|
10
|
+
GOOD = :good
|
7
11
|
|
8
12
|
# Hold the username that will be matched against password.
|
9
13
|
attr_accessor :username
|
@@ -42,11 +46,11 @@ module PasswordStrength
|
|
42
46
|
|
43
47
|
# Check if the password has the specified score.
|
44
48
|
# Level can be +:weak+, +:good+ or +:strong+.
|
45
|
-
def valid?(level =
|
49
|
+
def valid?(level = GOOD)
|
46
50
|
case level
|
47
|
-
when
|
51
|
+
when STRONG then
|
48
52
|
strong?
|
49
|
-
when
|
53
|
+
when GOOD then
|
50
54
|
good? || strong?
|
51
55
|
else
|
52
56
|
!invalid?
|
@@ -55,22 +59,42 @@ module PasswordStrength
|
|
55
59
|
|
56
60
|
# Check if the password has been detected as strong.
|
57
61
|
def strong?
|
58
|
-
status ==
|
62
|
+
status == STRONG
|
63
|
+
end
|
64
|
+
|
65
|
+
# Mark password as strong.
|
66
|
+
def strong!
|
67
|
+
@status = STRONG
|
59
68
|
end
|
60
69
|
|
61
70
|
# Check if the password has been detected as weak.
|
62
71
|
def weak?
|
63
|
-
status ==
|
72
|
+
status == WEAK
|
73
|
+
end
|
74
|
+
|
75
|
+
# Mark password as weak.
|
76
|
+
def weak!
|
77
|
+
@status = WEAK
|
64
78
|
end
|
65
79
|
|
66
80
|
# Check if the password has been detected as good.
|
67
81
|
def good?
|
68
|
-
status ==
|
82
|
+
status == GOOD
|
83
|
+
end
|
84
|
+
|
85
|
+
# Mark password as good.
|
86
|
+
def good!
|
87
|
+
@status = GOOD
|
69
88
|
end
|
70
89
|
|
71
90
|
# Check if password has invalid characters based on PasswordStrength::Base#exclude.
|
72
91
|
def invalid?
|
73
|
-
status ==
|
92
|
+
status == INVALID
|
93
|
+
end
|
94
|
+
|
95
|
+
# Mark password as invalid.
|
96
|
+
def invalid!
|
97
|
+
@status = INVALID
|
74
98
|
end
|
75
99
|
|
76
100
|
# Return the score for the specified rule.
|
@@ -92,7 +116,7 @@ module PasswordStrength
|
|
92
116
|
|
93
117
|
case name
|
94
118
|
when :password_size then
|
95
|
-
if password.size <
|
119
|
+
if password.size < 6
|
96
120
|
score = -100
|
97
121
|
else
|
98
122
|
score = password.size * 4
|
@@ -136,7 +160,7 @@ module PasswordStrength
|
|
136
160
|
@score = 0
|
137
161
|
|
138
162
|
if contain_invalid_matches?
|
139
|
-
|
163
|
+
invalid!
|
140
164
|
else
|
141
165
|
@score += score_for(:password_size)
|
142
166
|
@score += score_for(:numbers)
|
@@ -154,9 +178,9 @@ module PasswordStrength
|
|
154
178
|
@score = 0 if score < 0
|
155
179
|
@score = 100 if score > 100
|
156
180
|
|
157
|
-
|
158
|
-
|
159
|
-
|
181
|
+
weak! if score < 35
|
182
|
+
good! if score >= 35 && score < 70
|
183
|
+
strong! if score >= 70
|
160
184
|
end
|
161
185
|
|
162
186
|
score
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module PasswordStrength
|
2
|
+
module Validators
|
3
|
+
# Validates a Windows 2008 password against the following rules:
|
4
|
+
#
|
5
|
+
# * Passwords cannot contain the user's account name or parts of the user's full name that exceed two consecutive characters.
|
6
|
+
# * Passwords must be at least six characters in length.
|
7
|
+
# * Passwords must contain characters from three of the following four categories: English uppercase characters (A through Z); English lowercase characters (a through z); Base 10 digits (0 through 9); Non-alphabetic characters (for example, !, $, #, %).
|
8
|
+
#
|
9
|
+
# Reference: http://technet.microsoft.com/en-us/library/cc264456.aspx
|
10
|
+
#
|
11
|
+
class Windows2008 < PasswordStrength::Base
|
12
|
+
def test
|
13
|
+
return invalid! if password.size < 6
|
14
|
+
|
15
|
+
variety = 0
|
16
|
+
variety += 1 if password =~ /[A-Z]/
|
17
|
+
variety += 1 if password =~ /[a-z]/
|
18
|
+
variety += 1 if password =~ /[0-9]/
|
19
|
+
variety += 1 if password =~ PasswordStrength::Base::SYMBOL_RE
|
20
|
+
|
21
|
+
return invalid! if variety < 3
|
22
|
+
return invalid! if password_contains_username?
|
23
|
+
|
24
|
+
strong!
|
25
|
+
end
|
26
|
+
|
27
|
+
def password_contains_username?
|
28
|
+
0.upto(password.size - 1) do |i|
|
29
|
+
substring = password[i, 3]
|
30
|
+
return true if username.include?(substring)
|
31
|
+
end
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -164,8 +164,8 @@ class TestPasswordStrength < Test::Unit::TestCase
|
|
164
164
|
end
|
165
165
|
|
166
166
|
def test_password_length
|
167
|
-
@strength.password = "
|
168
|
-
assert_equal
|
167
|
+
@strength.password = "123456"
|
168
|
+
assert_equal 24, @strength.score_for(:password_size)
|
169
169
|
end
|
170
170
|
|
171
171
|
def test_password_with_numbers
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class Window2008Test < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
PasswordStrength.enabled = true
|
6
|
+
Object.class_eval { remove_const("User") } if defined?(User)
|
7
|
+
load "user.rb"
|
8
|
+
User.validates_strength_of :password, :using => PasswordStrength::Validators::Windows2008
|
9
|
+
|
10
|
+
@user = User.new(:username => "Administrator")
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_require_password_to_include_three_character_categories
|
14
|
+
@user.update_attributes :password => "abcABC"
|
15
|
+
assert @user.errors.full_messages.any?
|
16
|
+
|
17
|
+
@user.update_attributes :password => "abc123"
|
18
|
+
assert @user.errors.full_messages.any?
|
19
|
+
|
20
|
+
@user.update_attributes :password => "abc$!~"
|
21
|
+
assert @user.errors.full_messages.any?
|
22
|
+
|
23
|
+
@user.update_attributes :password => "123ABC"
|
24
|
+
assert @user.errors.full_messages.any?
|
25
|
+
|
26
|
+
@user.update_attributes :password => "123$!~"
|
27
|
+
assert @user.errors.full_messages.any?
|
28
|
+
|
29
|
+
@user.update_attributes :password => "ABC$!~"
|
30
|
+
assert @user.errors.full_messages.any?
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_invalidate_password_that_includes_username
|
34
|
+
@user.update_attributes :password => "abc$!~ABC123Admin"
|
35
|
+
assert @user.errors.full_messages.any?
|
36
|
+
|
37
|
+
@user.update_attributes :password => "abc$!~ABC123Adm"
|
38
|
+
assert @user.errors.full_messages.any?
|
39
|
+
|
40
|
+
@user.update_attributes :password => "abc$!~ABC123admin"
|
41
|
+
assert @user.errors.full_messages.any?
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_invalidate_short_passwords
|
45
|
+
@user.update_attributes :password => "12345"
|
46
|
+
assert @user.errors.full_messages.any?
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_accept_numbers_uppercases_and_lowercases
|
50
|
+
@user.update_attributes :password => "123ABCabc"
|
51
|
+
assert @user.valid?
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_accept_numbers_uppercases_and_special_chars
|
55
|
+
@user.update_attributes :password => "123ABC$!~"
|
56
|
+
assert @user.valid?
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_accept_numbers_lowercases_and_special_chars
|
60
|
+
@user.update_attributes :password => "123ABC$!~"
|
61
|
+
assert @user.valid?
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_accept_uppercases_lowercases_and_special_chars
|
65
|
+
@user.update_attributes :password => "ABCabc$!~"
|
66
|
+
assert @user.valid?
|
67
|
+
end
|
68
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: password_strength
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nando Vieira
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-08-11 00:00:00 -03:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -50,6 +50,7 @@ files:
|
|
50
50
|
- lib/password_strength/active_record/ar2.rb
|
51
51
|
- lib/password_strength/active_record/ar3.rb
|
52
52
|
- lib/password_strength/base.rb
|
53
|
+
- lib/password_strength/validators/windows2008.rb
|
53
54
|
- lib/password_strength/version.rb
|
54
55
|
- locales/en.yml
|
55
56
|
- locales/pt.yml
|
@@ -65,6 +66,7 @@ files:
|
|
65
66
|
- test/schema.rb
|
66
67
|
- test/test_helper.rb
|
67
68
|
- test/user.rb
|
69
|
+
- test/validators/windows2008_test.rb
|
68
70
|
has_rdoc: true
|
69
71
|
homepage: http://github.com/fnando/password_strength
|
70
72
|
licenses: []
|
@@ -99,3 +101,4 @@ test_files:
|
|
99
101
|
- test/schema.rb
|
100
102
|
- test/test_helper.rb
|
101
103
|
- test/user.rb
|
104
|
+
- test/validators/windows2008_test.rb
|