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.
@@ -0,0 +1,9 @@
1
+ module NicePassword #:nodoc:
2
+
3
+ class FormatError < StandardError #:nodoc:
4
+ end
5
+
6
+ class DictionaryError < StandardError #:nodoc:
7
+ end
8
+
9
+ end
@@ -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
@@ -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