strong_password 0.0.3 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,36 +3,36 @@ require 'spec_helper'
3
3
  module StrongPassword
4
4
  describe DictionaryAdjuster do
5
5
  describe '#is_strong?' do
6
- let(:subject) { DictionaryAdjuster.new('password') }
6
+ let(:subject) { DictionaryAdjuster.new }
7
7
 
8
8
  it 'returns true if the calculated entropy is >= the minimum' do
9
- subject.stub(adjusted_entropy: 18)
10
- expect(subject.is_strong?).to be_true
9
+ allow(subject).to receive_messages(adjusted_entropy: 18)
10
+ expect(subject.is_strong?('password')).to be_truthy
11
11
  end
12
12
 
13
13
  it 'returns false if the calculated entropy is < the minimum' do
14
- subject.stub(adjusted_entropy: 17)
15
- expect(subject.is_strong?).to be_false
14
+ allow(subject).to receive_messages(adjusted_entropy: 17)
15
+ expect(subject.is_strong?('password')).to be_falsey
16
16
  end
17
17
  end
18
18
 
19
19
  describe '#is_weak?' do
20
- let(:subject) { DictionaryAdjuster.new('password') }
20
+ let(:subject) { DictionaryAdjuster.new }
21
21
 
22
22
  it 'returns the opposite of is_strong?' do
23
- subject.stub(is_strong?: true)
24
- expect(subject.is_weak?).to be_false
23
+ allow(subject).to receive_messages(is_strong?: true)
24
+ expect(subject.is_weak?('password')).to be_falsey
25
25
  end
26
26
  end
27
27
 
28
28
  describe '#adjusted_entropy' do
29
- before(:each) { NistBonusBits.stub(bonus_bits: 0)}
29
+ before(:each) { allow(NistBonusBits).to receive_messages(bonus_bits: 0) }
30
30
 
31
31
  it 'checks against all variants of a given password' do
32
32
  password = 'password'
33
- adjuster = DictionaryAdjuster.new(password)
34
- PasswordVariants.should_receive(:all_variants).with(password).and_return([])
35
- adjuster.adjusted_entropy
33
+ adjuster = DictionaryAdjuster.new
34
+ expect(PasswordVariants).to receive(:all_variants).with(password).and_return([])
35
+ adjuster.adjusted_entropy(password)
36
36
  end
37
37
 
38
38
  {
@@ -40,29 +40,29 @@ module StrongPassword
40
40
  'h#e0zbPas' => 19.5, # Random string should not get adjusted by dictionary adjuster
41
41
  'password' => 4, # Adjusts common dictionary words
42
42
  'E_!3password' => 11.5, # Adjusts common dictionary words regardless of placement
43
- 'h#e0zbPas 32e2i81 password' => 31.3125, # Even if there are multiple words
43
+ 'h#e0zbPas 32e2i81 password' => 31.0625, # Even if there are multiple words
44
44
  '123456' => 4, # Even if they are also qwerty strings
45
- 'password123456' => 16, # But only drops the first matched word
45
+ 'password123456' => 14, # But only drops the first matched word
46
46
  'asdf)asdf' => 14, # Doesn't break with parens
47
47
  'asdf[]asdf' => 16 # Doesn't break with []s
48
48
  }.each do |password, bits|
49
49
  it "returns #{bits} for '#{password}'" do
50
- expect(DictionaryAdjuster.new(password).adjusted_entropy).to eq(bits)
50
+ expect(DictionaryAdjuster.new.adjusted_entropy(password)).to eq(bits)
51
51
  end
52
52
  end
53
53
 
54
54
  it 'allows extra words to be provided as an array' do
55
- password = 'mcmanus'
55
+ password = 'administratorWEQ@123'
56
56
  base_entropy = EntropyCalculator.calculate(password)
57
- expect(DictionaryAdjuster.new(password).adjusted_entropy(extra_dictionary_words: ['mcmanus'])).not_to eq(base_entropy)
57
+ expect(DictionaryAdjuster.new(extra_dictionary_words: ['administrator']).adjusted_entropy(password)).not_to eq(base_entropy)
58
58
  end
59
59
 
60
60
  it 'allows minimum word length to be adjusted' do
61
61
  password = '6969'
62
- base_entropy = DictionaryAdjuster.new(password).adjusted_entropy
62
+ base_entropy = DictionaryAdjuster.new.adjusted_entropy(password)
63
63
  # If we increase the min_word_length above the length of the password we should get a higher entropy
