Class: MemorablePassword

Inherits:
Object
  • Object
show all
Defined in:
lib/memorable_password.rb,
lib/memorable_password/version.rb

Overview

This class is used for generating memorable passwords. It generates the password from a list of words, proper names, digits and characters.

If no options are passed in, the dictionary is built from ‘/usr/share/dict/words’.

Options are:

  • ban_list - an array of words that should added to the blacklist

  • dictionary_paths - an array of paths to files that contain dictionary words

  • blacklist_paths - an array of paths to files that contain blacklisted words

Option examples:

MemorablePassword.new(:ban_list => ['bad word'])
MemorablePassword.new(:dictionary_paths => ['path_to_dictionary/dict.txt'])
MemorablePassword.new(:blacklist_paths => ['path_to_blacklist/blacklist.txt'])

Generate password

Generate a random password by calling #generate. See #generate for configuration details.

MemorablePassword.new.generate
# => "fig7joeann"

Generate simple password

Generate a simple 9-character password by calling #generate_simple.

MemorablePassword.new.generate_simple
# => "sons3pied"

Constant Summary collapse

MAX_WORD_LENGTH =
7
DIGITS =
%w[0 1 2 3 4 5 6 7 8 9].freeze
NON_WORD_DIGITS =
(DIGITS - %w(2 4 8)).freeze
CHARACTERS =
%w[! @ $ ? -].freeze
DEFAULT_PATH =

The default paths to the various dictionary flat files

"#{File.dirname(__FILE__)}/memorable_password"
DEFAULT_DICTIONARY_PATHS =
['/usr/share/dict/words', "#{DEFAULT_PATH}/names.txt"]
DEFAULT_BLACKLIST_PATHS =
["#{DEFAULT_PATH}/blacklist.txt"]
DEFAULT_GENERATE_OPTIONS =
{
  :mixed_case => false,
  :special_characters => false,
  :length => nil,
  :min_length => nil
}
VERSION =
"0.1.3"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ MemorablePassword



52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/memorable_password.rb', line 52

def initialize(options={})
  # TODO implement these lists as Sets to get Hash lookup and uniqueness for free -- matt.dressel 20120328
  # The dictionary is currently a hash of length to words: {1 => ['w','a'], 2 => ['at']}
  @ban_list = options.fetch(:ban_list, [])

  # TODO support passing data in as an array -- matt.dressel 20120328
  @dictionary_paths = options.fetch(:dictionary_paths, DEFAULT_DICTIONARY_PATHS)
  @blacklist_paths = options.fetch(:blacklist_paths, DEFAULT_BLACKLIST_PATHS)

  @dictionary = {}
  @blacklist = []

  build_dictionary
end

Instance Attribute Details

#ban_listObject (readonly)

Returns the value of attribute ban_list.



49
50
51
# File 'lib/memorable_password.rb', line 49

def ban_list
  @ban_list
end

#blacklistObject (readonly)

Returns the value of attribute blacklist.



49
50
51
# File 'lib/memorable_password.rb', line 49

def blacklist
  @blacklist
end

#blacklist_pathsObject (readonly)

Returns the value of attribute blacklist_paths.



49
50
51
# File 'lib/memorable_password.rb', line 49

def blacklist_paths
  @blacklist_paths
end

#dictionaryObject (readonly)

Returns the value of attribute dictionary.



49
50
51
# File 'lib/memorable_password.rb', line 49

def dictionary
  @dictionary
end

#dictionary_pathsObject (readonly)

Returns the value of attribute dictionary_paths.



49
50
51
# File 'lib/memorable_password.rb', line 49

def dictionary_paths
  @dictionary_paths
end

Instance Method Details

#add_word(word) ⇒ Object

Adds the word to the dictionary unless it is invalid or blacklisted



136
137
138
139
140
141
142
143
144
145
# File 'lib/memorable_password.rb', line 136

def add_word(word)
  return unless validate_word(word)
  word = normalize_word(word)

  unless @blacklist.include?(word)
    length = word.length
    @dictionary[length] = [] unless @dictionary[length]
    @dictionary[length] << word
  end
end

#blacklist_word(word) ⇒ Object

Adds the word to the blacklist unless it is invalid



148
149
150
151
152
153
154
155
156
# File 'lib/memorable_password.rb', line 148

def blacklist_word(word)
  return unless validate_word(word)
  word = normalize_word(word)

  @blacklist << word
  # Remove the blacklisted word from the dictionary if it exists
  dictionary_array = @dictionary[word.length]
  dictionary_array.delete(word) if dictionary_array && dictionary_array.include?(word)
end

#generate(opts = {}) ⇒ Object

Generates memorable password.

opts

hash with options

:mixed_case

true or false - use mixedCase (default: false)

:special_characters

true or false - use special characters like ! @ $? - (default: false)

:length

Fixnum - generate passoword with specific length

:min_length

Fixnum - generate passoword with length grather or equal of specified

Options :length and :min_length are incompatible.



80
81
82
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/memorable_password.rb', line 80

def generate(opts={})
  opts = DEFAULT_GENERATE_OPTIONS.merge(opts)

  raise "You cannot specify :length and :min_length at the same time" if opts[:length] && opts[:min_length]

  if opts[:length]
    password = [(opts[:length] >= 8 ? long_word : word), (opts[:special_characters] ? character : digit)]
    password << word(opts[:length] - password.compact.join.length)

    if (count = opts[:length] - password.compact.join.length) > 0
      if count == 1
        password << digit
      else
        password << word(count)
      end
    end

  elsif opts[:special_characters]
    password = [word, character, word, digit]
  else
    password = [word, non_word_digit, long_word]
  end

  if opts[:mixed_case]
    password.compact.reject{|x| x.length == 1}.sample.capitalize!
  end

  # If a minimum length is required and this password is too short
  compact_password_length = password.compact.join.length
  if opts[:min_length] && compact_password_length < opts[:min_length]
    if (count = opts[:min_length] - compact_password_length) == 1
      password << digit
    else
      password << word(count)
    end
  end


  # If it is too long, just cut it down to size. This should not happen often unless the :length option is present and is very small.
  compact_password_string = password.compact.join
  if opts[:length] && compact_password_string.length > opts[:length]
    result = compact_password_string.slice(0, opts[:length])

    # If there is no digit then it is probably a short password that by chance is just a dictionary word. Override that because that is bad.
    if result =~ /^[a-z]+$/
      password = [(opts[:mixed_case] ? word(opts[:length] - 1).capitalize : word(opts[:length] - 1)), (opts[:special_characters] ? character : digit)]
      result = password.compact.join
    end

    result
  else
    compact_password_string
  end
end

#generate_simpleObject

Generates memorable password as a combination of two 4-letter dictionary words joined by a numeric character excluding 2, 4 and 8.



69
70
71
# File 'lib/memorable_password.rb', line 69

def generate_simple
  "#{word(4)}#{non_word_digit}#{word(4)}"
end

#inspectObject



158
159
160
# File 'lib/memorable_password.rb', line 158

def inspect
  to_s
end