nice_password 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|