nobspw 0.4.0 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2822386df873b457c238d992dc10557886e06852ff13d3d82ff83aa678d399d5
4
- data.tar.gz: 514207c17b7aa170f7e037953ff48dde07c884a86fbba822277e317506d9738f
3
+ metadata.gz: d81c15016a1b06dc3d38fc995120b526574a65cd5fd13ed98bc05e614f189b5b
4
+ data.tar.gz: a7303590e8fa254ad9718fda4444a2c3d5faabb77cc528d5a5284e52792742be
5
5
  SHA512:
6
- metadata.gz: 3c22655a227cddafed368bf1fe64ae30825106ebf50f3c5833868a4cfec9ec077c784d2c62bc78860d6d30be0a169e0dab209920e7424e87e4ee6ad854bcdff6
7
- data.tar.gz: ee59f029dc465da9adf867c939c96d62a03a888f54256a6c22256efc666a1bc5b54acdc20a2f9b9746af6a777c305d8eac901a6a46ea087e89468b2399af1cf5
6
+ metadata.gz: 2e8b250a5a326593bab9f576cdc7ec15da4e03573c273bacc62b74f46c4977df5e323b079aa43f613d17f458f1417b1f569e2f7dd19f190be16af5b0b68e7a13
7
+ data.tar.gz: 6ab6462e6c32b58a1f79cd01ffa0a540fab446f22db43b43c52366821a3a185f7c5a1c5b78cd938f4a5be94c2f93ba1cc094f9a456c8d957c68cfa079c7a2cba
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
  /coverage/
11
11
  /spec/examples.txt
12
12
  *.gem
13
+ .byebug_history
@@ -1,7 +1,7 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3.3
5
- - 2.4.0
6
4
  - 2.5.0
7
- before_install: gem install bundler -v 1.14.2
5
+ - 2.6.2
6
+ - 2.7.1
7
+ before_install: gem install bundler
data/README.md CHANGED
@@ -60,8 +60,9 @@ Optionally, you can configure some options:
60
60
  config.min_unique_characters = 5
61
61
  config.dictionary_path = 'path/to/dictionary.txt'
62
62
  config.grep_path = '/usr/bin/grep'
63
+ config.use_ruby_grep = false # Defaults to false; slower when true. Uses Ruby's internal Grep method instead of shelling out.
63
64
  config.domain_name = 'mywebsitedomain.com' # it is recommended you configure this
64
- config.blacklist = ['this_password_is_not_allowed']
65
+ config.blacklist = ['this_password_is_not_allowed', /password/]
65
66
  end
66
67
  ```
67
68
 
@@ -70,7 +71,7 @@ Optionally, you can configure some options:
70
71
  I included `PasswordValidator` for Rails. Validating passwords in your model couldn't be easier:
71
72
 
72
73
  ```ruby
73
- validates :password, presence: true, password: true, if: -> { new_record? || changes[:password] }
74
+ validates :password, presence: true, password: true, if: -> { new_record? || changes[:password] || changes[:password_digest] }
74
75
  ```
75
76
 
76
77
  PasswordValidator will try to guess the correct field name for each `PasswordChecker` argument as follow:
@@ -84,7 +85,7 @@ If you have field names different than above, you can tell `PasswordValidator` w
84
85
  ```ruby
85
86
  validates :password, password: { :name => :customer_name,
86
87
  :email => :electronic_address },
