class PasswordIsStrongValidator
Validates that
Constants
- COMMON_PASSWORDS
Known passwords that should NOT be allowed and should be considered weak.
- SPECIAL_CHARS
Special characters that are considered to strength passwords and are required once in a strong password.
Public Instance Methods
validate_each(record, attribute, value)
click to toggle source
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.
# File app/validators/password_is_strong_validator.rb, line 19 def validate_each(record, attribute, value) return if value.blank? if is_simple?(value) record.errors[attribute] << "must contain letters, numbers, and at least one special character" end if contains_username?(record.username, value) record.errors[attribute] << "must not contain the username" end if is_common_password?(value) record.errors[attribute] << "must not be a common password" end if contains_repetition?(value) record.errors[attribute] << "must not be a predictable sequence of characters" end end
Private Instance Methods
contains_repetition?(password)
click to toggle source
# File app/validators/password_is_strong_validator.rb, line 95 def contains_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
contains_username?(username, password)
click to toggle source
# File app/validators/password_is_strong_validator.rb, line 45 def contains_username?(username, password) !!(password =~ /#{username}/i) end
is_common_password?(password)
click to toggle source
# File app/validators/password_is_strong_validator.rb, line 49 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_simple?(password)
click to toggle source
# File app/validators/password_is_strong_validator.rb, line 41 def is_simple?(password) not (password =~ /[A-Za-z]/ and password =~ /[0-9]/ and password =~ /[#{Regexp.escape(SPECIAL_CHARS)}]/) end
mutate_pass(password)
click to toggle source
# File app/validators/password_is_strong_validator.rb, line 62 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