memorable_password 0.0.3 → 0.1.1
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.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +1 -1
- data/README.markdown +11 -7
- data/Rakefile +9 -0
- data/lib/memorable_password/sample.rb +1 -1
- data/lib/memorable_password/version.rb +2 -2
- data/lib/memorable_password.rb +181 -75
- data/memorable_password.gemspec +6 -2
- data/spec/lib/config/custom_blacklist.txt +3 -0
- data/spec/lib/config/custom_dictionary.txt +14 -0
- data/spec/lib/config/short_dictionary.txt +1 -0
- data/spec/lib/memorable_password_spec.rb +171 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/match_any_matcher.rb +23 -0
- metadata +62 -10
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm ree@memorable_password --create
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Memorable Password
|
2
2
|
|
3
|
-
[Kevin McPhillips](mailto:github@kevinmcphillips.ca)
|
4
|
-
|
3
|
+
[Kevin McPhillips](mailto:github@kevinmcphillips.ca),
|
4
|
+
[Oleksandr Ulianytskyi](mailto:a.ulyanitsky@gmail.com)
|
5
5
|
|
6
6
|
## About
|
7
7
|
|
@@ -14,29 +14,33 @@ It is, of course, by definition less secure than a truly random password. The in
|
|
14
14
|
|
15
15
|
Generates a password with the default length of 8 characters.
|
16
16
|
|
17
|
-
MemorablePassword.generate
|
17
|
+
MemorablePassword.new.generate
|
18
18
|
=> "pad8dune"
|
19
19
|
|
20
20
|
Generates a password with a specified length.
|
21
21
|
|
22
|
-
MemorablePassword.generate :length => 10
|
22
|
+
MemorablePassword.new.generate :length => 10
|
23
23
|
=> "june3eaten"
|
24
24
|
|
25
25
|
Generates a password that is at least a certain length.
|
26
26
|
|
27
|
-
MemorablePassword.generate :min_length => 8
|
27
|
+
MemorablePassword.new.generate :min_length => 8
|
28
28
|
=> "gale3covalt"
|
29
29
|
|
30
30
|
Generates a password that includes special characters.
|
31
31
|
|
32
|
-
MemorablePassword.generate :special_characters => true
|
32
|
+
MemorablePassword.new.generate :special_characters => true
|
33
33
|
=> "grace!pi"
|
34
34
|
|
35
35
|
Generates a password that mixes upper case in.
|
36
36
|
|
37
|
-
MemorablePassword.generate :mixed_case => true
|
37
|
+
MemorablePassword.new.generate :mixed_case => true
|
38
38
|
=> "was7Room"
|
39
39
|
|
40
|
+
Generates a password that is two 4-char words joined by non-ambiguous digit (not 2 and 4).
|
41
|
+
|
42
|
+
MemorablePassword.new.generate_simple
|
43
|
+
=> "sons3pied"
|
40
44
|
|
41
45
|
## Feedback
|
42
46
|
|
data/Rakefile
CHANGED
@@ -1,2 +1,11 @@
|
|
1
1
|
require 'bundler'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
2
4
|
Bundler::GemHelper.install_tasks
|
5
|
+
RSpec::Core::RakeTask.new
|
6
|
+
|
7
|
+
desc "Run all specs with rcov"
|
8
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
9
|
+
t.rcov = true
|
10
|
+
t.rcov_opts = %w{--rails --exclude osx\/objc,gems\/,spec\/,features\/}
|
11
|
+
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "0.
|
1
|
+
class MemorablePassword
|
2
|
+
VERSION = "0.1.1"
|
3
3
|
end
|
data/lib/memorable_password.rb
CHANGED
@@ -1,27 +1,87 @@
|
|
1
1
|
require 'memorable_password/sample'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
# This class is used for generating memorable passwords. It generates the password
|
4
|
+
# from a list of words, proper names, digits and characters.
|
5
|
+
#
|
6
|
+
# If no options are passed in, the dictionary is built from '/usr/share/dict/words'.
|
7
|
+
#
|
8
|
+
# Options are:
|
9
|
+
# * <tt>ban_list</tt> - an array of words that should added to the blacklist
|
10
|
+
# * <tt>dictionary_paths</tt> - an array of paths to files that contain dictionary words
|
11
|
+
# * <tt>blacklist_paths</tt> - an array of paths to files that contain blacklisted words
|
12
|
+
#
|
13
|
+
# Option examples:
|
14
|
+
# MemorablePassword.new(:ban_list => ['bad word'])
|
15
|
+
# MemorablePassword.new(:dictionary_paths => ['path_to_dictionary/dict.txt'])
|
16
|
+
# MemorablePassword.new(:blacklist_paths => ['path_to_blacklist/blacklist.txt'])
|
17
|
+
#
|
18
|
+
# == Generate password
|
19
|
+
# Generate a random password by calling #generate.
|
20
|
+
# See #generate for configuration details.
|
21
|
+
#
|
22
|
+
# MemorablePassword.new.generate
|
23
|
+
# # => "fig7joeann"
|
24
|
+
#
|
25
|
+
# == Generate simple password
|
26
|
+
# Generate a simple 9-character password by calling #generate_simple.
|
27
|
+
#
|
28
|
+
# MemorablePassword.new.generate_simple
|
29
|
+
# # => "sons3pied"
|
30
|
+
#
|
31
|
+
class MemorablePassword
|
5
32
|
MAX_WORD_LENGTH = 7
|
6
|
-
DIGITS =
|
7
|
-
|
8
|
-
|
9
|
-
|
33
|
+
DIGITS = %w[0 1 2 3 4 5 6 7 8 9].freeze
|
34
|
+
NON_WORD_DIGITS = (DIGITS - %w(2 4 8)).freeze
|
35
|
+
CHARACTERS = %w[! @ $ ? -].freeze
|
36
|
+
|
37
|
+
# The default paths to the various dictionary flat files
|
38
|
+
DEFAULT_PATH = "#{File.dirname(__FILE__)}/memorable_password"
|
39
|
+
DEFAULT_DICTIONARY_PATHS = ['/usr/share/dict/words', "#{DEFAULT_PATH}/names.txt"]
|
40
|
+
DEFAULT_BLACKLIST_PATHS = ["#{DEFAULT_PATH}/blacklist.txt"]
|
41
|
+
|
42
|
+
DEFAULT_GENERATE_OPTIONS = {
|
10
43
|
:mixed_case => false,
|
11
44
|
:special_characters => false,
|
12
45
|
:length => nil,
|
13
46
|
:min_length => nil
|
14
47
|
}
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
48
|
+
|
49
|
+
attr_reader :dictionary, :blacklist, :ban_list,
|
50
|
+
:dictionary_paths, :blacklist_paths
|
51
|
+
|
52
|
+
def initialize(options={})
|
53
|
+
# TODO implement these lists as Sets to get Hash lookup and uniqueness for free -- matt.dressel 20120328
|
54
|
+
# The dictionary is currently a hash of length to words: {1 => ['w','a'], 2 => ['at']}
|
55
|
+
@ban_list = options.fetch(:ban_list, [])
|
56
|
+
|
57
|
+
# TODO support passing data in as an array -- matt.dressel 20120328
|
58
|
+
@dictionary_paths = options.fetch(:dictionary_paths, DEFAULT_DICTIONARY_PATHS)
|
59
|
+
@blacklist_paths = options.fetch(:blacklist_paths, DEFAULT_BLACKLIST_PATHS)
|
60
|
+
|
61
|
+
@dictionary = {}
|
62
|
+
@blacklist = []
|
63
|
+
|
64
|
+
build_dictionary
|
65
|
+
end
|
66
|
+
|
67
|
+
# Generates memorable password as a combination of two 4-letter dictionary
|
68
|
+
# words joined by a numeric character excluding 2, 4 and 8.
|
69
|
+
def generate_simple
|
70
|
+
"#{word(4)}#{non_word_digit}#{word(4)}"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Generates memorable password.
|
74
|
+
# +opts+:: hash with options
|
75
|
+
# [:mixed_case] +true+ or +false+ - use mixedCase (default: +false+)
|
76
|
+
# [:special_characters] +true+ or +false+ - use special characters like ! @ $? - (default: +false+)
|
77
|
+
# [:length] Fixnum - generate passoword with specific length
|
78
|
+
# [:min_length] Fixnum - generate passoword with length grather or equal of specified
|
79
|
+
# Options +:length+ and +:min_length+ are incompatible.
|
80
|
+
def generate(opts={})
|
81
|
+
opts = DEFAULT_GENERATE_OPTIONS.merge(opts)
|
82
|
+
|
83
|
+
raise "You cannot specify :length and :min_length at the same time" if opts[:length] && opts[:min_length]
|
84
|
+
|
25
85
|
if opts[:length]
|
26
86
|
password = [(opts[:length] >= 8 ? long_word : word), (opts[:special_characters] ? character : digit)]
|
27
87
|
password << word(opts[:length] - password.compact.join.length)
|
@@ -33,95 +93,141 @@ module MemorablePassword
|
|
33
93
|
password << word(count)
|
34
94
|
end
|
35
95
|
end
|
36
|
-
|
96
|
+
|
97
|
+
elsif opts[:special_characters]
|
98
|
+
password = [word, character, word, digit]
|
37
99
|
else
|
38
|
-
|
39
|
-
password = [word, character, word, digit]
|
40
|
-
else
|
41
|
-
password = [word, digit, long_word]
|
42
|
-
end
|
100
|
+
password = [word, non_word_digit, long_word]
|
43
101
|
end
|
44
|
-
|
102
|
+
|
45
103
|
if opts[:mixed_case]
|
46
104
|
password.compact.reject{|x| x.length == 1}.sample.capitalize!
|
47
105
|
end
|
48
|
-
|
106
|
+
|
49
107
|
# If a minimum length is required and this password is too short
|
50
|
-
|
51
|
-
|
108
|
+
compact_password_length = password.compact.join.length
|
109
|
+
if opts[:min_length] && compact_password_length < opts[:min_length]
|
110
|
+
if (count = opts[:min_length] - compact_password_length) == 1
|
52
111
|
password << digit
|
53
112
|
else
|
54
113
|
password << word(count)
|
55
114
|
end
|
56
115
|
end
|
57
|
-
|
116
|
+
|
117
|
+
|
58
118
|
# 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.
|
59
|
-
|
60
|
-
|
61
|
-
|
119
|
+
compact_password_string = password.compact.join
|
120
|
+
if opts[:length] && compact_password_string.length > opts[:length]
|
121
|
+
result = compact_password_string.slice(0, opts[:length])
|
122
|
+
|
62
123
|
# 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.
|
63
124
|
if result =~ /^[a-z]+$/
|
64
125
|
password = [(opts[:mixed_case] ? word(opts[:length] - 1).capitalize : word(opts[:length] - 1)), (opts[:special_characters] ? character : digit)]
|
65
126
|
result = password.compact.join
|
66
127
|
end
|
67
|
-
|
128
|
+
|
68
129
|
result
|
69
130
|
else
|
70
|
-
|
131
|
+
compact_password_string
|
71
132
|
end
|
72
133
|
end
|
73
|
-
|
134
|
+
|
135
|
+
# Adds the +word+ to the dictionary unless it is invalid or blacklisted
|
136
|
+
def add_word(word)
|
137
|
+
return unless validate_word(word)
|
138
|
+
word = normalize_word(word)
|
139
|
+
|
140
|
+
unless @blacklist.include?(word)
|
141
|
+
length = word.length
|
142
|
+
@dictionary[length] = [] unless @dictionary[length]
|
143
|
+
@dictionary[length] << word
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Adds the +word+ to the blacklist unless it is invalid
|
148
|
+
def blacklist_word(word)
|
149
|
+
return unless validate_word(word)
|
150
|
+
word = normalize_word(word)
|
151
|
+
|
152
|
+
@blacklist << word
|
153
|
+
# Remove the blacklisted word from the dictionary if it exists
|
154
|
+
dictionary_array = @dictionary[word.length]
|
155
|
+
dictionary_array.delete(word) if dictionary_array && dictionary_array.include?(word)
|
156
|
+
end
|
157
|
+
|
74
158
|
private
|
75
|
-
|
76
|
-
|
159
|
+
|
160
|
+
# Returns a random character
|
161
|
+
def character
|
77
162
|
CHARACTERS.sample
|
78
163
|
end
|
79
|
-
|
80
|
-
|
164
|
+
|
165
|
+
# Returns a random digit
|
166
|
+
def digit
|
81
167
|
DIGITS.sample
|
82
168
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
169
|
+
|
170
|
+
# Returns a random, non-ambiguous digit (0..9 without 2, 4 and 8)
|
171
|
+
def non_word_digit
|
172
|
+
NON_WORD_DIGITS.sample
|
87
173
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
174
|
+
|
175
|
+
# Ensures that the word is valid:
|
176
|
+
# is not null
|
177
|
+
# is at least 2 letters
|
178
|
+
# and does not exceed the MAX_WORD_LENGTH
|
179
|
+
#
|
180
|
+
# Returns +true+ if the word if it is valid
|
181
|
+
# Returns +false+ if the word is invalid
|
182
|
+
def validate_word(word)
|
183
|
+
return false if word.nil?
|
184
|
+
|
185
|
+
word = normalize_word(word)
|
186
|
+
word.length <= MAX_WORD_LENGTH && word =~ /^[a-z]{2,}$/
|
92
187
|
end
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
end
|
111
|
-
|
112
|
-
self.dictionary
|
188
|
+
|
189
|
+
# Strips the word of carriage return characters
|
190
|
+
# and converts all characters to lowercase
|
191
|
+
def normalize_word(word)
|
192
|
+
word.strip.downcase
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns a random word. If +length+ is given, find a word with this length.
|
196
|
+
def word(length=nil)
|
197
|
+
length = @dictionary.keys.sample if !length || length > @dictionary.keys.max
|
198
|
+
@dictionary[length].sample if @dictionary.has_key?(length)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns a random word from most long ones
|
202
|
+
def long_word
|
203
|
+
keys = @dictionary.keys.sort
|
204
|
+
@dictionary[keys.partition{|v| v >= keys[keys.size/2] }.first.sample].sample # Magic! It actually just randomly picks from the larger words..
|
113
205
|
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
self.dictionary[length] << word
|
206
|
+
|
207
|
+
# Builds the blacklist from the blacklist text file
|
208
|
+
# Adds user-supplied banned words to the blacklist
|
209
|
+
def build_blacklist
|
210
|
+
# Load blacklisted words from blacklist files
|
211
|
+
blacklist_paths.each do |blacklist_path|
|
212
|
+
File.foreach(blacklist_path){ |word| blacklist_word(word) }
|
122
213
|
end
|
214
|
+
|
215
|
+
# Append custom banned words to the blacklist
|
216
|
+
ban_list.each{ |word| blacklist_word(word) }
|
123
217
|
end
|
124
218
|
|
125
|
-
|
126
|
-
|
219
|
+
# Builds the dictionary from flat files located in the dictionary_paths
|
220
|
+
#
|
221
|
+
# If the dictionary_paths option is not set, the default paths will be used
|
222
|
+
# including the system dictionary and a list of proper names
|
223
|
+
def build_dictionary
|
224
|
+
# Make sure the blacklist is built before building the dictionary
|
225
|
+
# add_word will make sure the word is not in the blacklist before adding it
|
226
|
+
build_blacklist
|
227
|
+
|
228
|
+
# Load dictionary words from the dictionary files
|
229
|
+
dictionary_paths.each do |dictionary_path|
|
230
|
+
File.foreach(dictionary_path){ |word| add_word(word) }
|
231
|
+
end
|
232
|
+
end
|
127
233
|
end
|
data/memorable_password.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "memorable_password"
|
7
7
|
s.version = MemorablePassword::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors = ["Kevin McPhillips"]
|
10
|
-
s.email = ["github@kevinmcphillips.ca"]
|
9
|
+
s.authors = ["Kevin McPhillips", "Oleksandr Ulianytskyi"]
|
10
|
+
s.email = ["github@kevinmcphillips.ca", "a.ulyanitsky@gmail.com"]
|
11
11
|
s.homepage = "http://github.com/kimos/memorable_password"
|
12
12
|
s.summary = %q{Generate human readable and easy to remember passwords}
|
13
13
|
s.description = %q{This simple gem generates a random password that is easy to read and remember. It uses dictionary words as well as a list of proper names mixed in with numbers and special characters.}
|
@@ -18,4 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency("rspec", ">= 2.6.0")
|
23
|
+
s.add_development_dependency("rcov", ">= 0")
|
24
|
+
s.add_development_dependency("yard", ">= 0")
|
21
25
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
foo
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
describe MemorablePassword do
|
5
|
+
let(:memorable_password) do
|
6
|
+
default_path = "#{File.dirname(__FILE__)}/config"
|
7
|
+
@memorable_password ||= MemorablePassword.new(
|
8
|
+
:dictionary_paths => ["#{default_path}/custom_dictionary.txt"],
|
9
|
+
:blacklist_paths => ["#{default_path}/custom_blacklist.txt"])
|
10
|
+
end
|
11
|
+
|
12
|
+
subject {memorable_password}
|
13
|
+
|
14
|
+
describe "constructor" do
|
15
|
+
it "should initialize ban_list" do
|
16
|
+
subject.ban_list.should_not be_nil
|
17
|
+
subject.ban_list.should be_empty
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should support a ban_list option" do
|
21
|
+
expected_ban_list = ['a','b']
|
22
|
+
MemorablePassword.new(:ban_list => expected_ban_list).ban_list.should == expected_ban_list
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should support configurable dictionary paths" do
|
26
|
+
subject.dictionary.values.flatten.should include 'word'
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should support configurable blacklist paths" do
|
30
|
+
subject.blacklist.should include 'blcklst'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#add_word" do
|
35
|
+
it "should not add words that are less than 2 characters" do
|
36
|
+
subject.add_word('i')
|
37
|
+
subject.dictionary.values.flatten.should_not include('i')
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should not add words that are greater than MemorablePassword::MAX_WORD_LENGTH" do
|
41
|
+
long_word = (0..MemorablePassword::MAX_WORD_LENGTH+1).map{|a| 'a'}.join
|
42
|
+
subject.add_word(long_word)
|
43
|
+
subject.dictionary.values.flatten.should_not include(long_word)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should not add words that have non-letters" do
|
47
|
+
subject.add_word('iask3')
|
48
|
+
subject.dictionary.values.flatten.should_not include('iask3')
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not add words that are blacklisted" do
|
52
|
+
blacklisted_word = subject.blacklist.first
|
53
|
+
subject.add_word(blacklisted_word)
|
54
|
+
subject.dictionary.values.flatten.should_not include(blacklisted_word)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should add valid words to the dictionary" do
|
58
|
+
valid_word = 'uhappy'
|
59
|
+
subject.add_word(valid_word)
|
60
|
+
subject.dictionary.values.flatten.should include(valid_word)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#blacklist_word" do
|
65
|
+
it "should not add words that are less than 2 characters" do
|
66
|
+
subject.blacklist_word('i')
|
67
|
+
subject.blacklist.should_not include('i')
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should not add words that are greater than MemorablePassword::MAX_WORD_LENGTH" do
|
71
|
+
long_word = (0..MemorablePassword::MAX_WORD_LENGTH+1).map{|a| 'a'}.join
|
72
|
+
subject.blacklist_word(long_word)
|
73
|
+
subject.blacklist.should_not include(long_word)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not add words that have non-letters" do
|
77
|
+
subject.blacklist_word('iask3')
|
78
|
+
subject.blacklist.should_not include('iask3')
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should add valid words to the blacklist" do
|
82
|
+
valid_word = 'uhappy'
|
83
|
+
subject.blacklist_word(valid_word)
|
84
|
+
subject.blacklist.should include(valid_word)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should remove blacklisted word from the dictionary" do
|
88
|
+
blacklisted_dictionary_word = subject.dictionary.values.flatten.sample
|
89
|
+
subject.blacklist_word(blacklisted_dictionary_word)
|
90
|
+
subject.dictionary.values.flatten.should_not include(blacklisted_dictionary_word)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#generate_simple" do
|
96
|
+
let(:generated_password) { @generated_password ||= subject.generate_simple }
|
97
|
+
|
98
|
+
it "should return a 9-letter string" do
|
99
|
+
generated_password.should be_a(String)
|
100
|
+
generated_password.length.should eq(9)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should be a combination of two 4-letter dictionary words joined by a numeric character" do
|
104
|
+
generated_password.should =~ /^[a-z]{4}[0-1,3-7,9][a-z]{4}$/i
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should exclude the numbers 2, 4 and 8" do
|
108
|
+
examples = Array.new(10) { memorable_password.generate_simple }
|
109
|
+
examples.should_not match_any([/[248]/])
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should not mutate the dictionary" do
|
113
|
+
default_path = "#{File.dirname(__FILE__)}/config"
|
114
|
+
memorable_password = MemorablePassword.new(:dictionary_paths => ["#{default_path}/short_dictionary.txt"])
|
115
|
+
|
116
|
+
generated_password = memorable_password.generate_simple
|
117
|
+
generated_password.should =~ /foo[0-9]foo$/
|
118
|
+
|
119
|
+
generated_password = memorable_password.generate_simple
|
120
|
+
generated_password.should =~ /foo[0-9]foo$/
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "#generate" do
|
126
|
+
it "should generate a random password" do
|
127
|
+
generated_password = memorable_password.generate
|
128
|
+
generated_password.should =~ /[a-z]*[0-9][a-z]*$/
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should support the mixed_case option" do
|
132
|
+
generated_password = memorable_password.generate(:mixed_case => true)
|
133
|
+
generated_password.should =~ /[A-Z]?[a-z]*[0-9][A-Z]?[a-z]*$/
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should support the special_characters option" do
|
137
|
+
generated_password = memorable_password.generate(:special_characters => true)
|
138
|
+
generated_password.should =~ /[a-z]*[!@$?-][a-z]*[0-9]$/
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should support the length option" do
|
142
|
+
generated_password = memorable_password.generate(:length => 5)
|
143
|
+
generated_password.length.should == 5
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should support the min_length option" do
|
147
|
+
generated_password = memorable_password.generate(:min_length => 12)
|
148
|
+
generated_password.length.should >= 12
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should raise an exception if both the length and min_length options are supplied" do
|
152
|
+
expect {
|
153
|
+
memorable_password.generate(:length => 5, :min_length => 2)
|
154
|
+
}.should raise_exception('You cannot specify :length and :min_length at the same time')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "#dictionary" do
|
159
|
+
# TODO determine if we should have a mechanistm to test or update the default blacklist
|
160
|
+
# with words from http://www.bannedwordlist.com/lists/swearWords.txt
|
161
|
+
let(:default_memorable_password) do
|
162
|
+
@default_memorable_password ||= MemorablePassword.new
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should not include any blacklisted words" do
|
166
|
+
uniq_dictionary_words = default_memorable_password.dictionary.values.flatten.uniq
|
167
|
+
uniq_blacklist_words = default_memorable_password.blacklist.uniq
|
168
|
+
(uniq_dictionary_words - uniq_blacklist_words).should == uniq_dictionary_words
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Mather check that enumerable with strings match any regexp in array
|
2
|
+
# ["abc", "def", "abb"].should match_any([/ab/, /ss/]) # => passes
|
3
|
+
# ["abc", "def", "abb"].should match_any([/yz/, /ss/]) # => fails
|
4
|
+
RSpec::Matchers.define :match_any do |patterns|
|
5
|
+
match do |actual|
|
6
|
+
actual.any? { |str| patterns.any? { |regexp| str =~ regexp } }
|
7
|
+
end
|
8
|
+
|
9
|
+
# TODO: make failure messages more informative
|
10
|
+
failure_message_for_should do |actual|
|
11
|
+
"expected that
|
12
|
+
#{actual.inspect}
|
13
|
+
matches any of
|
14
|
+
#{patterns.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
failure_message_for_should_not do |actual|
|
18
|
+
"expected that
|
19
|
+
#{actual.inspect}
|
20
|
+
not matches any of
|
21
|
+
#{patterns.inspect}"
|
22
|
+
end
|
23
|
+
end
|
metadata
CHANGED
@@ -2,26 +2,70 @@
|
|
2
2
|
name: memorable_password
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
hash: 25
|
5
|
-
prerelease:
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Kevin McPhillips
|
14
|
+
- Oleksandr Ulianytskyi
|
14
15
|
autorequire:
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date:
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
date: 2012-05-03 00:00:00 Z
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 6
|
33
|
+
- 0
|
34
|
+
version: 2.6.0
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rcov
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: yard
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
22
65
|
description: This simple gem generates a random password that is easy to read and remember. It uses dictionary words as well as a list of proper names mixed in with numbers and special characters.
|
23
66
|
email:
|
24
67
|
- github@kevinmcphillips.ca
|
68
|
+
- a.ulyanitsky@gmail.com
|
25
69
|
executables: []
|
26
70
|
|
27
71
|
extensions: []
|
@@ -30,6 +74,8 @@ extra_rdoc_files: []
|
|
30
74
|
|
31
75
|
files:
|
32
76
|
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- .rvmrc
|
33
79
|
- Gemfile
|
34
80
|
- README.markdown
|
35
81
|
- Rakefile
|
@@ -39,7 +85,12 @@ files:
|
|
39
85
|
- lib/memorable_password/sample.rb
|
40
86
|
- lib/memorable_password/version.rb
|
41
87
|
- memorable_password.gemspec
|
42
|
-
|
88
|
+
- spec/lib/config/custom_blacklist.txt
|
89
|
+
- spec/lib/config/custom_dictionary.txt
|
90
|
+
- spec/lib/config/short_dictionary.txt
|
91
|
+
- spec/lib/memorable_password_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
- spec/support/match_any_matcher.rb
|
43
94
|
homepage: http://github.com/kimos/memorable_password
|
44
95
|
licenses: []
|
45
96
|
|
@@ -69,9 +120,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
120
|
requirements: []
|
70
121
|
|
71
122
|
rubyforge_project: memorable_password
|
72
|
-
rubygems_version: 1.
|
123
|
+
rubygems_version: 1.8.15
|
73
124
|
signing_key:
|
74
125
|
specification_version: 3
|
75
126
|
summary: Generate human readable and easy to remember passwords
|
76
127
|
test_files: []
|
77
128
|
|
129
|
+
has_rdoc:
|