nobspw 0.2.0 → 0.3.0

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
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