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 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
- if (typeof(options) == "function") {
4
- callback = options;
5
- options = {};
6
- } else if (!options) {
7
- options = {};
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
- this.status = "invalid";
16
+ this.status = "invalid";
17
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
- }
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
- if (!this.exclude) {
166
- return false;
167
- }
165
+ if (!this.exclude) {
166
+ return false;
167
+ }
168
168
 
169
- if (!this.exclude.test) {
170
- return false;
171
- }
169
+ if (!this.exclude.test) {
170
+ return false;
171
+ }
172
172
 
173
- return this.exclude.test(this.password.toString());
173
+ return this.exclude.test(this.password.toString());
174
174
  };
175
175
 
176
176
  this.sequences = function(text) {
@@ -1,6 +1,7 @@
1
1
  require "active_support"
2
2
  require "password_strength/base"
3
3
  require "password_strength/active_record"
4
+ require "password_strength/validators/windows2008"
4
5
 
5
6
  module PasswordStrength
6
7
  # Test the password strength by applying several rules.
@@ -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 = PasswordStrength.test(record.send(options[:with]), value, :exclude => options[:exclude])
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 = PasswordStrength.test(record.send(options[:with]), value, :exclude => options[:exclude])
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 = :good)
49
+ def valid?(level = GOOD)
46
50
  case level
47
- when :strong then
51
+ when STRONG then
48
52
  strong?
49
- when :good then
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 == :strong
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 == :weak
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 == :good
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 == :invalid
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 < 4
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
- @status = :invalid
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
- @status = :weak if score < 35
158
- @status = :good if score >= 35 && score < 70
159
- @status = :strong if score >= 70
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
@@ -1,7 +1,7 @@
1
1
  module PasswordStrength
2
2
  module Version # :nodoc: all
3
3
  MAJOR = 0
4
- MINOR = 2
4
+ MINOR = 3
5
5
  PATCH = 0
6
6
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
7
  end
@@ -164,8 +164,8 @@ class TestPasswordStrength < Test::Unit::TestCase
164
164
  end
165
165
 
166
166
  def test_password_length
167
- @strength.password = "12345"
168
- assert_equal 20, @strength.score_for(:password_size)
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.2.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-07-27 00:00:00 -03:00
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