strong_password 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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
 
@@ -40,4 +40,4 @@ module ActiveModel
40
40
  end
41
41
  end
42
42
  end
43
- end
43
+ end
@@ -997,4 +997,4 @@ module StrongPassword
997
997
  end
998
998
  end
999
999
  end
1000
- end
1000
+ end
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module StrongPassword
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -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'
@@ -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)
@@ -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,
@@ -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
@@ -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
@@ -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: