checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
---
2
- SHA1:
3
- metadata.gz: 21042c15094f1a9a0f960b16435fee882ac7e517
4
- data.tar.gz: f4e9cf9c311aa13a7cf591b866d1f43a91c91290
2
+ SHA256:
3
+ metadata.gz: 0506c07961d8cf54ee30542579493af60a4a508c176f7de7c0131d8082bcdf35
4
+ data.tar.gz: 132ea4c4bc8a96f44b687a342f418ef9c183efb696618cf6b6a7f14a3558c590
5
5
SHA512:
6
- metadata.gz: b5be352df9e03643ab21a4c10ba1bdc9561607776faa8974ea59563b43432cfc62a6824d86365811875672a57028c99887df4aee172618f5c0c407d2312ed974
7
- data.tar.gz: 67032a9ff38562e8893a9dbd581d55a79531a9b83165029bdbcf05fcf883ab4effd8a8021996c79167fc3d8fc1193d25bfda9b71b2e894c4a31a4a264d244806
6
+ metadata.gz: 9342ba93d158425cb8623662341f43fda16c33102590c846571767f09aa8def4a34f4d0d03eb150ea468b3f3c2de58a4cdf2f57c29367fbbc402a3e797b4f893
7
+ data.tar.gz: 5ba21b3b35527fc07fe5744dc9560e083fe0844a316c88ef22249aeacea0f77ec1b3082a6fb20b4eb6cc7f4cabb63560433023c1c438a73a28e60ec46e64b4aa
data/Gemfile CHANGED
@@ -12,4 +12,7 @@ else
12
12
end
13
13
14
14
gem "rails", rails
15
- gem "codeclimate-test-reporter", group: :test, require: nil
15
+ group :test do
16
+ gem "simplecov", "~> 0.16.1", require: false
17
+ gem "simplecov-console", "~> 0.4.2", require: false
18
+ end
data/README.md CHANGED
@@ -19,7 +19,7 @@ NOTE: StrongPassword requires the use of Ruby 2.0. Upgrade if you haven't alrea
19
19
20
20
Add this line to your application's Gemfile:
21
21
22
- gem 'strong_password', '~> 0.0.5'
22
+ gem 'strong_password', '~> 0.0.6'
23
23
24
24
And then execute:
25
25
data/lib/active_model/validations/password_strength_validator.rb CHANGED
@@ -40,4 +40,4 @@ module ActiveModel
40
40
end
41
41
end
42
42
end
43
- end
43
+ end
data/lib/strong_password/dictionary_adjuster.rb CHANGED
@@ -997,4 +997,4 @@ module StrongPassword
997
997
end
998
998
end
999
999
end
1000
- end
1000
+ end
data/lib/strong_password/entropy_calculator.rb CHANGED
@@ -8,7 +8,7 @@ module StrongPassword
8
8
bits(password)
9
9
end
10
10
end
11
-
11
+
12
12
# The basic NIST entropy calculation is based solely
13
13
# on the length of the password in question.
14
14
def self.bits(password)
@@ -24,7 +24,7 @@ module StrongPassword
24
24
end
25
25
bits + NistBonusBits.bonus_bits(password)
26
26
end
27
-
27
+
28
28
# A modified version of the basic entropy calculation
29
29
# which lowers the amount of entropy gained for each
30
30
# repeated character in the password
@@ -36,9 +36,9 @@ module StrongPassword
36
36
end
37
37
bits + NistBonusBits.bonus_bits(password)
38
38
end
39
-
39
+
40
40
private
41
-
41
+
42
42
def self.bit_value_at_position(position, base = 1)
43
43
if position > 19
44
44
return base
@@ -50,17 +50,17 @@ module StrongPassword
50
50
return 4
51
51
end
52
52
end
53
-
53
+
54
54
class EntropyResolver
55
55
BASE_VALUE = 1
56
56
REPEAT_WEAKENING_FACTOR = 0.75
57
-
57
+
58
58
attr_reader :char_multiplier
59
-
59
+
60
60
def initialize
61
61
@char_multiplier = {}
62
62
end
63
-
63
+
64
64
# Returns the current entropy value for a character and weakens the entropy
65
65
# for future calls for the same character.
66
66
def entropy_for(char)
data/lib/strong_password/nist_bonus_bits.rb CHANGED
@@ -1,26 +1,26 @@
1
1
module StrongPassword
2
2
module NistBonusBits
3
3
@@bonus_bits_for_password = {}
4
-
4
+
5
5
# NIST password strength rules allow up to 6 bonus bits for mixed case and non-alphabetic
6
6
def self.bonus_bits(password)
7
7
@@bonus_bits_for_password[password] ||= begin
8
8
calculate_bonus_bits_for(password)
9
9
end
10
10
end
11
-
11
+
12
12
# This smells bad as it's only used for testing...
13
13
def self.reset_bonus_cache!
14
14
@@bonus_bits_for_password = {}
15
15
end
16
-
16
+
17
17
def self.calculate_bonus_bits_for(password)
18
18
upper = !!(password =~ /[[:upper:]]/)
19
19
lower = !!(password =~ /[[:lower:]]/)
20
20
numeric = !!(password =~ /[[:digit:]]/)
21
21
other = !!(password =~ /[^a-zA-Z0-9 ]/)
22
22
space = !!(password =~ / /)
23
-
23
+
24
24
# I had this condensed to nested ternaries but that shit was ugly
25
25
bonus_bits = if upper && lower && other && numeric
26
26
6
@@ -42,4 +42,4 @@ module StrongPassword
42
42
bonus_bits
43
43
end
44
44
end
45
- end
45
+ end
data/lib/strong_password/qwerty_adjuster.rb CHANGED
@@ -16,21 +16,21 @@ module StrongPassword
16
16
"014725836914702583697894561230258/369*+-*/",
17
17
"abcdefghijklmnopqrstuvwxyz"
18
18
]
19
-
19
+
20
20
attr_reader :base_password
21
-
21
+
22
22
def initialize(password)
23
23
@base_password = password.downcase
24
24
end
25
-
25
+
26
26
def is_strong?(min_entropy: 18)
27
27
adjusted_entropy(entropy_threshhold: min_entropy) >= min_entropy
28
28
end
29
-
29
+
30
30
def is_weak?(min_entropy: 18)
31
31
!is_strong?(min_entropy: min_entropy)
32
32
end
33
-
33
+
34
34
# Returns the minimum entropy for the password's qwerty locality
35
35
# adjustments. If a threshhold is specified we will bail
36
36
# early to avoid unnecessary processing.
@@ -53,9 +53,9 @@ module StrongPassword
53
53
end
54
54
min_entropy
55
55
end
56
-
56
+
57
57
private
58
-
58
+
59
59
def mask_qwerty_strings(password, qwerty_string)
60
60
masked_password = password
61
61
z = 6
@@ -70,4 +70,4 @@ module StrongPassword
70
70
masked_password
71
71
end
72
72
end
73
- end
73
+ end
data/lib/strong_password/strength_checker.rb CHANGED
@@ -1,13 +1,15 @@
1
1
module StrongPassword
2
2
class StrengthChecker
3
3
BASE_ENTROPY = 18
4
-
4
+ PASSWORD_LIMIT = 1_000
5
+ EXTRA_WORDS_LIMIT = 1_000
6
+
5
7
attr_reader :base_password
6
8
7
9
def initialize(password)
8
- @base_password = password.dup
10
+ @base_password = password.dup[0...PASSWORD_LIMIT]
9
11
end
10
-
12
+
11
13
def is_weak?(min_entropy: BASE_ENTROPY, use_dictionary: false, min_word_length: 4, extra_dictionary_words: [])
12
14
!is_strong?(min_entropy: min_entropy,
13
15
use_dictionary: use_dictionary,
@@ -27,11 +29,12 @@ module StrongPassword
27
29
return !weak
28
30
end
29
31
end
30
-
32
+
31
33
def calculate_entropy(use_dictionary: false, min_word_length: 4, extra_dictionary_words: [])
34
+ extra_dictionary_words.collect! { |w| w[0...EXTRA_WORDS_LIMIT] }
32
35
entropies = [EntropyCalculator.calculate(base_password), EntropyCalculator.calculate(base_password.downcase), QwertyAdjuster.new(base_password).adjusted_entropy]
33
36
entropies << DictionaryAdjuster.new(base_password).adjusted_entropy(min_word_length: min_word_length, extra_dictionary_words: extra_dictionary_words) if use_dictionary
34
37
entropies.min
35
38
end
36
39
end
37
- end
40
+ end
data/lib/strong_password/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
module StrongPassword
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,8 @@
1
- require "codeclimate-test-reporter"
2
- CodeClimate::TestReporter.start
1
+ require 'simplecov'
2
+ require 'simplecov-console'
3
+ SimpleCov.formatter = SimpleCov::Formatter::Console
4
+ SimpleCov.start
5
+
3
6
require 'bundler/setup'
4
7
require 'pry'
5
8
require 'active_model'
data/spec/strong_password/entropy_calculator_spec.rb CHANGED
@@ -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)
data/spec/strong_password/nist_bonus_bits_spec.rb CHANGED
@@ -8,7 +8,7 @@ module StrongPassword
8
8
NistBonusBits.should_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
14
NistBonusBits.stub(calculate_bonus_bits_for: 1)
@@ -17,7 +17,7 @@ module StrongPassword
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,
data/spec/strong_password/password_variants_spec.rb CHANGED
@@ -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
11
subject.stub(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
16
subject.stub(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
data/spec/strong_password/strength_checker_spec.rb CHANGED
@@ -15,7 +15,7 @@ module StrongPassword
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,
@@ -30,7 +30,7 @@ module StrongPassword
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,
@@ -45,7 +45,7 @@ module StrongPassword
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,
@@ -61,5 +61,25 @@ module StrongPassword
61
61
end
62
62
end
63
63
end
64
+
65
+ context 'with long password' do
66
+ let(:strength_checker) { StrengthChecker.new("ba"*500_000) }
67
+ it 'should be truncated' do
68
+ expect(strength_checker.instance_variable_get(:@base_password).length).to eq StrengthChecker::PASSWORD_LIMIT
69
+ end
70
+ end
71
+
72
+ context 'with long extra words' do
73
+ let(:strength_checker) { StrengthChecker.new("$tr0NgP4s$w0rd91d£") }
74
+ let(:exta_limit) { StrengthChecker::EXTRA_WORDS_LIMIT }
75
+ it 'should be truncated' do
76
+ expect_any_instance_of(DictionaryAdjuster).to receive(:adjusted_entropy).with({
77
+ min_word_length: 4,
78
+ extra_dictionary_words:
79
+ ["a"*StrengthChecker::EXTRA_WORDS_LIMIT, "b"*StrengthChecker::EXTRA_WORDS_LIMIT, "c"*10]
80
+ }).and_call_original
81
+ strength_checker.calculate_entropy(use_dictionary: true, extra_dictionary_words: ["a"*1_000_000, "b"*10_000_000, "c"*10])
82
+ end
83
+ end
64
84
end
65
- end
85
+ end
data/strong_password.gemspec CHANGED
@@ -20,6 +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
24
spec.add_development_dependency 'pry'
25
25
end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
--- !ruby/object:Gem::Specification
2
2
name: strong_password
3
3
version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
platform: ruby
6
6
authors:
7
7
- Brian McManus
8
8
autorequire:
9
9
bindir: bin
10
10
cert_chain: []
11
- date: 2016-01-28 00:00:00.000000000 Z
11
+ date: 2018-10-09 00:00:00.000000000 Z
12
12
dependencies:
13
13
- !ruby/object:Gem::Dependency
14
14
name: bundler
@@ -44,14 +44,14 @@ dependencies:
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
55
- !ruby/object:Gem::Dependency
56
56
name: pry
57
57
requirement: !ruby/object:Gem::Requirement
@@ -120,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
120
version: '0'
121
121
requirements: []
122
122
rubyforge_project:
123
- rubygems_version: 2.4.5
123
+ rubygems_version: 2.7.6
124
124
signing_key:
125
125
specification_version: 4
126
126
summary: StrongPassword adds a class to check password strength and a validator for
@@ -134,4 +134,3 @@ test_files:
134
134
- spec/strong_password/qwerty_adjuster_spec.rb
135
135
- spec/strong_password/strength_checker_spec.rb
136
136
- spec/validation/strength_validator_spec.rb
137
- has_rdoc: