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 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