nobspw 0.2.0 → 0.3.0

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
  SHA1:
3
- metadata.gz: 391e780d690175f0dd19aec4f70506b81c587632
4
- data.tar.gz: 164a2ed02a9041eeb5f46c0db2e13f1f7965b832
3
+ metadata.gz: 48035c919105e867a0aa76612c6dcf13708da443
4
+ data.tar.gz: ab7cdfe7326b2c6bcf9e4afdf8518a17dd21bec2
5
5
  SHA512:
6
- metadata.gz: 138e85ae7d22ff15af74e4fcaa646b237d671a5746a9e3b957fc793e3ed99ed1e4e8f752856c34c75dc18b64b3e0f6023c72849c1a6382573f80aeea1f411646
7
- data.tar.gz: '09ab32aa868f3afe7505fb378b67375cc0413a87f4600cf37a5f641e88d029911a4d29114245c429b4346a0442fe4e743e2d31837f55a8fc40809b55fa3f505f'
6
+ metadata.gz: 628ec2cae8c6ea70c2a43027e749bf6f1c3c1f1b4b825ed1e0b24a429cea18d043ee25cbef04a37b73dd49f7ee7343cf468e99a50a21e9f05ee458951f43a5ab
7
+ data.tar.gz: cd3b210d94da9263eb497577d93fb93bc36d8cd45fd9e58ca0267019c646c426832ed9af2ccc001bb2a94a29e05a8aa02ac0b94c96aaf01fb4cf3aebc9bea428
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # NOBSPW - No Bullshit Password strength checker
2
2
 
