strong_password 0.0.4 → 0.0.10
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.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/.travis.yml +17 -0
- data/CHANGELOG +16 -1
- data/Gemfile +7 -2
- data/README.md +38 -24
- data/Rakefile +5 -0
- data/lib/active_model/validations/password_strength_validator.rb +4 -4
- data/lib/strong_password.rb +2 -2
- data/lib/strong_password/dictionary_adjuster.rb +16 -12
- data/lib/strong_password/entropy_calculator.rb +8 -8
- data/lib/strong_password/locale/en.yml +1 -1
- data/lib/strong_password/nist_bonus_bits.rb +5 -5
- data/lib/strong_password/password_variants.rb +18 -55
- data/lib/strong_password/qwerty_adjuster.rb +58 -56
- data/lib/strong_password/railtie.rb +1 -1
- data/lib/strong_password/strength_checker.rb +41 -20
- data/lib/strong_password/version.rb +1 -1
- data/spec/spec_helper.rb +8 -2
- data/spec/strong_password/dictionary_adjuster_spec.rb +18 -18
- data/spec/strong_password/entropy_calculator_spec.rb +4 -4
- data/spec/strong_password/nist_bonus_bits_spec.rb +5 -5
- data/spec/strong_password/password_variants_spec.rb +14 -14
- data/spec/strong_password/qwerty_adjuster_spec.rb +26 -16
- data/spec/strong_password/strength_checker_spec.rb +31 -8
- data/spec/validation/strength_validator_spec.rb +12 -8
- data/strong_password.gemspec +3 -2
- metadata +29 -15
@@ -2,31 +2,29 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module StrongPassword
|
4
4
|
describe QwertyAdjuster do
|
5
|
-
|
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.
|
10
|
-
expect(subject.is_strong?).to
|
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.
|
15
|
-
expect(subject.is_strong?).to
|
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.
|
24
|
-
expect(subject.is_weak?).to
|
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.
|
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(
|
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,11 +11,11 @@ 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(
|
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,
|
@@ -26,11 +26,11 @@ module StrongPassword
|
|
26
26
|
'aB$1' => false
|
27
27
|
}.each do |password, strength|
|
28
28
|
it "is_strong? returns #{strength} for '#{password}' with 12 bits of entropy" do
|
29
|
-
expect(StrengthChecker.new(
|
29
|
+
expect(StrengthChecker.new(min_entropy: 12, use_dictionary: true).is_strong?(password)).to eq(strength)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
context 'with standard entropy requirement and dictionary checking' do
|
35
35
|
{
|
36
36
|
'blahblah' => false,
|
@@ -41,11 +41,11 @@ module StrongPassword
|
|
41
41
|
'correct horse battery staple' => true
|
42
42
|
}.each do |password, strength|
|
43
43
|
it "is_strong? returns #{strength} for '#{password}' with standard bits of entropy" do
|
44
|
-
expect(StrengthChecker.new(
|
44
|
+
expect(StrengthChecker.new(use_dictionary: true).is_strong?(password)).to eq(strength)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
context 'with crazy entropy requirement and dictionary checking' do
|
50
50
|
{
|
51
51
|
'blahblah' => false,
|
@@ -57,9 +57,32 @@ module StrongPassword
|
|
57
57
|
'c0rr#ct h0rs3 Batt$ry st@pl3 is Gr34t' => true
|
58
58
|
}.each do |password, strength|
|
59
59
|
it "is_strong? returns #{strength} for '#{password}' with standard bits of entropy" do
|
60
|
-
expect(StrengthChecker.new(
|
60
|
+
expect(StrengthChecker.new(min_entropy: 40, use_dictionary: true).is_strong?(password)).to eq(strength)
|
61
61
|
end
|
62
62
|
end
|
63
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
|
64
87
|
end
|
65
|
-
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: ['
|
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(["
|
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(["
|
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(["
|
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 = '
|
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
|
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
|
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
|
data/strong_password.gemspec
CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency 'bundler', '
|
21
|
+
spec.add_development_dependency 'bundler', '>= 1.3'
|
22
22
|
spec.add_development_dependency 'rake'
|
23
|
-
spec.add_development_dependency 'rspec', '~>
|
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.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian McManus
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-10 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: '
|
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: '
|
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
|
-
|
108
|
-
rubygems_version: 2.2.0
|
122
|
+
rubygems_version: 3.1.2
|
109
123
|
signing_key:
|
110
124
|
specification_version: 4
|
111
125
|
summary: StrongPassword adds a class to check password strength and a validator for
|