nice_password 1.0.0
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/Gemfile +9 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +75 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/lib/nice_password.rb +5 -0
- data/lib/nice_password/core_ext.rb +11 -0
- data/lib/nice_password/dictionaries/en.yml +1330 -0
- data/lib/nice_password/dictionaries/es.yml +1006 -0
- data/lib/nice_password/dictionaries/fr.yml +1330 -0
- data/lib/nice_password/errors.rb +9 -0
- data/lib/nice_password/nice_password.rb +134 -0
- data/rails/init.rb +1 -0
- data/spec/nice_password/nice_password_spec.rb +135 -0
- data/spec/spec_helper.rb +23 -0
- metadata +83 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
require "yaml"
|
2
|
+
module NicePassword #:nodoc:
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
@@languages = ['en', 'fr', 'es']
|
7
|
+
@@default_language = 'en'
|
8
|
+
@@dictionaries = {}
|
9
|
+
|
10
|
+
def default_language; @@default_language; end
|
11
|
+
def default_language=(value); @@default_language = value; end
|
12
|
+
|
13
|
+
# <tt>new</tt> generates a new NicePassword according to the parameters passed
|
14
|
+
# in via the options hash, or using the default parameters.
|
15
|
+
#
|
16
|
+
# Options include:
|
17
|
+
# :length - total number of characters in the password, default is 12
|
18
|
+
# :words - total number of words in the password, between 1-4, default is 2
|
19
|
+
# :digits - number of digits between each word (not the total number), default is 2
|
20
|
+
# :language - abbreviation for language to use for the words, default is 'en' (English)
|
21
|
+
# :dictionary - specify a custom dictionary to use instead of the default dictionary
|
22
|
+
#
|
23
|
+
def new(options= {})
|
24
|
+
password = ""
|
25
|
+
total_length = (options[:length] || 12).to_i
|
26
|
+
word_count = (options[:words] || 2).to_i
|
27
|
+
digit_length = (options[:digits] || 2).to_i
|
28
|
+
word_options = options.slice(:language, :dictionary)
|
29
|
+
length_variance = 3
|
30
|
+
|
31
|
+
validate_word_count(word_count)
|
32
|
+
|
33
|
+
total_digits = digit_length * (word_count - 1)
|
34
|
+
avg_word_length = (total_length - total_digits) / word_count
|
35
|
+
|
36
|
+
validate_avg_word_length(avg_word_length)
|
37
|
+
|
38
|
+
word_count.downto(1) do |n|
|
39
|
+
length_needed = total_length - password.length
|
40
|
+
break if length_needed <= 0
|
41
|
+
|
42
|
+
if n == 1 # this is the last word
|
43
|
+
word_options[:exact] = length_needed
|
44
|
+
else # not the last word
|
45
|
+
word_options[:min] = [1, avg_word_length - length_variance].max
|
46
|
+
# max_length = total remaining length - remaining digits and words
|
47
|
+
word_options[:max] = length_needed - ((n-1) * (digit_length + avg_word_length))
|
48
|
+
end
|
49
|
+
password << pick_dictionary_word(word_options)
|
50
|
+
password << pick_random_number(digit_length).to_s if digit_length > 0 && n > 1
|
51
|
+
end
|
52
|
+
|
53
|
+
return password
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
# <tt>load_dictionary</tt> load the YAML dictionary file for the given language
|
59
|
+
# The language should be a two letter abbreviation that corresponds to the file
|
60
|
+
# name in /lib/nice_password/dictionaries/*.yml such as 'en', 'fr', 'es'.
|
61
|
+
def load_dictionary(lang)
|
62
|
+
return false unless lang
|
63
|
+
dictionary = File.join(File.dirname(__FILE__), 'dictionaries', "#{lang}.yml")
|
64
|
+
YAML::load(File.open(dictionary))
|
65
|
+
end
|
66
|
+
|
67
|
+
# <tt>pick_dictionary_word</tt> selects a word from the dictionary that matches the
|
68
|
+
# desired (or default) length parameters.
|
69
|
+
#
|
70
|
+
# Options include:
|
71
|
+
# :min - minimum length word to retrieve, defaults to 1
|
72
|
+
# :max - maximum length word to retreive, defaults to 5
|
73
|
+
# :exact - if included, :exact overrides :min and :max lengths
|
74
|
+
# :language - abbreviation for language to use for the words, default is 'en' (English)
|
75
|
+
# :dictionary - specify a custom dictionary to use instead of the default dictionary
|
76
|
+
#
|
77
|
+
def pick_dictionary_word(options={})
|
78
|
+
min_length = (options[:exact] || options[:min] || 1).to_i
|
79
|
+
max_length = (options[:exact] || options[:max] || 5).to_i
|
80
|
+
|
81
|
+
language = (options[:language] || @@default_language).to_s
|
82
|
+
validate_language(language)
|
83
|
+
|
84
|
+
dictionary = options[:dictionary] || @@dictionaries[language] ||= load_dictionary(language)
|
85
|
+
validate_dictionary(dictionary)
|
86
|
+
|
87
|
+
sized_words = dictionary.select do |word|
|
88
|
+
min_length <= word.length && word.length <= max_length
|
89
|
+
end
|
90
|
+
if !sized_words.empty?
|
91
|
+
return sized_words[rand(sized_words.length)]
|
92
|
+
else
|
93
|
+
# TODO: How should an empty sized_words be handled?
|
94
|
+
return ""
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# <tt>pick_random_number</tt> chooses a random number with the same number of digits
|
99
|
+
# as the first argument
|
100
|
+
def pick_random_number(digits=1)
|
101
|
+
min = 10 ** (digits - 1)
|
102
|
+
max = (10 ** digits ) - 1
|
103
|
+
semirandom = min + rand(max-min)
|
104
|
+
semirandom += 1 if semirandom == 666 #would be unpleasant to receive...
|
105
|
+
return semirandom
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def validate_word_count(word_count)
|
111
|
+
raise NicePassword::FormatError.new("Word count must be greater than 0") if word_count < 1
|
112
|
+
raise NicePassword::FormatError.new("Word count must be less than 4") if word_count > 4
|
113
|
+
end
|
114
|
+
|
115
|
+
def validate_avg_word_length(avg_word_length)
|
116
|
+
raise NicePassword::FormatError.new("Word length is too short, increase :length") if avg_word_length <= 2
|
117
|
+
raise NicePassword::FormatError.new("Word length is too long, decrease :length") if avg_word_length >= 12
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_language(language)
|
121
|
+
unless @@languages.include?(language)
|
122
|
+
raise NicePassword::DictionaryError.new("Invalid language")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def validate_dictionary(dictionary)
|
127
|
+
unless dictionary.is_a?(Array) && !dictionary.compact.empty?
|
128
|
+
raise NicePassword::DictionaryError.new("Invalid dictionary format")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'nice_password')
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Nice Password" do
|
4
|
+
|
5
|
+
describe "defaults" do
|
6
|
+
|
7
|
+
it "should have the length of 12" do
|
8
|
+
NicePassword.new.length.should == 12
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should contain a two digit number" do
|
12
|
+
digits = NicePassword.new.scan(/\d+/)
|
13
|
+
digits.join.size.should == 2
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should contain two words" do
|
17
|
+
words = NicePassword.new.scan(/\D+/)
|
18
|
+
words.size.should == 2
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "options" do
|
24
|
+
|
25
|
+
it "should be able to specify length" do
|
26
|
+
(8..24).each do |n|
|
27
|
+
password = NicePassword.new(:length => n)
|
28
|
+
password.length.should == n
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should be able to specify number of digits" do
|
33
|
+
(1..5).each do |n|
|
34
|
+
password = NicePassword.new(:digits => n)
|
35
|
+
password.scan(/\d+/).join.size.should == n
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be able to specify number of words" do
|
40
|
+
(2..4).each do |n|
|
41
|
+
# :length increases :words to avoid validation errors
|
42
|
+
password = NicePassword.new(:words => n, :length => n * 5)
|
43
|
+
password.scan(/\D+/).size.should == n
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should be able to specify the language as a string" do
|
48
|
+
['en', 'fr', 'es'].each do |lang|
|
49
|
+
yaml_file = File.join(File.dirname(__FILE__), '..', '..', 'lib', 'nice_password', 'dictionaries', "#{lang}.yml")
|
50
|
+
dictionary = YAML::load(File.open(yaml_file))
|
51
|
+
|
52
|
+
password = NicePassword.new(:language => lang)
|
53
|
+
password.scan(/\D+/).each do |word|
|
54
|
+
dictionary.include?(word).should be_true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be able to specify the language as a symbol" do
|
61
|
+
[:en, :fr, :es].each do |lang|
|
62
|
+
yaml_file = File.join(File.dirname(__FILE__), '..', '..', 'lib', 'nice_password', 'dictionaries', "#{lang}.yml")
|
63
|
+
dictionary = YAML::load(File.open(yaml_file))
|
64
|
+
|
65
|
+
password = NicePassword.new(:language => lang)
|
66
|
+
password.scan(/\D+/).each do |word|
|
67
|
+
dictionary.include?(word).should be_true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be able to send an alternate dictionary" do
|
74
|
+
dictionary = ['a', 'b', 'c', 'd', 'e', 'ax', 'be', 'co', 'do', 'em',
|
75
|
+
'art', 'bye', 'cat', 'doe', 'ear', 'aces', 'baby', 'cart', 'dove', 'even',
|
76
|
+
'apple', 'boast', 'child', 'devil', 'eager',
|
77
|
+
'aprons', 'bounce', 'chance', 'denied', 'easter',
|
78
|
+
'approve', 'beneath', 'chiller', 'defined', 'evolves',
|
79
|
+
'annotate', 'believes', 'children', 'destruct', 'eloquent',
|
80
|
+
'appreciate', 'backbench', 'construct', 'deflected', 'everybody']
|
81
|
+
password = NicePassword.new(:dictionary => dictionary)
|
82
|
+
password.scan(/\D+/).each do |word|
|
83
|
+
dictionary.include?(word).should be_true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "validations" do
|
90
|
+
|
91
|
+
it "should raise FormatError if avg word count is too small" do
|
92
|
+
lambda do
|
93
|
+
NicePassword.new(:length => 6, :words => 0, :digits => 2)
|
94
|
+
end.should raise_error(NicePassword::FormatError)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should raise FormatError if avg word count is too large" do
|
98
|
+
lambda do
|
99
|
+
NicePassword.new(:length => 6, :words => 5, :digits => 2)
|
100
|
+
end.should raise_error(NicePassword::FormatError)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should raise FormatError if avg word length is too small" do
|
104
|
+
lambda do
|
105
|
+
NicePassword.new(:length => 6, :words => 2, :digits => 2)
|
106
|
+
end.should raise_error(NicePassword::FormatError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should raise FormatError if avg word length is too large" do
|
110
|
+
lambda do
|
111
|
+
NicePassword.new(:length => 26, :words => 2, :digits => 2)
|
112
|
+
end.should raise_error(NicePassword::FormatError)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should raise DictionaryError if language is an invalid choice' do
|
116
|
+
lambda do
|
117
|
+
NicePassword.new(:language => 'martian')
|
118
|
+
end.should raise_error(NicePassword::DictionaryError)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should raise DictionaryError if dictionary is invalid' do
|
122
|
+
lambda do
|
123
|
+
NicePassword.new(:dictionary => {})
|
124
|
+
end.should raise_error(NicePassword::DictionaryError)
|
125
|
+
lambda do
|
126
|
+
NicePassword.new(:dictionary => [])
|
127
|
+
end.should raise_error(NicePassword::DictionaryError)
|
128
|
+
lambda do
|
129
|
+
NicePassword.new(:dictionary => [nil, nil, nil])
|
130
|
+
end.should raise_error(NicePassword::DictionaryError)
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH << "." unless $LOAD_PATH.include?(".")
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "rubygems"
|
5
|
+
require "bundler"
|
6
|
+
|
7
|
+
if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.5")
|
8
|
+
raise RuntimeError, "Your bundler version is too old." +
|
9
|
+
"Run `gem install bundler` to upgrade."
|
10
|
+
end
|
11
|
+
|
12
|
+
# Set up load paths for all bundled gems
|
13
|
+
Bundler.setup
|
14
|
+
rescue Bundler::GemNotFound
|
15
|
+
raise RuntimeError, "Bundler couldn't find some gems." +
|
16
|
+
"Did you run `bundle install`?"
|
17
|
+
end
|
18
|
+
|
19
|
+
Bundler.require(:default, :test)
|
20
|
+
|
21
|
+
|
22
|
+
require File.expand_path('../../lib/nice_password', __FILE__)
|
23
|
+
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nice_password
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Kevin Skoglund
|
14
|
+
- Matthew Bergman
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-07-06 00:00:00 -04:00
|
20
|
+
default_executable:
|
21
|
+
dependencies: []
|
22
|
+
|
23
|
+
description: NicePassword creates easy-to-remember, reasonably-secure passwords by mixing dictionary words and random numbers.
|
24
|
+
email: kevin@novafabrica.com
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files:
|
30
|
+
- README.rdoc
|
31
|
+
files:
|
32
|
+
- Gemfile
|
33
|
+
- MIT-LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- VERSION
|
37
|
+
- lib/nice_password.rb
|
38
|
+
- lib/nice_password/core_ext.rb
|
39
|
+
- lib/nice_password/dictionaries/en.yml
|
40
|
+
- lib/nice_password/dictionaries/es.yml
|
41
|
+
- lib/nice_password/dictionaries/fr.yml
|
42
|
+
- lib/nice_password/errors.rb
|
43
|
+
- lib/nice_password/nice_password.rb
|
44
|
+
- rails/init.rb
|
45
|
+
- spec/nice_password/nice_password_spec.rb
|
46
|
+
- spec/spec_helper.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/novafabrica/nice_password
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --charset=UTF-8
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.7
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Generate easy to read/remember passwords
|
81
|
+
test_files:
|
82
|
+
- spec/nice_password/nice_password_spec.rb
|
83
|
+
- spec/spec_helper.rb
|