3
+ [![Build Status](https://travis-ci.org/cmer/nobspw.svg?branch=master)](https://travis-ci.org/cmer/nobspw)
4
+
3
5
  NOBSPW is simple, no non-sense password strength checker written in Ruby. It does NOT validate against [bullshit password rules](https://twitter.com/codinghorror/status/631238409269309440?ref_src=twsrc%5Etfw) such as:
4
6
 
5
7
  - must contain uppercase _(bullshit!)_
@@ -73,9 +75,9 @@ validates :password, presence: true, password: true, if: -> { new_record? || cha
73
75
 
74
76
  PasswordValidator will try to guess the correct field name for each `PasswordChecker` argument as follow:
75
77
 
76
- - `username`: `username user_name user screenname screen_name`
77
- - `name`: `name full_name first_name+last_name`
78
- - `email`: `email email_address`
78
+ - `username`: `username` `user_name` `user` `screenname` `screen_name`
79
+ - `name`: `name` `full_name` `first_name+last_name`
80
+ - `email`: `email` `email_address`
79
81
 
80
82
  If you have field names different than above, you can tell `PasswordValidator` which fields to use for specific attributes:
81
83
 
@@ -86,23 +88,61 @@ validates :password, presence: true,
86
88
  if: -> { new_record? || changes[:password] }
87
89
  ```
88
90
 
89
- # Checks
91
+ ## Validations
90
92
 
91
- NOBSPW currently checks for the following, in this order:
93
+ NOBSPW currently validates for the following, in this order:
92
94
 
93
95
  ```ruby
94
- :name_included_in_password
95
- :email_included_in_password
96
- :domain_included_in_password
97
- :password_too_short
98
- :password_too_long
99
- :not_enough_unique_characters
100
- :password_blacklisted
101
- :password_too_common
96
+ name_included_in_password?
97
+ email_included_in_password?
98
+ domain_included_in_password?
99
+ password_too_short?
100
+ password_too_long?
101
+ not_enough_unique_characters?
102
+ password_not_allowed?
103
+ password_too_common?
102
104
  ```
103
-
104
105
  If any of these tests fail, they'll be returned by `#reasons`, or with Rails, they'll be added to `errors[:password]`.
105
106
 
107
+
108
+ ## Custom Validations
109
+
110
+ It is possible and easy to add your own validations, or remove default ones.
111
+
112
+ ### Adding a custom validation
113
+
114
+ ```ruby
115
+ module NOBSPW::ValidationMethods
116
+ def contains_letter_a?
117
+ # This is obviously a silly validation. Don't do this!
118
+ # If method returns true, it means that it FAILED validation.
119
+ # For example, it does contain the letter a and it's not considered acceptable.
120
+ !!@password.downcase.index('a')
121
+ end
122
+ end
123
+
124
+ NOBSPW.configuration.validation_methods << :contains_letter_a?
125
+
126
+ # if using the Rails validator, you also need to define the error message:
127
+ ActiveModel::Validations::PasswordValidator.error_messages[:contains_letter_a] = \
128
+ 'contains an unacceptable character'
129
+
130
+ ```
131
+
132
+ ### Removing a built-in validation
133
+
134
+ ```ruby
135
+ NOBSPW.configuration.validation_methods.delete(:domain_included_in_password?)
136
+ ```
137
+
138
+ ### Localization
139
+
140
+ You can localize all error messages, including custom ones, using I18n.
141
+
142
+ See `PasswordValidator#get_message` and `PasswordValidator#DEFAULT_ERROR_MESSAGES` for implementation details.
143
+
144
+
145
+
106
146
  ## License
107
147
 
108
148
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -1,4 +1,16 @@
1
1
  class ActiveModel::Validations::PasswordValidator < ActiveModel::EachValidator
2
+ DEFAULT_ERROR_MESSAGES = {
3
+ name_included_in_password: 'is too similar to your name',
4
+ email_included_in_password: 'is too similar to your email',
5
+ domain_included_in_password: 'is too similar to this domain name',
6
+ password_too_short: 'is too short',
7
+ password_too_long: 'is too long',
8
+ not_enough_unique_characters: 'does not have enough unique characters',
9
+ password_not_allowed: 'is not allowed',
10
+ password_too_common: 'is too common',
11
+ fallback: 'is not valid'
12
+ }
13
+
2
14
  def validate_each(record, attribute, value)
3
15
  pc = NOBSPW::PasswordChecker.new password: record.send(attribute),
4
16
  email: email_value(record),
@@ -11,6 +23,14 @@ class ActiveModel::Validations::PasswordValidator < ActiveModel::EachValidator
11
23
  pc.strong?
12
24
  end
13
25
 
26
+ def error_messages
27
+ @error_messages ||= DEFAULT_ERROR_MESSAGES
28
+ end
29
+
30
+ def error_messages=(em)
31
+ @error_messages = em
32
+ end
33
+
14
34
  private
15
35
 
16
36
  def email_value(record)
@@ -57,26 +77,7 @@ class ActiveModel::Validations::PasswordValidator < ActiveModel::EachValidator
57
77
  end
58
78
 
59
79
  def get_message(reason)
60
- case reason
61
- when :name_included_in_password
62
- I18n.t 'password_validator.is_too_similar_to_your_name', default: 'is too similar to your name'
63
- when :email_included_in_password
64
- I18n.t 'password_validator.is_too_similar_to_your_email', default: 'is too similar to your email'
65
- when :domain_included_in_password
66
- I18n.t 'password_validator.is_too_similar_to_this_domain_name', default: 'is too similar to this domain name'
67
- when :password_too_short
68
- I18n.t 'password_validator.is_too_short', default: 'is too short'
69
- when :password_too_long
70
- I18n.t 'password_validator.is_too_long', default: 'is too long'
71
- when :password_blacklisted
72
- I18n.t 'password_validator.is_not_allowed', default: 'is not allowed'
73
- when :password_too_common
74
- I18n.t 'password_validator.is_too_common', default: 'is too common'
75
- when :not_enough_unique_characters
76
- I18n.t 'password_validator.does_not_have_enough_unique_chars', default: 'does not have enough unique characters'
77
- else
78
- I18n.t 'password_validator.is_not_valid', default: 'is not valid'
79
- end
80
+ I18n.t "password_validator.#{reason}", default: error_messages[reason] ||
81
+ error_messages[:fallback]
80
82
  end
81
-
82
83
  end
@@ -2,6 +2,7 @@ require "nobspw/version"
2
2
 
3
3
  module NOBSPW
4
4
  autoload :PasswordChecker, 'nobspw/password_checker'
5
+ autoload :ValidationMethods, 'nobspw/validation_methods'
5
6
  autoload :Configuration, 'nobspw/configuration'
6
7
 
7
8
  if (defined?(::ActiveModel) && ActiveModel::VERSION::MAJOR >= 4) && defined?(I18n)
@@ -7,6 +7,7 @@ module NOBSPW
7
7
  attr_accessor :grep_path
8
8
  attr_accessor :domain_name
9
9
  attr_accessor :blacklist
10
+ attr_accessor :validation_methods
10
11
 
11
12
  def initialize
12
13
  @min_password_length = 10
@@ -16,6 +17,7 @@ module NOBSPW
16
17
  @grep_path = `which grep`.strip
17
18
  @domain_name = nil
18
19
  @blacklist = nil
20
+ @validation_methods = NOBSPW::ValidationMethods::DEFAULT_VALIDATION_METHODS
19
21
  end
20
22
  end
21
23
  end
@@ -1,13 +1,6 @@
1
1
  module NOBSPW
2
2
  class PasswordChecker
3
- CHECKS = %i(name_included_in_password
4
- email_included_in_password
5
- domain_included_in_password
6
- password_too_short
7
- password_too_long
8
- not_enough_unique_characters
9
- password_blacklisted
10
- password_too_common)
3
+ include NOBSPW::ValidationMethods
11
4
 
12
5
  def initialize(name: nil, username: nil, email: nil, password:)
13
6
  @name, @username, @email, @password = \
@@ -36,90 +29,15 @@ module NOBSPW
36
29
  def check_password
37
30
  @weak_password_reasons = []
38
31
 
39
- CHECKS.each do |check|
40
- @weak_password_reasons << check if send("#{check}?")
32
+ NOBSPW.configuration.validation_methods.each do |method|
33
+ @weak_password_reasons << method.to_s.sub(/\?$/, '').to_sym if send("#{method}")
41
34
  end
42
35
 
43
36
  @strong = @weak_password_reasons.empty?
44
37
  end
45
38
 
46
- def name_included_in_password?
47
- return nil unless @name
48
- words = remove_word_separators(@name).split(' ')
49
- words_included_in_password?(words)
50
- end
51
-
52
- def email_included_in_password?
53
- return nil unless @email
54
- words = remove_word_separators(email_without_extension(@email)).split(' ')
55
- words_included_in_password?(words)
56
- end
57
-
58
- def domain_included_in_password?
59
- domain = NOBSPW.configuration.domain_name
60
- return nil unless domain
61
- domain = strip_extension_from_domain(domain)
62
- domain_without_separator = remove_word_separators(domain).gsub(' ', '')
63
- words_included_in_password?([domain, domain_without_separator])
64
- end
65
-
66
- def password_blacklisted?
67
- return nil unless NOBSPW.configuration.blacklist
68
- NOBSPW.configuration.blacklist.include?(@password)
69
- end
70
-
71
- def password_too_short?
72
- @password.length < NOBSPW.configuration.min_password_length
73
- end
74
-
75
- def password_too_long?
76
- @password.length > NOBSPW.configuration.max_password_length
77
- end
78
-
79
- def not_enough_unique_characters?
80
- unique = @password.split(//).uniq.size
81
- minimum = NOBSPW.configuration.min_unique_characters
82
- !(unique >= minimum)
83
- end
84
-
85
- def words_included_in_password?(words)
86
- downcased_pw = @password.downcase
87
- words.each do |word|
88
- return true if word.length > 2 && downcased_pw.index(word.downcase)
89
- end; false
90
- end
91
-
92
- def password_too_common?
93
- `#{grep_command(NOBSPW.configuration.dictionary_path)}`
94
-
95
- case $?.exitstatus
96
- when 0
97
- true
98
- when 1
99
- false
100
- when 127
101
- raise StandardError.new("Grep not found at: #{NOBSPW.configuration.grep_path}")
102
- else
103
- false
104
- end
105
- end
106
-
107
39
  def grep_command(path)
108
40
  "#{NOBSPW.configuration.grep_path} '^#{@password}$' #{path}"
109
41
  end
110
-
111
- def email_without_extension(email)
112
- name, domain, whatev = email.split("@", 3)
113
- "#{name}@#{strip_extension_from_domain(domain)}"
114
- end
115
-
116
- def strip_extension_from_domain(domain)
117
- domain.split(".").first
118
- end
119
-
120
- def remove_word_separators(str)
121
- str.gsub(/-|_|\.|\'|\"|\@/, ' ')
122
- end
123
42
  end
124
-
125
43
  end
@@ -0,0 +1,91 @@
1
+ module NOBSPW
2
+ module ValidationMethods
3
+ DEFAULT_VALIDATION_METHODS = %i(name_included_in_password?
4
+ email_included_in_password?
5
+ domain_included_in_password?
6
+ password_too_short?
7
+ password_too_long?
8
+ not_enough_unique_characters?
9
+ password_not_allowed?
10
+ password_too_common?)
11
+
12
+ private
13
+
14
+ def name_included_in_password?
15
+ return nil unless @name
16
+ words = remove_word_separators(@name).split(' ')
17
+ words_included_in_password?(words)
18
+ end
19
+
20
+ def email_included_in_password?
21
+ return nil unless @email
22
+ words = remove_word_separators(email_without_extension(@email)).split(' ')
23
+ words_included_in_password?(words)
24
+ end
25
+
26
+ def domain_included_in_password?
27
+ domain = NOBSPW.configuration.domain_name
28
+ return nil unless domain
29
+ domain = strip_extension_from_domain(domain)
30
+ domain_without_separator = remove_word_separators(domain).gsub(' ', '')
31
+ words_included_in_password?([domain, domain_without_separator])
32
+ end
33
+
34
+ def password_not_allowed?
35
+ return nil unless NOBSPW.configuration.blacklist
36
+ NOBSPW.configuration.blacklist.include?(@password)
37
+ end
38
+
39
+ def password_too_short?
40
+ @password.length < NOBSPW.configuration.min_password_length
41
+ end
42
+
43
+ def password_too_long?
44
+ @password.length > NOBSPW.configuration.max_password_length
45
+ end
46
+
47
+ def not_enough_unique_characters?
48
+ unique = @password.split(//).uniq.size
49
+ minimum = NOBSPW.configuration.min_unique_characters
50
+ !(unique >= minimum)
51
+ end
52
+
53
+ def words_included_in_password?(words)
54
+ downcased_pw = @password.downcase
55
+ words.each do |word|
56
+ return true if word.length > 2 && downcased_pw.index(word.downcase)
57
+ end; false
58
+ end
59
+
60
+ def password_too_common?
61
+ `#{grep_command(NOBSPW.configuration.dictionary_path)}`
62
+
63
+ case $?.exitstatus
64
+ when 0
65
+ true
66
+ when 1
67
+ false
68
+ when 127
69
+ raise StandardError.new("Grep not found at: #{NOBSPW.configuration.grep_path}")
70
+ else
71
+ false
72
+ end
73
+ end
74
+
75
+ # Helper methods
76
+
77
+ def email_without_extension(email)
78
+ name, domain, whatev = email.split("@", 3)
79
+ "#{name}@#{strip_extension_from_domain(domain)}"
80
+ end
81
+
82
+ def strip_extension_from_domain(domain)
83
+ domain.split(".").first
84
+ end
85
+
86
+ def remove_word_separators(str)
87
+ str.gsub(/-|_|\.|\'|\"|\@/, ' ')
88
+ end
89
+
90
+ end
91
+ end
@@ -1,3 +1,3 @@
1
1
  module NOBSPW
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nobspw
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Mercier
@@ -173,6 +173,7 @@ files:
173
173
  - lib/nobspw.rb
174
174
  - lib/nobspw/configuration.rb
175
175
  - lib/nobspw/password_checker.rb
176
+ - lib/nobspw/validation_methods.rb
176
177
  - lib/nobspw/version.rb
177
178
  - nobspw.gemspec
178
179
  homepage: https://github.com/cmer/nobspw