password_strength 0.1.5 → 0.1.6

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
@@ -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");
@@ -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 +:strong+
17
+ # The current test status. Can be +:weak+, +:good+, +:strong+ or +:invalid+.
18
18
  attr_reader :status
19
19
 
20
- def initialize(username, password)
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
- true
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
- @score += score_for(:password_size)
115
- @score += score_for(:numbers)
116
- @score += score_for(:symbols)
117
- @score += score_for(:uppercase_lowercase)
118
- @score += score_for(:numbers_chars)
119
- @score += score_for(:numbers_symbols)
120
- @score += score_for(:symbols_chars)
121
- @score += score_for(:only_chars)
122
- @score += score_for(:only_numbers)
123
- @score += score_for(:username)
124
- @score += score_for(:sequences)
125
- @score += score_for(:repetitions)
126
-
127
- @score = 0 if score < 0
128
- @score = 100 if score > 100
129
-
130
- @status = :weak if score < 35
131
- @status = :good if score >= 35 && score < 70
132
- @status = :strong if score >= 70
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
@@ -2,7 +2,7 @@ module PasswordStrength
2
2
  module Version # :nodoc: all
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
- PATCH = 5
5
+ PATCH = 6
6
6
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
7
  end
8
8
  end
@@ -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.score >= 70) {
44
- this.status = "strong";
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 true;
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;
@@ -10,8 +10,21 @@ module PasswordStrength
10
10
  # strength.weak?
11
11
  # #=> true
12
12
  #
13
- def self.test(username, password)
14
- strength = Base.new(username, password)
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
@@ -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
- assert(strength.isValid("weak"));
34
- assertEqual(false, strength.isStrong());
35
- assertEqual(false, strength.isGood());
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
- - 5
9
- version: 0.1.5
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-23 00:00:00 -03:00
17
+ date: 2010-03-31 00:00:00 -03:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency