nice_password 1.0.0

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