87
- if: -> { new_record? || changes[:password] }
88
+ if: -> { new_record? || changes[:password] || changes[:password_digest] }
88
89
  ```
89
90
 
90
91
  ## Validations
@@ -1,6 +1,7 @@
1
1
  class ActiveModel::Validations::PasswordValidator < ActiveModel::EachValidator
2
2
  DEFAULT_ERROR_MESSAGES = {
3
3
  name_included_in_password: 'is too similar to your name',
4
+ username_included_in_password: 'is too similar to your username',
4
5
  email_included_in_password: 'is too similar to your email',
5
6
  domain_included_in_password: 'is too similar to this domain name',
6
7
  password_too_short: 'is too short',
@@ -5,6 +5,7 @@ module NOBSPW
5
5
  attr_accessor :min_unique_characters
6
6
  attr_accessor :dictionary_path
7
7
  attr_accessor :grep_path
8
+ attr_accessor :use_ruby_grep
8
9
  attr_accessor :domain_name
9
10
  attr_accessor :blacklist
10
11
  attr_accessor :validation_methods
@@ -16,6 +17,7 @@ module NOBSPW
16
17
  @min_unique_characters = 5
17
18
  @dictionary_path = File.join(File.dirname(__FILE__), "..", "db", "dictionary.txt")
18
19
  @grep_path = `which grep`.strip
20
+ @use_ruby_grep = @grep_path.empty?
19
21
  @domain_name = nil
20
22
  @blacklist = nil
21
23
  @validation_methods = NOBSPW::ValidationMethods::DEFAULT_VALIDATION_METHODS
@@ -1,3 +1,5 @@
1
+ require 'shellwords'
2
+
1
3
  module NOBSPW
2
4
  class PasswordChecker
3
5
  include NOBSPW::ValidationMethods
@@ -26,7 +28,6 @@ module NOBSPW
26
28
 
27
29
  def check_password
28
30
  @weak_password_reasons = []
29
-
30
31
  NOBSPW.configuration.validation_methods.each do |method|
31
32
  if send("#{method}")
32
33
  @weak_password_reasons << method.to_s.sub(/\?$/, '').to_sym
@@ -36,9 +37,5 @@ module NOBSPW
36
37
 
37
38
  @strong = @weak_password_reasons.empty?
38
39
  end
39
-
40
- def grep_command(path)
41
- "#{NOBSPW.configuration.grep_path} '^#{@password}$' #{path}"
42
- end
43
40
  end
44
41
  end
@@ -1,7 +1,11 @@
1
+ require 'shellwords'
2
+ require 'open3'
3
+
1
4
  module NOBSPW
2
5
  module ValidationMethods
3
6
  DEFAULT_VALIDATION_METHODS = %i(password_empty?
4
7
  name_included_in_password?
8
+ username_included_in_password?
5
9
  email_included_in_password?
6
10
  domain_included_in_password?
7
11
  password_too_short?
@@ -11,6 +15,8 @@ module NOBSPW
11
15
  password_too_common?)
12
16
 
13
17
  INTERRUPT_VALIDATION_FOR = %i(password_empty?)
18
+ STDIN_GREP_COMMAND = ['/usr/bin/grep', '-m 1', '-f', '/dev/stdin',
19
+ NOBSPW.configuration.dictionary_path]
14
20
 
15
21
  private
16
22
 
@@ -24,6 +30,12 @@ module NOBSPW
24
30
  words_included_in_password?(words)
25
31
  end
26
32
 
33
+ def username_included_in_password?
34
+ return nil unless @username
35
+ words = remove_word_separators(@username).split(' ')
36
+ words_included_in_password?(words)
37
+ end
38
+
27
39
  def email_included_in_password?
28
40
  return nil unless @email
29
41
  words = remove_word_separators(email_without_extension(@email)).split(' ')
@@ -40,7 +52,16 @@ module NOBSPW
40
52
 
41
53
  def password_not_allowed?
42
54
  return nil unless NOBSPW.configuration.blacklist
43
- NOBSPW.configuration.blacklist.include?(@password)
55
+
56
+ NOBSPW.configuration.blacklist.each do |expression|
57
+ if expression.is_a?(Regexp)
58
+ return true if @password.match?(expression)
59
+ else
60
+ return true if expression.to_s == @password
61
+ end
62
+ end
63
+
64
+ false
44
65
  end
45
66
 
46
67
  def password_too_short?
@@ -65,22 +86,27 @@ module NOBSPW
65
86
  end
66
87
 
67
88
  def password_too_common?
68
- `#{grep_command(NOBSPW.configuration.dictionary_path)}`
69
-
70
- case $?.exitstatus
71
- when 0
72
- true
73
- when 1
74
- false
75
- when 127
76
- raise StandardError.new("Grep not found at: #{NOBSPW.configuration.grep_path}")
77
- else
78
- false
79
- end
89
+ NOBSPW.configuration.use_ruby_grep ? ruby_grep : shell_grep
80
90
  end
81
91
 
82
92
  # Helper methods
83
93
 
