Class: PasswordIsStrongValidator
- Inherits:
-
ActiveModel::EachValidator
- Object
- ActiveModel::EachValidator
- PasswordIsStrongValidator
- Defined in:
- app/validators/password_is_strong_validator.rb
Overview
Validates that the password is strong.
Constant Summary collapse
- COMMON_PASSWORDS =
Known passwords that should NOT be allowed and should be considered weak.
%w{ password pass root admin metasploit msf 123456 qwerty abc123 letmein monkey link182 demo changeme test1234 rapid7 }
- SPECIAL_CHARS =
Special characters that are considered to strength passwords and are required once in a strong password.
%q{!@"#$%&'()*+,-./:;<=>?[\\]^_`{|}~ }
Instance Method Summary collapse
-
#contains_username?(username, password) ⇒ true, false
private
Returns whether username is in password (case-insensitively).
-
#is_common_password?(password) ⇒ Boolean
private
Returns whether
password
is in COMMON_PASSWORDS or a simple variation of a password in COMMON_PASSWORDS. -
#is_only_repetition?(password) ⇒ Boolean
private
Returns whether
password
is only composed of repetitions. -
#is_simple?(password) ⇒ false, true
private
Returns whether the password is simple.
-
#mutate_pass(password) ⇒ String
private
Returns a leet mutated variant of the original password.
-
#validate_each(record, attribute, value) ⇒ void
Validates that the
attribute
'svalue
onrecord
contains letters, numbers, and at least one special character without containing therecord.username
, any COMMON_PASSWORDS or repetition.
Instance Method Details
#contains_username?(username, password) ⇒ true, false (private)
Returns whether username is in password (case-insensitively).
58 59 60 |
# File 'app/validators/password_is_strong_validator.rb', line 58 def contains_username?(username, password) !!(password =~ /#{username}/i) end |
#is_common_password?(password) ⇒ Boolean (private)
Returns whether password
is in COMMON_PASSWORDS or a simple variation of a password in COMMON_PASSWORDS.
66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'app/validators/password_is_strong_validator.rb', line 66 def is_common_password?(password) COMMON_PASSWORDS.each do |pw| common_pw = [pw] # pw + "!", pw + "1", pw + "12", pw + "123", pw + "1234"] common_pw += mutate_pass(pw) common_pw.each do |common_pass| if password.downcase =~ /#{common_pass}[\d!]*/ return true end end end false end |
#is_only_repetition?(password) ⇒ Boolean (private)
Returns whether password
is only composed of repetitions
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'app/validators/password_is_strong_validator.rb', line 119 def is_only_repetition?(password) # Password repetition (quite basic) -- no "aaaaaa" or "ababab" or "abcabc" or # "abcdabcd" (but note that the user can use "aaaaaab" or something). if password.scan(/./).uniq.size < 2 return true end if (password.size % 2 == 0) and (password.scan(/../).uniq.size < 2) return true end if (password.size % 3 == 0) and (password.scan(/.../).uniq.size < 2) return true end if (password.size % 4 == 0) and (password.scan(/..../).uniq.size < 2) return true end false end |
#is_simple?(password) ⇒ false, true (private)
Returns whether the password is simple.
50 51 52 |
# File 'app/validators/password_is_strong_validator.rb', line 50 def is_simple?(password) not (password =~ /[A-Za-z]/ and password =~ /[0-9]/ and password =~ /[#{Regexp.escape(SPECIAL_CHARS)}]/) end |
#mutate_pass(password) ⇒ String (private)
Returns a leet mutated variant of the original password
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'app/validators/password_is_strong_validator.rb', line 83 def mutate_pass(password) mutations = { 'a' => '@', 'o' => '0', 'e' => '3', 's' => '$', 't' => '7', 'l' => '1' } iterations = mutations.keys.dup results = [] # Find PowerSet of all possible mutation combinations iterations = iterations.inject([[]]){|c,y|r=[];c.each{|i|r<<i;r<<i+[y]};r} # Iterate through combinations to create each possible mutation iterations.each do |iteration| next if iteration.flatten.empty? first = iteration.shift intermediate = password.gsub(/#{first}/i, mutations[first]) iteration.each do |mutator| next unless mutator.kind_of? String intermediate.gsub!(/#{mutator}/i, mutations[mutator]) end results << intermediate end return results end |
#validate_each(record, attribute, value) ⇒ void
This method returns an undefined value.
Validates that the attribute
's value
on record
contains letters, numbers, and at least one special character
without containing the record.username
, any COMMON_PASSWORDS or repetition.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'app/validators/password_is_strong_validator.rb', line 24 def validate_each(record, attribute, value) return if value.blank? if is_simple?(value) record.errors.add attribute, 'must contain letters, numbers, and at least one special character' end if !record.username.blank? && contains_username?(record.username, value) record.errors.add attribute, 'must not contain the username' end if is_common_password?(value) record.errors.add attribute, 'must not be a common password' end if is_only_repetition?(value) record.errors.add attribute, 'must not be a predictable sequence of characters' end end |