password_strength 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|