94
+ def shell_grep
95
+ raise StandardError.new("Grep not found at: #{NOBSPW.configuration.grep_path}") \
96
+ if !File.exist?(NOBSPW.configuration.grep_path)
97
+
98
+ output = Open3.popen3(STDIN_GREP_COMMAND.join(" "), out: '/dev/null') { |stdin, stdout, stderr, wait_thr|
99
+ stdin.puts "^#{escaped_password}$"
100
+ stdin.close
101
+ wait_thr.value
102
+ }
103
+ output.success?
104
+ end
105
+
106
+ def ruby_grep
107
+ File.open(NOBSPW.configuration.dictionary_path).grep(/^#{escaped_password}$/).any?
108
+ end
109
+
84
110
  def email_without_extension(email)
85
111
  name, domain, whatev = email&.split("@", 3)
86
112
  "#{name}@#{strip_extension_from_domain(domain)}"
@@ -94,5 +120,8 @@ module NOBSPW
94
120
  str&.gsub(/-|_|\.|\'|\"|\@/, ' ')
95
121
  end
96
122
 
123
+ def escaped_password(password = @password)
124
+ Shellwords.escape(password)
125
+ end
97
126
  end
98
127
  end
@@ -1,3 +1,3 @@
1
1
  module NOBSPW
2
- VERSION = '0.4.0'
2
+ VERSION = '0.6.2'
3
3
  end
@@ -0,0 +1,73 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'benchmark'
4
+ require 'shellwords'
5
+ require 'open3'
6
+ require 'subprocess'
7
+
8
+ ITERATIONS = 100
9
+ DICTIONARY_PATH = File.join(File.dirname(__FILE__), '..', 'lib/db/dictionary.txt')
10
+ STDIN_GREP_COMMAND = ['/usr/bin/grep', '-m 1', '-f', '/dev/stdin', DICTIONARY_PATH]
11
+
12
+ password = 'swordfish'
13
+
14
+ def shell_grep(password)
15
+ password = Shellwords.escape(password)
16
+ `/usr/bin/grep -m 1 '^#{password}$' #{DICTIONARY_PATH}`
17
+ $?.exitstatus == 0
18
+ end
19
+
20
+ def shell_grep_open3(password)
21
+ password = Shellwords.escape(password)
22
+
23
+ output = Open3.popen3(STDIN_GREP_COMMAND.join(" "), out: '/dev/null') { |stdin, stdout, stderr, wait_thr|
24
+ stdin.puts "^#{password}$"
25
+ stdin.close
26
+ wait_thr.value
27
+ }
28
+ output.success?
29
+ end
30
+
31
+ def shell_grep_subprocess(password)
32
+ password = Shellwords.escape(password)
33
+
34
+ Subprocess.check_call(STDIN_GREP_COMMAND, stdin: Subprocess::PIPE, stdout: '/dev/null') do |p|
35
+ p.communicate("^#{password}$")
36
+ end
37
+ true
38
+ rescue Subprocess::NonZeroExit
39
+ false
40
+ end
41
+
42
+ def ruby_grep(password)
43
+ File.open(DICTIONARY_PATH).grep(/^#{password}$/)
44
+ end
45
+
46
+ def ruby_loop(password)
47
+ File.open(DICTIONARY_PATH).read_line do |l|
48
+ return true if l.match?(/^#{password}$/)
49
+ end
50
+ false
51
+ end
52
+
53
+ Benchmark.bm(17) do |benchmark|
54
+ benchmark.report("Shell") do
55
+ ITERATIONS.times { shell_grep(password) }
56
+ end
57
+
58
+ benchmark.report("Ruby Grep") do
59
+ ITERATIONS.times { ruby_grep(password) }
60
+ end
61
+
62
+ benchmark.report("Ruby Loop") do
63
+ ITERATIONS.times { ruby_grep(password) }
64
+ end
65
+
66
+ benchmark.report("Open3 stdin") do
67
+ ITERATIONS.times { shell_grep_open3(password) }
68
+ end
69
+
70
+ benchmark.report("Subprocess stdin") do
71
+ ITERATIONS.times { shell_grep_subprocess(password) }
72
+ end
73
+ end
@@ -21,14 +21,16 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_development_dependency "bundler", "~> 1.14"
25
- spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake", "~> 12.3.3"
26
26
  spec.add_development_dependency "rspec", "~> 3.0"
27
27
  spec.add_development_dependency "simplecov", "~> 0.13"
28
28
  spec.add_development_dependency "guard", "~> 2.14"
29
29
  spec.add_development_dependency "guard-rspec", "~> 4.7.3"
30
30
  spec.add_development_dependency "activemodel", "~> 5.0"
31
31
  spec.add_development_dependency "i18n", "~> 0.8.1"
32
+ spec.add_development_dependency "subprocess"
33
+ spec.add_development_dependency "byebug"
32
34
 
33
35
  if RUBY_PLATFORM =~ /darwin/
34
36
  spec.add_development_dependency 'ruby_gntp', "~> 0.3.4"
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nobspw
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Mercier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-27 00:00:00.000000000 Z
11
+ date: 2020-06-29 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
- version: '1.14'
19
+ version: '0'
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
- version: '1.14'
26
+ version: '0'
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
- version: '10.0'
33
+ version: 12.3.3
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
- version: '10.0'
40
+ version: 12.3.3
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +122,34 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: 0.8.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: subprocess
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: byebug
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
125
153
  - !ruby/object:Gem::Dependency
126
154
  name: ruby_gntp
127
155
  requirement: !ruby/object:Gem::Requirement
@@ -175,6 +203,7 @@ files:
175
203
  - lib/nobspw/password_checker.rb
176
204
  - lib/nobspw/validation_methods.rb
177
205
  - lib/nobspw/version.rb
206
+ - misc/grep_benchmark.rb
178
207
  - nobspw.gemspec
179
208
  homepage: https://github.com/cmer/nobspw
180
209
  licenses:
@@ -195,8 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
224
  - !ruby/object:Gem::Version
196
225
  version: '0'
197
226
  requirements: []
198
- rubyforge_project:
199
- rubygems_version: 2.7.3
227
+ rubygems_version: 3.1.2
200
228
  signing_key:
201
229
  specification_version: 4
202
230
  summary: No Bullshit Password strength checker