password_strength 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +9 -0
- data/README.rdoc +5 -0
- data/lib/jquery.strength.js +11 -1
- data/lib/password_strength/active_record/ar2.rb +2 -1
- data/lib/password_strength/active_record/ar3.rb +2 -1
- data/lib/password_strength/base.rb +61 -26
- data/lib/password_strength/version.rb +1 -1
- data/lib/password_strength.js +52 -31
- data/lib/password_strength.rb +15 -2
- data/test/active_record_test.rb +7 -0
- data/test/jquery_strength_test.js +10 -0
- data/test/password_strength_test.js +17 -3
- data/test/password_strength_test.rb +14 -0
- metadata +3 -3
data/CHANGELOG.rdoc
CHANGED
@@ -20,3 +20,12 @@
|
|
20
20
|
|
21
21
|
* jQuery function can accept string instead of selectors
|
22
22
|
* Added repetition penalization
|
23
|
+
|
24
|
+
== 0.1.5 - March 23 2010
|
25
|
+
|
26
|
+
* Added ActiveSupport as dependency
|
27
|
+
* Added JavaScript implementation for JavaScript
|
28
|
+
|
29
|
+
== 0.1.6 - March 31 2010
|
30
|
+
|
31
|
+
* Added :exclude option
|
data/README.rdoc
CHANGED
@@ -76,6 +76,11 @@ The PasswordStrength also implements the algorithm in the JavaScript.
|
|
76
76
|
|
77
77
|
The API is basically the same!
|
78
78
|
|
79
|
+
You can use the <tt>:exclude</tt> option. Only regular expressions are supported for now.
|
80
|
+
|
81
|
+
var strength = PasswordStrength.test("johndoe", "password with whitespaces", {exclude: /\s/});
|
82
|
+
strength.isInvalid();
|
83
|
+
|
79
84
|
Additionaly, a jQuery plugin is available.
|
80
85
|
|
81
86
|
$.strength("#username", "#password");
|
data/lib/jquery.strength.js
CHANGED
@@ -1,8 +1,18 @@
|
|
1
1
|
(function($){
|
2
|
-
$.strength = function(username, password, callback) {
|
2
|
+
$.strength = function(username, password, options, callback) {
|
3
|
+
if (typeof(options) == "function") {
|
4
|
+
callback = options;
|
5
|
+
options = {};
|
6
|
+
} else if (!options) {
|
7
|
+
options = {};
|
8
|
+
}
|
9
|
+
|
3
10
|
var usernameField = $(username);
|
4
11
|
var passwordField = $(password);
|
5
12
|
var strength = new PasswordStrength();
|
13
|
+
|
14
|
+
strength.exclude = options["exclude"];
|
15
|
+
|
6
16
|
callback = callback || $.strength.callback;
|
7
17
|
|
8
18
|
var handler = function(){
|
@@ -19,10 +19,11 @@ module PasswordStrength
|
|
19
19
|
options.reverse_merge!(:level => :good, :with => :username)
|
20
20
|
|
21
21
|
raise ArgumentError, "The :with option must be supplied" unless options.include?(:with)
|
22
|
+
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)
|
22
23
|
raise ArgumentError, "The :level option must be one of [:weak, :good, :strong]" unless [:weak, :good, :strong].include?(options[:level])
|
23
24
|
|
24
25
|
validates_each(attr_names, options) do |record, attr_name, value|
|
25
|
-
strength = PasswordStrength.test(record.send(options[:with]), value)
|
26
|
+
strength = PasswordStrength.test(record.send(options[:with]), value, :exclude => options[:exclude])
|
26
27
|
record.errors.add(attr_name, :too_weak, :default => options[:message]) unless strength.valid?(options[:level])
|
27
28
|
end
|
28
29
|
end
|
@@ -6,12 +6,13 @@ module ActiveModel # :nodoc:
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def validate_each(record, attribute, value)
|
9
|
-
strength = PasswordStrength.test(record.send(options[:with]), value)
|
9
|
+
strength = PasswordStrength.test(record.send(options[:with]), value, :exclude => options[:exclude])
|
10
10
|
record.errors.add(attribute, :too_weak, :default => options[:message], :value => value) unless strength.valid?(options[:level])
|
11
11
|
end
|
12
12
|
|
13
13
|
def check_validity!
|
14
14
|
raise ArgumentError, "The :with option must be supplied" unless options.include?(:with)
|
15
|
+
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)
|
15
16
|
raise ArgumentError, "The :level option must be one of [:weak, :good, :strong]" unless [:weak, :good, :strong].include?(options[:level])
|
16
17
|
super
|
17
18
|
end
|
@@ -5,34 +5,51 @@ module PasswordStrength
|
|
5
5
|
SYMBOL_RE = /[!@#\$%^&*?_~-]/
|
6
6
|
UPPERCASE_LOWERCASE_RE = /([a-z].*[A-Z])|([A-Z].*[a-z])/
|
7
7
|
|
8
|
-
# Hold the username that will be matched against password
|
8
|
+
# Hold the username that will be matched against password.
|
9
9
|
attr_accessor :username
|
10
10
|
|
11
|
-
# The password that will be tested
|
11
|
+
# The password that will be tested.
|
12
12
|
attr_accessor :password
|
13
13
|
|
14
|
-
# The score for the latest test. Will be nil if the password has not been tested.
|
14
|
+
# The score for the latest test. Will be +nil+ if the password has not been tested.
|
15
15
|
attr_reader :score
|
16
16
|
|
17
|
-
# The current test status. Can be +:weak+, +:good+ or +:
|
17
|
+
# The current test status. Can be +:weak+, +:good+, +:strong+ or +:invalid+.
|
18
18
|
attr_reader :status
|
19
19
|
|
20
|
-
|
20
|
+
# Set what characters cannot be present on password.
|
21
|
+
# Can be a regular expression or array.
|
22
|
+
#
|
23
|
+
# strength = PasswordStrength.test("john", "password with whitespaces", :exclude => [" ", "asdf"])
|
24
|
+
# strength = PasswordStrength.test("john", "password with whitespaces", :exclude => /\s/)
|
25
|
+
#
|
26
|
+
# Then you can check the test result:
|
27
|
+
#
|
28
|
+
# strength.valid?(:weak)
|
29
|
+
# #=> false
|
30
|
+
#
|
31
|
+
# strength.status
|
32
|
+
# #=> :invalid
|
33
|
+
#
|
34
|
+
attr_accessor :exclude
|
35
|
+
|
36
|
+
def initialize(username, password, options = {})
|
21
37
|
@username = username.to_s
|
22
38
|
@password = password.to_s
|
23
39
|
@score = 0
|
40
|
+
@exclude = options[:exclude]
|
24
41
|
end
|
25
42
|
|
26
43
|
# Check if the password has the specified score.
|
27
44
|
# Level can be +:weak+, +:good+ or +:strong+.
|
28
|
-
def valid?(level)
|
45
|
+
def valid?(level = :good)
|
29
46
|
case level
|
30
47
|
when :strong then
|
31
48
|
strong?
|
32
49
|
when :good then
|
33
50
|
good? || strong?
|
34
51
|
else
|
35
|
-
|
52
|
+
!invalid?
|
36
53
|
end
|
37
54
|
end
|
38
55
|
|
@@ -51,6 +68,11 @@ module PasswordStrength
|
|
51
68
|
status == :good
|
52
69
|
end
|
53
70
|
|
71
|
+
# Check if password has invalid characters based on PasswordStrength::Base#exclude.
|
72
|
+
def invalid?
|
73
|
+
status == :invalid
|
74
|
+
end
|
75
|
+
|
54
76
|
# Return the score for the specified rule.
|
55
77
|
# Available rules:
|
56
78
|
#
|
@@ -109,31 +131,44 @@ module PasswordStrength
|
|
109
131
|
score
|
110
132
|
end
|
111
133
|
|
134
|
+
# Run all tests on password and return the final score.
|
112
135
|
def test
|
113
136
|
@score = 0
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
137
|
+
|
138
|
+
if contain_invalid_matches?
|
139
|
+
@status = :invalid
|
140
|
+
else
|
141
|
+
@score += score_for(:password_size)
|
142
|
+
@score += score_for(:numbers)
|
143
|
+
@score += score_for(:symbols)
|
144
|
+
@score += score_for(:uppercase_lowercase)
|
145
|
+
@score += score_for(:numbers_chars)
|
146
|
+
@score += score_for(:numbers_symbols)
|
147
|
+
@score += score_for(:symbols_chars)
|
148
|
+
@score += score_for(:only_chars)
|
149
|
+
@score += score_for(:only_numbers)
|
150
|
+
@score += score_for(:username)
|
151
|
+
@score += score_for(:sequences)
|
152
|
+
@score += score_for(:repetitions)
|
153
|
+
|
154
|
+
@score = 0 if score < 0
|
155
|
+
@score = 100 if score > 100
|
156
|
+
|
157
|
+
@status = :weak if score < 35
|
158
|
+
@status = :good if score >= 35 && score < 70
|
159
|
+
@status = :strong if score >= 70
|
160
|
+
end
|
133
161
|
|
134
162
|
score
|
135
163
|
end
|
136
164
|
|
165
|
+
def contain_invalid_matches? # :nodoc:
|
166
|
+
return false unless exclude
|
167
|
+
regex = exclude
|
168
|
+
regex = /#{exclude.collect {|i| Regexp.escape(i)}.join("|")}/ if exclude.kind_of?(Array)
|
169
|
+
password.to_s =~ regex
|
170
|
+
end
|
171
|
+
|
137
172
|
def repetitions(text, size) # :nodoc:
|
138
173
|
text = text.mb_chars
|
139
174
|
count = 0
|
data/lib/password_strength.js
CHANGED
@@ -11,37 +11,42 @@ var PasswordStrength = function() {
|
|
11
11
|
|
12
12
|
this.test = function() {
|
13
13
|
this.score = 0;
|
14
|
-
this.score += this.scoreFor("password_size");
|
15
|
-
this.score += this.scoreFor("numbers");
|
16
|
-
this.score += this.scoreFor("symbols");
|
17
|
-
this.score += this.scoreFor("uppercase_lowercase");
|
18
|
-
this.score += this.scoreFor("numbers_chars");
|
19
|
-
this.score += this.scoreFor("numbers_symbols");
|
20
|
-
this.score += this.scoreFor("symbols_chars");
|
21
|
-
this.score += this.scoreFor("only_chars");
|
22
|
-
this.score += this.scoreFor("only_numbers");
|
23
|
-
this.score += this.scoreFor("username");
|
24
|
-
this.score += this.scoreFor("sequences");
|
25
|
-
this.score += this.scoreFor("repetitions");
|
26
|
-
|
27
|
-
if (this.score < 0) {
|
28
|
-
this.score = 0;
|
29
|
-
}
|
30
|
-
|
31
|
-
if (this.score > 100) {
|
32
|
-
this.score = 100;
|
33
|
-
}
|
34
|
-
|
35
|
-
if (this.score < 35) {
|
36
|
-
this.status = "weak";
|
37
|
-
}
|
38
|
-
|
39
|
-
if (this.score >= 35 && this.score < 70) {
|
40
|
-
this.status = "good";
|
41
|
-
}
|
42
14
|
|
43
|
-
if (this.
|
44
|
-
|
15
|
+
if (this.containInvalidMatches()) {
|
16
|
+
this.status = "invalid";
|
17
|
+
} else {
|
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
|
+
}
|
45
50
|
}
|
46
51
|
|
47
52
|
return this.score;
|
@@ -142,16 +147,32 @@ var PasswordStrength = function() {
|
|
142
147
|
return this.status == "strong";
|
143
148
|
};
|
144
149
|
|
150
|
+
this.isInvalid = function() {
|
151
|
+
return this.status == "invalid";
|
152
|
+
};
|
153
|
+
|
145
154
|
this.isValid = function(level) {
|
146
155
|
if(level == "strong") {
|
147
156
|
return this.isStrong();
|
148
157
|
} else if (level == "good") {
|
149
158
|
return this.isStrong() || this.isGood();
|
150
159
|
} else {
|
151
|
-
return
|
160
|
+
return !this.containInvalidMatches();
|
152
161
|
}
|
153
162
|
};
|
154
163
|
|
164
|
+
this.containInvalidMatches = function() {
|
165
|
+
if (!this.exclude) {
|
166
|
+
return false;
|
167
|
+
}
|
168
|
+
|
169
|
+
if (!this.exclude.test) {
|
170
|
+
return false;
|
171
|
+
}
|
172
|
+
|
173
|
+
return this.exclude.test(this.password.toString());
|
174
|
+
};
|
175
|
+
|
155
176
|
this.sequences = function(text) {
|
156
177
|
var matches = 0;
|
157
178
|
var sequenceSize = 0;
|
data/lib/password_strength.rb
CHANGED
@@ -10,8 +10,21 @@ module PasswordStrength
|
|
10
10
|
# strength.weak?
|
11
11
|
# #=> true
|
12
12
|
#
|
13
|
-
|
14
|
-
|
13
|
+
# You can provide an options hash.
|
14
|
+
#
|
15
|
+
# strength = PasswordStrength.test("johndoe", "^Str0ng P4ssw0rd$", :exclude => /\s/)
|
16
|
+
# strength.status
|
17
|
+
# #=> :invalid
|
18
|
+
#
|
19
|
+
# strength.invalid?
|
20
|
+
# #=> true
|
21
|
+
#
|
22
|
+
# You can also provide an array.
|
23
|
+
#
|
24
|
+
# strength = PasswordStrength.test("johndoe", "^Str0ng P4ssw0rd$", :exclude => [" ", "asdf", "123"])
|
25
|
+
#
|
26
|
+
def self.test(username, password, options = {})
|
27
|
+
strength = Base.new(username, password, options)
|
15
28
|
strength.test
|
16
29
|
strength
|
17
30
|
end
|
data/test/active_record_test.rb
CHANGED
@@ -66,4 +66,11 @@ class TestActiveRecord < Test::Unit::TestCase
|
|
66
66
|
@user.update_attributes :password => "johndoe"
|
67
67
|
assert @user.errors.full_messages.any?
|
68
68
|
end
|
69
|
+
|
70
|
+
def test_exclude_option
|
71
|
+
User.validates_strength_of :password, :exclude => /\s/
|
72
|
+
|
73
|
+
@user.update_attributes :password => "^password with whitespaces 1234ASDF$"
|
74
|
+
assert @user.errors.full_messages.any?
|
75
|
+
end
|
69
76
|
end
|
@@ -90,6 +90,16 @@ new Test.Unit.Runner({
|
|
90
90
|
assertEqual("mypass", strength.password);
|
91
91
|
});
|
92
92
|
|
93
|
+
$("#username").trigger("keydown");
|
94
|
+
}},
|
95
|
+
|
96
|
+
// Exclude option as regular expression
|
97
|
+
testExcludeOption: function() { with(this) {
|
98
|
+
$.strength("#username", "password with whitespaces", {exclude: /\s/}, function(username, password, strength){
|
99
|
+
assertEqual("invalid", strength.status);
|
100
|
+
assert(strength.isInvalid());
|
101
|
+
});
|
102
|
+
|
93
103
|
$("#username").trigger("keydown");
|
94
104
|
}}
|
95
105
|
});
|
@@ -24,15 +24,17 @@ new Test.Unit.Runner({
|
|
24
24
|
assert(strength.isValid("good"));
|
25
25
|
assertEqual(false, strength.isWeak());
|
26
26
|
assertEqual(false, strength.isStrong());
|
27
|
+
assertEqual(false, strength.isInvalid());
|
27
28
|
}},
|
28
29
|
|
29
30
|
// Weak strength
|
30
31
|
testWeakStrength: function() { with(this) {
|
31
32
|
strength.status = "weak";
|
32
33
|
assert(strength.isWeak());
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
assert(strength.isValid("weak"));
|
35
|
+
// assertEqual(false, strength.isStrong());
|
36
|
+
// assertEqual(false, strength.isGood());
|
37
|
+
// assertEqual(false, strength.isInvalid());
|
36
38
|
}},
|
37
39
|
|
38
40
|
// Strong strength
|
@@ -43,6 +45,7 @@ new Test.Unit.Runner({
|
|
43
45
|
assert(strength.isValid("good"));
|
44
46
|
assertEqual(false, strength.isWeak());
|
45
47
|
assertEqual(false, strength.isGood());
|
48
|
+
assertEqual(false, strength.isInvalid());
|
46
49
|
}},
|
47
50
|
|
48
51
|
// Short password
|
@@ -240,5 +243,16 @@ new Test.Unit.Runner({
|
|
240
243
|
// Four char repetition
|
241
244
|
testFourCharRepetition: function() { with(this) {
|
242
245
|
assertEqual(4, strength.repetitions("abcdabcdabcd", 4));
|
246
|
+
}},
|
247
|
+
|
248
|
+
// Exclude option as regular expression
|
249
|
+
testExcludeOptionAsRegularExpression: function() { with(this) {
|
250
|
+
strength.password = "password with whitespaces";
|
251
|
+
strength.exclude = /\s/;
|
252
|
+
strength.test();
|
253
|
+
|
254
|
+
assertEqual("invalid", strength.status);
|
255
|
+
assert(strength.isInvalid());
|
256
|
+
assertEqual(false, strength.isValid());
|
243
257
|
}}
|
244
258
|
});
|
@@ -223,4 +223,18 @@ class TestPasswordStrength < Test::Unit::TestCase
|
|
223
223
|
# expected: §£€à, £€à§, €à§£, ৣ€
|
224
224
|
assert_equal 4, @strength.repetitions("§£€à§£€à§£€à", 4)
|
225
225
|
end
|
226
|
+
|
227
|
+
def test_exclude_option_as_regular_expression
|
228
|
+
@strength = PasswordStrength.test("johndoe", "^Str0ng P4ssw0rd$", :exclude => /\s/)
|
229
|
+
assert_equal :invalid, @strength.status
|
230
|
+
assert @strength.invalid?
|
231
|
+
assert_equal false, @strength.valid?
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_exclude_option_as_array
|
235
|
+
@strength = PasswordStrength.test("johndoe", "asdfasdfasdf", :exclude => ["asdf", "123"])
|
236
|
+
assert_equal :invalid, @strength.status
|
237
|
+
assert @strength.invalid?
|
238
|
+
assert_equal false, @strength.valid?
|
239
|
+
end
|
226
240
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 6
|
9
|
+
version: 0.1.6
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Nando Vieira
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-03-
|
17
|
+
date: 2010-03-31 00:00:00 -03:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|