64
- expect(DictionaryAdjuster.new(password).adjusted_entropy(min_word_length: 6)).not_to be < base_entropy
64
+ expect(DictionaryAdjuster.new(min_word_length: 6).adjusted_entropy(password)).not_to be < base_entropy
65
65
  end
66
66
  end
67
67
  end
68
- end
68
+ end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  module StrongPassword
4
4
  describe EntropyCalculator do
5
5
  describe '.bits' do
6
- before(:each) { NistBonusBits.stub(bonus_bits: 0) }
6
+ before(:each) { allow(NistBonusBits).to receive_messages(bonus_bits: 0) }
7
7
  {
8
8
  '' => 0,
9
9
  '*' => 4,
@@ -36,9 +36,9 @@ module StrongPassword
36
36
  end
37
37
  end
38
38
  end
39
-
39
+
40
40
  describe '.bits_with_repeats_weakened' do
41
- before(:each) { NistBonusBits.stub(bonus_bits: 0) }
41
+ before(:each) { allow(NistBonusBits).to receive(:bonus_bits).and_return(0) }
42
42
  {
43
43
  '' => 0,
44
44
  '*' => 4,
@@ -52,7 +52,7 @@ module StrongPassword
52
52
  expect(subject.bits_with_repeats_weakened(password)).to eq(bits)
53
53
  end
54
54
  end
55
-
55
+
56
56
  it 'returns the same value for repeated calls on a password' do
57
57
  password = 'password'
58
58
  initial_value = subject.bits_with_repeats_weakened(password)
@@ -5,19 +5,19 @@ module StrongPassword
5
5
  describe '.bonus_bits' do
6
6
  it 'calculates the bonus bits the first time for a given password' do
7
7
  NistBonusBits.reset_bonus_cache!
8
- NistBonusBits.should_receive(:calculate_bonus_bits_for).and_return(1)
8
+ expect(NistBonusBits).to receive(:calculate_bonus_bits_for).and_return(1)
9
9
  expect(NistBonusBits.bonus_bits('password')).to eq(1)
10
10
  end
11
-
11
+
12
12
  it 'caches the bonus bits for a password for later use' do
13
13
  NistBonusBits.reset_bonus_cache!
14
- NistBonusBits.stub(calculate_bonus_bits_for: 1)
14
+ allow(NistBonusBits).to receive_messages(calculate_bonus_bits_for: 1)
15
15
  NistBonusBits.bonus_bits('password')
16
- NistBonusBits.should_not_receive(:calculate_bonus_bits_for)
16
+ expect(NistBonusBits).not_to receive(:calculate_bonus_bits_for)
17
17
  expect(NistBonusBits.bonus_bits('password')).to eq(1)
18
18
  end
19
19
  end
20
-
20
+
21
21
  describe '.calculate_bonus_bits_for' do
22
22
  {
23
23
  'Ab$9' => 4,
@@ -6,59 +6,59 @@ module StrongPassword
6
6
  it 'includes the lowercase password' do
7
7
  expect(subject.all_variants("PASSWORD")).to include('password')
8
8
  end
9
-
9
+
10
10
  it 'includes keyboard shift variants' do
11
- subject.stub(keyboard_shift_variants: ['foo', 'bar'])
11
+ allow(subject).to receive_messages(keyboard_shift_variants: ['foo', 'bar'])
12
12
  expect(subject.all_variants("password")).to include('foo', 'bar')
13
13
  end
14
-
14
+
15
15
  it 'includes leet speak variants' do
16
- subject.stub(leet_speak_variants: ['foo', 'bar'])
16
+ allow(subject).to receive_messages(leet_speak_variants: ['foo', 'bar'])
17
17
  expect(subject.all_variants("password")).to include('foo', 'bar')
18
18
  end
19
-
19
+
20
20
  it 'does not mutate the password' do
21
21
  password = 'PASSWORD'
22
22
  subject.all_variants(password)
23
23
  expect(password).to eq('PASSWORD')
24
24
  end
25
25
  end
26
-
26
+
27
27
  describe '.keyboard_shift_variants' do
28
28
  it 'returns no variants if password includes only bottom row characters' do
29
29
  expect(subject.keyboard_shift_variants('zxcvbnm,./')).to eq([])
30
30
  end
31
-
31
+
32
32
  it 'maps down-right passwords' do
33
33
  expect(subject.keyboard_shift_variants('qwerty')).to include('asdfgh')
34
34
  end
35
-
35
+
36
36
  it 'includes reversed down-right password' do
37
37
  expect(subject.keyboard_shift_variants('qwerty')).to include('hgfdsa')
38
38
  end
39
-
39
+
40
40
  it 'maps down-left passwords' do
41
41
  expect(subject.keyboard_shift_variants('sdfghj')).to include('zxcvbn')
42
42
  end
43
-
43
+
44
44
  it 'maps reversed down-left passwords' do
45
45
  expect(subject.keyboard_shift_variants('sdfghj')).to include('nbvcxz')
46
46
  end
47
47
  end
48
-
48
+
49
49
  describe '.leet_speak_variants' do
50
50
  it 'returns no variants if the password includes no leet speak' do
51
51
  expect(subject.leet_speak_variants('password')).to eq([])
52
52
  end
53
-
53
+
54
54
  it 'returns standard leet speak variants' do
55
55
  expect(subject.leet_speak_variants('p4ssw0rd')).to include('password')
56
56
  end
57
-
57
+
58
58
  it 'returns reversed standard leet speak variants' do
59
59
  expect(subject.leet_speak_variants('p4ssw0rd')).to include('drowssap')
60
60
  end
61
-
61
+
62
62
  it 'returns both i and l variants when given a 1' do
63
63
  expect(subject.leet_speak_variants('h1b0b')).to include('hibob', 'hlbob')
64
64
  end
@@ -2,31 +2,29 @@ require 'spec_helper'
2
2
 
3
3
  module StrongPassword
4
4
  describe QwertyAdjuster do
5
- describe '#is_strong?' do
6
- let(:subject) { QwertyAdjuster.new('password') }
5
+ subject(:qwerty_adjuster) { QwertyAdjuster.new(entropy_threshhold: 0) }
7
6
 
7
+ describe '#is_strong?' do
8
8
  it 'returns true if the calculated entropy is >= the minimum' do
9
- subject.stub(adjusted_entropy: 18)
10
- expect(subject.is_strong?).to be_true
9
+ allow(subject).to receive_messages(adjusted_entropy: 18)
10
+ expect(subject.is_strong?('password')).to be_truthy
11
11
  end
12
-
12
+
13
13
  it 'returns false if the calculated entropy is < the minimum' do
14
- subject.stub(adjusted_entropy: 17)
15
- expect(subject.is_strong?).to be_false
14
+ allow(subject).to receive_messages(adjusted_entropy: 17)
15
+ expect(subject.is_strong?('password')).to be_falsey
16
16
  end
17
17
  end
18
-
19
- describe '#is_weak?' do
20
- let(:subject) { QwertyAdjuster.new('password') }
21
18
 
19
+ describe '#is_weak?' do
22
20
  it 'returns the opposite of is_strong?' do
23
- subject.stub(is_strong?: true)
24
- expect(subject.is_weak?).to be_false
21
+ allow(subject).to receive_messages(is_strong?: true)
22
+ expect(subject.is_weak?('password')).to be_falsey
25
23
  end
26
24
  end
27
-
25
+
28
26
  describe '#adjusted_entropy' do
29
- before(:each) { NistBonusBits.stub(bonus_bits: 0)}
27
+ before(:each) { allow(NistBonusBits).to receive_messages(bonus_bits: 0) }
30
28
  {
31
29
  'qwertyuio' => 5.5,
32
30
  '1234567' => 6,
@@ -37,9 +35,21 @@ module StrongPassword
37
35
  'password' => 17.5 # Ensure that we don't qwerty-adjust 'password'
38
36
  }.each do |password, bits|
39
37
  it "returns #{bits} for '#{password}'" do
40
- expect(QwertyAdjuster.new(password).adjusted_entropy).to eq(bits)
38
+ expect(subject.adjusted_entropy(password)).to eq(bits)
39
+ end
40
+ end
41
+
42
+ describe 'with a default entropy threshhold' do
43
+ subject(:qwerty_adjuster) { QwertyAdjuster.new }
44
+
45
+ it 'returns the higher entropy before running qwerty adjustments' do
46
+ # Default threshhold is equal to the default min_entropy for a strong password (18).
47
+ # When not set we should get the base password's entropy from this (16) instead of the
48
+ # lower qwerty-adjusted entropy (6) indicating we avoided doing additional work in the
49
+ # qwerty adjustment code since we already know the password is weak.
50
+ expect(subject.adjusted_entropy('1234567')).to eq(16)
41
51
  end
42
52
  end
43
53
  end
44
54
  end
45
- end
55
+ end
@@ -11,25 +11,26 @@ module StrongPassword
11
11
  'aB$1' => false
12
12
  }.each do |password, strength|
13
13
  it "is_strong? returns #{strength} for '#{password}' with 12 bits of entropy" do
14
- expect(StrengthChecker.new(password).is_strong?(min_entropy: 12)).to be(strength)
14
+ expect(StrengthChecker.new(min_entropy: 12).is_strong?(password)).to be(strength)
15
15
  end
16
16
  end
17
17
  end
18
-
18
+
19
19
  context 'with lowered entropy requirement and dictionary checking' do
20
20
  {
21
21
  'blahblah' => true,
22
22
  'password' => false,
23
23
  'wwwwwwww' => false,
24
- 'adamruge' => true,
24
+ 'adamruge' => false,
25
+ 'madaegur' => true,
25
26
  'aB$1' => false
26
27
  }.each do |password, strength|
27
28
  it "is_strong? returns #{strength} for '#{password}' with 12 bits of entropy" do
28
- expect(StrengthChecker.new(password).is_strong?(min_entropy: 12, use_dictionary: true)).to be(strength)
29
+ expect(StrengthChecker.new(min_entropy: 12, use_dictionary: true).is_strong?(password)).to eq(strength)
29
30
  end
30
31
  end
31
32
  end
32
-
33
+
33
34
  context 'with standard entropy requirement and dictionary checking' do
34
35
  {
35
36
  'blahblah' => false,
@@ -40,11 +41,11 @@ module StrongPassword
40
41
  'correct horse battery staple' => true
41
42
  }.each do |password, strength|
42
43
  it "is_strong? returns #{strength} for '#{password}' with standard bits of entropy" do
43
- expect(StrengthChecker.new(password).is_strong?(use_dictionary: true)).to be(strength)
44
+ expect(StrengthChecker.new(use_dictionary: true).is_strong?(password)).to eq(strength)
44
45
  end
45
46
  end
46
47
  end
47
-
48
+
48
49
  context 'with crazy entropy requirement and dictionary checking' do
49
50
  {
50
51
  'blahblah' => false,
@@ -56,9 +57,32 @@ module StrongPassword
56
57
  'c0rr#ct h0rs3 Batt$ry st@pl3 is Gr34t' => true
57
58
  }.each do |password, strength|
58
59
  it "is_strong? returns #{strength} for '#{password}' with standard bits of entropy" do
59
- expect(StrengthChecker.new(password).is_strong?(min_entropy: 40, use_dictionary: true)).to be(strength)
60
+ expect(StrengthChecker.new(min_entropy: 40, use_dictionary: true).is_strong?(password)).to eq(strength)
60
61
  end
61
62
  end
62
63
  end
64
+
65
+ context 'with long password' do
66
+ let(:strength_checker) { StrengthChecker.new }
67
+ let(:password) { ("ba"*500_000) }
68
+ it 'should be truncated' do
69
+ expect(strength_checker.calculate_entropy(password)).
70
+ to eq(strength_checker.calculate_entropy(password.slice(0, StrengthChecker::PASSWORD_LIMIT)))
71
+ end
72
+ end
73
+
74
+ context 'with long extra words' do
75
+ let(:strength_checker) { StrengthChecker.new(use_dictionary: true, extra_dictionary_words: ["a"*1_000_000, "b"*10_000_000, "c"*10]) }
76
+ let(:exta_limit) { StrengthChecker::EXTRA_WORDS_LIMIT }
77
+ it 'should be truncated' do
78
+ expect(DictionaryAdjuster).to receive(:new).with({
79
+ min_entropy: 18,
80
+ min_word_length: 4,
81
+ extra_dictionary_words:
82
+ ["a"*StrengthChecker::EXTRA_WORDS_LIMIT, "b"*StrengthChecker::EXTRA_WORDS_LIMIT, "c"*10]
83
+ }).and_call_original
84
+ strength_checker.calculate_entropy("$tr0NgP4s$w0rd91d£")
85
+ end
86
+ end
63
87
  end
64
- end
88
+ end
@@ -18,7 +18,7 @@ class TestStrengthStrongEntropy < User
18
18
  end
19
19
 
20
20
  class TestStrengthExtraWords < User
21
- validates :password, password_strength: {extra_dictionary_words: ['mcmanus'], use_dictionary: true}
21
+ validates :password, password_strength: {extra_dictionary_words: ['administrator'], use_dictionary: true}
22
22
  end
23
23
 
24
24
  class TestBaseStrengthAlternative < User
@@ -47,7 +47,7 @@ module ActiveModel
47
47
  it "adds errors when password is '#{password}'" do
48
48
  base_strength.password = password
49
49
  base_strength.valid?
50
- expect(base_strength.errors[:password]).to eq(["Password is too weak"])
50
+ expect(base_strength.errors[:password]).to eq(["is too weak"])
51
51
  end
52
52
  end
53
53
  end
@@ -79,7 +79,7 @@ module ActiveModel
79
79
  it "adds errors when password is '#{password}'" do
80
80
  alternative_usage.password = password
81
81
  alternative_usage.valid?
82
- expect(alternative_usage.errors[:password]).to eq(["Password is too weak"])
82
+ expect(alternative_usage.errors[:password]).to eq(["is too weak"])
83
83
  end
84
84
  end
85
85
  end
@@ -129,7 +129,7 @@ module ActiveModel
129
129
  it "'#{password}' should be invalid with increased entropy requirement" do
130
130
  strong_entropy.password = password
131
131
  strong_entropy.valid?
132
- expect(strong_entropy.errors[:password]).to eq(["Password is too weak"])
132
+ expect(strong_entropy.errors[:password]).to eq(["is too weak"])
133
133
  end
134
134
  end
135
135
  end
@@ -138,14 +138,18 @@ module ActiveModel
138
138
 
139
139
  describe 'extra words' do
140
140
  it 'allows extra words to be specified as an option to the validation' do
141
- password = 'mcmanus'
141
+ password = 'administratorWEQ@123'
142
+ # Validate that without 'administrator' added to extra_dictionary_words
143
+ # this password is considered strong
142
144
  weak_entropy.password = password
143
- expect(weak_entropy.valid?).to be_true
145
+ expect(weak_entropy.valid?).to be_truthy
146
+ # Now check that with 'administrator' added to extra_dictionary_words
147
+ # in our model, the same password is considered weak.
144
148
  extra_words.password = password
145
- expect(extra_words.valid?).to be_false
149
+ expect(extra_words.valid?).to be_falsey
146
150
  end
147
151
  end
148
152
  end
149
153
  end
150
154
  end
151
- end
155
+ end
@@ -20,5 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency 'bundler', '~> 1.3'
22
22
  spec.add_development_dependency 'rake'
23
- spec.add_development_dependency 'rspec', '~> 2.12'
23
+ spec.add_development_dependency 'rspec', '~> 3.8'
24
+ spec.add_development_dependency 'pry'
24
25
  end
metadata CHANGED
@@ -1,57 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strong_password
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian McManus
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-27 00:00:00.000000000 Z
11
+ date: 2020-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.3'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2.12'
47
+ version: '3.8'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2.12'
54
+ version: '3.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description: Entropy-based password strength checking for Ruby and ActiveModel
56
70
  email:
57
71
  - bdmac97@gmail.com
@@ -59,7 +73,8 @@ executables: []
59
73
  extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
62
- - .gitignore
76
+ - ".gitignore"
77
+ - ".travis.yml"
63
78
  - CHANGELOG
64
79
  - Gemfile
65
80
  - LICENSE.txt
@@ -95,17 +110,16 @@ require_paths:
95
110
  - lib
96
111
  required_ruby_version: !ruby/object:Gem::Requirement
97
112
  requirements:
98
- - - '>='
113
+ - - ">="
99
114
  - !ruby/object:Gem::Version
100
115
  version: '0'
101
116
  required_rubygems_version: !ruby/object:Gem::Requirement
102
117
  requirements:
103
- - - '>='
118
+ - - ">="
104
119
  - !ruby/object:Gem::Version
105
120
  version: '0'
106
121
  requirements: []
107
- rubyforge_project:
108
- rubygems_version: 2.0.3
122
+ rubygems_version: 3.0.3
109
123
  signing_key:
110
124
  specification_version: 4
111
125
  summary: StrongPassword adds a class to check password strength and a validator for