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