sifar 0.1.0 → 0.2.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/.document +2 -2
- data/{spec/spec.opts → .rspec} +0 -0
- data/{LICENSE → LICENSE.txt} +1 -1
- data/README.rdoc +78 -7
- data/Rakefile +21 -24
- data/VERSION +1 -1
- data/lib/sifar.rb +120 -158
- data/sifar.gemspec +29 -25
- data/spec/sifar_spec.rb +104 -3
- data/spec/spec_helper.rb +19 -5
- metadata +55 -26
- data/.gitignore +0 -24
- data/init.rb +0 -1
data/.document
CHANGED
data/{spec/spec.opts → .rspec}
RENAMED
File without changes
|
data/{LICENSE → LICENSE.txt}
RENAMED
data/README.rdoc
CHANGED
@@ -2,32 +2,103 @@
|
|
2
2
|
|
3
3
|
Sifar can be used to check for strong passwords. Apart from the standard tests for length and homogeneity, it can check passwords that sound and spell similar to a given word.
|
4
4
|
|
5
|
-
Sifar can also generate passwords that satisfy the
|
5
|
+
Sifar can also generate passwords that satisfy the given criteria.
|
6
|
+
|
7
|
+
== Upgrade Notes
|
8
|
+
|
9
|
+
Version 0.2.0 is a complete rewrite. Please check usage before upgrading.
|
6
10
|
|
7
11
|
== Installation
|
8
12
|
|
9
13
|
=== Requirements
|
10
14
|
|
11
|
-
You need
|
15
|
+
You need the text gem. This is installed automatically if Sifar is installed as a gem. More information on text can be found at https://github.com/threedaymonk/text.
|
12
16
|
|
13
|
-
|
17
|
+
Sifar has been tested only on *nix systems.
|
14
18
|
|
15
|
-
=== As a gem
|
19
|
+
=== As a gem
|
16
20
|
|
17
21
|
Install the gem:
|
18
22
|
|
19
23
|
> sudo gem install sifar
|
20
24
|
|
21
|
-
To use the Sifar gem
|
25
|
+
To use the Sifar gem with bundler, add the following line in your Gemfile:
|
22
26
|
|
23
|
-
|
27
|
+
gem 'sifar'
|
24
28
|
|
25
29
|
=== As a Rails plugin
|
26
30
|
|
27
31
|
To add Sifar as a plugin in a Rails application, run the following command from your application root:
|
28
32
|
|
29
33
|
> ./script/plugin install git@github.com:meshbrain/sifar.git
|
34
|
+
|
35
|
+
== Usage
|
36
|
+
=== Validate
|
37
|
+
|
38
|
+
require 'sifar'
|
39
|
+
checker = Sifar.new ...
|
40
|
+
p checker.errors unless checker.check(word)
|
41
|
+
|
42
|
+
=== Generate
|
43
|
+
|
44
|
+
require 'sifar'
|
45
|
+
checker = Sifar.new ...
|
46
|
+
password = checker.generate
|
47
|
+
|
48
|
+
=== Checks
|
49
|
+
|
50
|
+
Checks can be used separately or in combination.
|
51
|
+
|
52
|
+
==== Minimum length
|
53
|
+
|
54
|
+
Password should be at least x characters long.
|
55
|
+
|
56
|
+
checker = Sifar.new :minimum_length => 8
|
57
|
+
checker.check('pword') # => false
|
58
|
+
checker.check('password') # => true
|
59
|
+
checker.check('longpassword') # => true
|
60
|
+
|
61
|
+
==== Heterogeneous passwords
|
62
|
+
|
63
|
+
Password should contain a mix of digits, uppercase and lowercase characters.
|
64
|
+
|
65
|
+
checker = Sifar.new :heterogeneous => true
|
66
|
+
checker.check('password') # => false
|
67
|
+
checker.check('Pa55w0rD') # => true
|
68
|
+
|
69
|
+
==== Dictionary passwords
|
70
|
+
|
71
|
+
Password should not contain any word from a given file.
|
72
|
+
|
73
|
+
checker = Sifar.new :dictionary => '/path/to/dictionary'
|
74
|
+
checker.check('indictionary') # => false
|
75
|
+
|
76
|
+
==== Reject specific characters
|
77
|
+
|
78
|
+
Password should not contain any character from a given set.
|
79
|
+
|
80
|
+
checker = Sifar.new :character_blacklist => %w(& % $)
|
81
|
+
checker.check('pass%word') # => false
|
82
|
+
|
83
|
+
==== Passwords that spell similar to a given name
|
84
|
+
|
85
|
+
Levenshtein distance of two words should be more than a given threshold. :name is mandatory.
|
86
|
+
|
87
|
+
checker = Sifar.new :similarity => 1, :name => 'shoeman'
|
88
|
+
checker.check('showman') # => false
|
89
|
+
checker.check('anothershowman') # => false
|
90
|
+
checker.check('password') # => true
|
91
|
+
|
92
|
+
==== Words that sound similar to a given name
|
93
|
+
|
94
|
+
Phonetic similarity of two words should be more than a given threshold. :name is mandatory.
|
95
|
+
|
96
|
+
checker = Sifar.new :phonetic_similarity => 1, :name => 'suman'
|
97
|
+
checker.check('showman') # => false
|
98
|
+
checker.check('password') # => true
|
99
|
+
|
100
|
+
NOTE: This check uses metaphone; and might not work as expected in all languages.
|
30
101
|
|
31
102
|
== Copyright
|
32
103
|
|
33
|
-
Copyright (c)
|
104
|
+
Copyright (c) 2011 Suman Debnath. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,37 +1,34 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gem|
|
6
|
+
gem.name = 'sifar'
|
7
|
+
gem.homepage = 'http://github.com/meshbrain/sifar'
|
8
|
+
gem.license = 'MIT'
|
9
|
+
gem.summary = %Q{A library to generate strong passwords and check password strength.}
|
10
|
+
gem.description = %Q{Sifar can be used to check for strong passwords. Apart from the standard tests for length and homogeneity, it can check passwords that sound and spell similar to a given word. Sifar can also generate passwords that satisfy the same criteria.}
|
11
|
+
gem.email = 'contact@meshbrain.com'
|
12
|
+
gem.authors = ['Suman Debnath']
|
13
|
+
gem.add_dependency 'text', '~> 0.2.0'
|
14
|
+
gem.add_development_dependency 'rspec', '~> 2.3.0'
|
15
|
+
#gem.add_development_dependency 'bundler', '~> 1.0.0'
|
16
|
+
gem.add_development_dependency 'jeweler', '~> 1.5.2'
|
17
|
+
gem.add_development_dependency 'rcov', '>= 0'
|
19
18
|
end
|
19
|
+
Jeweler::RubygemsDotOrgTasks.new
|
20
20
|
|
21
|
-
require '
|
22
|
-
|
23
|
-
|
24
|
-
spec.
|
21
|
+
require 'rspec/core'
|
22
|
+
require 'rspec/core/rake_task'
|
23
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
24
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
spec.libs << 'lib' << 'spec'
|
27
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
29
28
|
spec.pattern = 'spec/**/*_spec.rb'
|
30
29
|
spec.rcov = true
|
31
30
|
end
|
32
31
|
|
33
|
-
task :spec => :check_dependencies
|
34
|
-
|
35
32
|
task :default => :spec
|
36
33
|
|
37
34
|
require 'rake/rdoctask'
|
@@ -39,7 +36,7 @@ Rake::RDocTask.new do |rdoc|
|
|
39
36
|
version = File.exist?('VERSION') ? File.read('VERSION') : ''
|
40
37
|
|
41
38
|
rdoc.rdoc_dir = 'rdoc'
|
42
|
-
rdoc.title =
|
39
|
+
rdoc.title = 'sifar #{version}'
|
43
40
|
rdoc.rdoc_files.include('README*')
|
44
41
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
42
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/sifar.rb
CHANGED
@@ -1,170 +1,132 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
|
2
|
+
require 'text'
|
3
|
+
require 'digest'
|
3
4
|
|
4
5
|
class Sifar
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
6
|
+
attr_accessor :errors
|
7
|
+
|
8
|
+
def initialize(options = {}, error_messages = {})
|
9
|
+
set_opt options, error_messages
|
10
|
+
end
|
11
|
+
|
12
|
+
def check(word)
|
13
|
+
@errors = []
|
14
|
+
|
15
|
+
check_length(word) unless @options[:minimum_length].nil?
|
16
|
+
check_dictionary(word) unless @options[:dictionary].nil?
|
17
|
+
check_character_blacklist(word) unless @options[:character_blacklist].nil?
|
18
|
+
check_phonetic(word, @options[:name]) unless @options[:phonetic_similarity].nil?
|
19
|
+
check_similarity(word, @options[:name]) unless @options[:similarity].nil?
|
20
|
+
|
21
|
+
check_heterogeneity(word) if true == @options[:heterogeneous]
|
22
|
+
|
23
|
+
1 > @errors.length
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate
|
27
|
+
n = 0
|
28
|
+
begin
|
29
|
+
n += 1
|
30
|
+
word = create
|
31
|
+
end until true == check(word)
|
32
|
+
word
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def create
|
37
|
+
max = @options[:minimum_length] || 8
|
38
|
+
seed = (0..max).map{|i| (32 + rand(126-32)).chr}.join
|
39
|
+
Digest::SHA1.hexdigest(seed)[0..max]
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_length(word)
|
43
|
+
@errors << @error_messages[:length] if word.length < @options[:minimum_length].to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
def check_heterogeneity(word)
|
47
|
+
num_chars = word.length
|
48
|
+
num = {}
|
49
|
+
num[:upper] = word.gsub(/[^[:upper:]]/, '').length
|
50
|
+
num[:lower] = word.gsub(/[^[:lower:]]/, '').length
|
51
|
+
num[:digit] = word.gsub(/[^[:digit:]]/, '').length
|
52
|
+
num[:special] = num_chars - (num[:upper] + num[:lower] + num[:digit])
|
53
|
+
|
54
|
+
max = 4 > num_chars ? 3 : num_chars - 3
|
55
|
+
num.each_value do |value|
|
56
|
+
if value > max
|
57
|
+
@errors << @error_messages[:heter]
|
58
|
+
return false
|
59
|
+
end
|
18
60
|
end
|
19
61
|
|
20
|
-
|
21
|
-
|
22
|
-
@errors = []
|
62
|
+
true
|
63
|
+
end
|
23
64
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def generate_password(name = '', options = {}, error_messages = {})
|
31
|
-
self.set_opt options, error_messages
|
32
|
-
end
|
33
|
-
|
34
|
-
def check_strength(password)
|
35
|
-
@options[:password_check_type].each(''){|option| self.send(@checks[option], password)}
|
36
|
-
end
|
37
|
-
|
38
|
-
def check_against_name(password, name)
|
39
|
-
@options[:name_check_type].each(''){|option| self.send(@checks[option], password, name)}
|
40
|
-
end
|
41
|
-
|
42
|
-
def check_length(word)
|
43
|
-
@errors << @error_messages[:length] if word.length < @options[:minimum_length]
|
65
|
+
def check_dictionary(word)
|
66
|
+
if File.readable? @options[:dictionary]
|
67
|
+
@errors << @error_messages[:dict] if find_word_in_file(word, @options[:dictionary])
|
68
|
+
else
|
69
|
+
@errors << @error_messages[:config_blackword]
|
44
70
|
end
|
71
|
+
end
|
45
72
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
num[:lower] = word.gsub(/[^[:lower:]]/, '').length
|
51
|
-
num[:digit] = word.gsub(/[^[:digit:]]/, '').length
|
52
|
-
num[:special] = num_chars - (num[:upper] + num[:lower] + num[:digit])
|
53
|
-
|
54
|
-
max = 4 > num_chars ? 3 : num_chars - 3
|
55
|
-
num.each do |type, value|
|
56
|
-
if value > max
|
57
|
-
@errors << @error_messages[:heter]
|
58
|
-
return false
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
true
|
63
|
-
end
|
64
|
-
|
65
|
-
def check_dictionary(word)
|
66
|
-
if File.readable? @options[:dictionary]
|
67
|
-
@errors << @error_messages[:dict] if find_word_in_file(word, @options[:dictionary])
|
68
|
-
else
|
69
|
-
@errors << @error_messages[:config_blackword]
|
70
|
-
end
|
71
|
-
end
|
73
|
+
def check_character_blacklist(word)
|
74
|
+
pattern = /#{@options[:character_blacklist].map{|c| Regexp.escape c}.join('|')}/i
|
75
|
+
@errors << @error_messages[:config_blackword] unless pattern.match(word).nil?
|
76
|
+
end
|
72
77
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
@errors << @error_messages[:config_dict]
|
78
|
-
end
|
78
|
+
def check_phonetic(word, name)
|
79
|
+
unless @options[:phonetic_similarity] < Text::Levenshtein.distance(Text::Metaphone.metaphone(word), Text::Metaphone.metaphone(name))
|
80
|
+
@errors << @error_messages[:phonetic]
|
81
|
+
return false
|
79
82
|
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
return false
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
else
|
93
|
-
@errors << @error_messages[:config_blackchar]
|
94
|
-
return false
|
95
|
-
end
|
96
|
-
true
|
97
|
-
end
|
98
|
-
|
99
|
-
def check_phonetic(password, name)
|
100
|
-
require 'english/levenshtein'
|
101
|
-
require 'english/soundex'
|
102
|
-
unless 2 < English::Levenshtein.distance(password.soundex, name.soundex)
|
103
|
-
@errors << @error_messages[:phonetic]
|
104
|
-
return false
|
105
|
-
end
|
106
|
-
true
|
107
|
-
end
|
108
|
-
|
109
|
-
def check_similarity(password, name)
|
110
|
-
require 'english/similarity'
|
111
|
-
if 0.8 < password.similarity(name)
|
112
|
-
@errors << @error_messages[:similar]
|
113
|
-
return false
|
114
|
-
end
|
115
|
-
true
|
116
|
-
end
|
117
|
-
|
118
|
-
def check_string(password, name)
|
119
|
-
longer, shorter = password.size > name.size ? [password, name] : [name, password]
|
120
|
-
if longer.include?(shorter)
|
121
|
-
@errors << @error_messages[:similar]
|
122
|
-
return false
|
123
|
-
end
|
124
|
-
true
|
125
|
-
end
|
126
|
-
|
127
|
-
def find_word_in_file(word, file)
|
128
|
-
t = []
|
129
|
-
IO.popen("#{@options[:grep_path]} -ixs '#{word}' #{file}") {|io|
|
130
|
-
r = io.readlines
|
131
|
-
t << r unless r.empty?
|
132
|
-
}
|
133
|
-
not t.empty?
|
134
|
-
end
|
135
|
-
|
136
|
-
def set_opt(options = {}, error_messages = {})
|
137
|
-
@options = {
|
138
|
-
:minimum_length => 8,
|
139
|
-
:password_check_type => 'LD',
|
140
|
-
:name_check_type => 'S',
|
141
|
-
:dictionary => '',
|
142
|
-
:word_blacklist => '',
|
143
|
-
:char_blacklist => '',
|
144
|
-
:error_msg_hash => {},
|
145
|
-
:umlaut_hash => {},
|
146
|
-
:grep_path => '/bin/grep'
|
147
|
-
}.merge options
|
148
|
-
@options[:minimum_length] = 1 > @options[:minimum_length] ? 8 : @options[:minimum_length]
|
149
|
-
@options[:password_check_type] = @options[:password_check_type].gsub(/[^DLHWC]/, '').split('').sort.join
|
150
|
-
@options[:name_check_type] = @options[:name_check_type].gsub(/[^PTS]/, '').split('').sort.join
|
151
|
-
|
152
|
-
raise ArgumentError, 'grep not found' unless (File.stat(@options[:grep_path]).executable? rescue false)
|
153
|
-
|
154
|
-
@error_messages = {
|
155
|
-
:length => 'The password is too short!',
|
156
|
-
:similar => 'The password is too similar to the given name!',
|
157
|
-
:phonetic => 'The password sounds too similar to the given name!',
|
158
|
-
:dict => 'The password is based upon a dictionary word!',
|
159
|
-
:heter => 'The password is too homogeneous!',
|
160
|
-
:blackword => 'The password is based upon a blacklsited word!',
|
161
|
-
:blackchar => 'The password contains a blacklisted character!',
|
162
|
-
:similar_looking_chars => 'The password contains similar looking characters!',
|
163
|
-
:config_blackchar => 'Could not load a valid blacklisted character file!',
|
164
|
-
:config_blackword => 'Could not load a valid blacklisted word file!',
|
165
|
-
:config_dict => 'Could not load a valid dictionary file!',
|
166
|
-
:config_heter => 'Conflict in heterogeneity check and password generation configuration!',
|
167
|
-
:config_length => 'Generated password length cannot be less then specified minimum length!'
|
168
|
-
}.merge error_messages
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
def check_similarity(word, name)
|
87
|
+
longer, shorter = [word, name].sort{|x,y| y.size <=> x.size}
|
88
|
+
unless !longer.include?(shorter) and @options[:similarity] < Text::Levenshtein.distance(longer, shorter)
|
89
|
+
@errors << @error_messages[:similar]
|
90
|
+
return false
|
169
91
|
end
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def find_word_in_file(word, file)
|
96
|
+
t = []
|
97
|
+
IO.popen("#{@options[:grep_path]} -ixs '#{word}' #{file}") {|io|
|
98
|
+
r = io.readlines
|
99
|
+
t << r unless r.empty?
|
100
|
+
}
|
101
|
+
not t.empty?
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_opt(options = {}, error_messages = {})
|
105
|
+
@options = {
|
106
|
+
:password_check_type => 'LD',
|
107
|
+
:name_check_type => 'S',
|
108
|
+
:error_msg_hash => {},
|
109
|
+
:umlaut_hash => {},
|
110
|
+
:grep_path => `which grep`.chomp
|
111
|
+
}.merge options
|
112
|
+
|
113
|
+
raise ArgumentError, 'grep not found' unless (File.stat(@options[:grep_path]).executable? rescue false)
|
114
|
+
|
115
|
+
@error_messages = {
|
116
|
+
:length => 'The password is too short!',
|
117
|
+
:similar => 'The password is too similar to the given name!',
|
118
|
+
:phonetic => 'The password sounds too similar to the given name!',
|
119
|
+
:dict => 'The password is based upon a dictionary word!',
|
120
|
+
:heter => 'The password is too homogeneous!',
|
121
|
+
:blackword => 'The password is based upon a blacklsited word!',
|
122
|
+
:blackchar => 'The password contains a blacklisted character!',
|
123
|
+
:similar_looking_chars => 'The password contains similar looking characters!',
|
124
|
+
:config_blackchar => 'Could not load a valid blacklisted character file!',
|
125
|
+
:config_blackword => 'Could not load a valid blacklisted word file!',
|
126
|
+
:config_dict => 'Could not load a valid dictionary file!',
|
127
|
+
:config_heter => 'Conflict in heterogeneity check and password generation configuration!',
|
128
|
+
:config_length => 'Generated password length cannot be less then specified minimum length!'
|
129
|
+
}.merge error_messages
|
130
|
+
@errors ||= []
|
131
|
+
end
|
170
132
|
end
|
data/sifar.gemspec
CHANGED
@@ -1,43 +1,41 @@
|
|
1
1
|
# Generated by jeweler
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{sifar}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Suman Debnath"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2011-01-28}
|
13
13
|
s.description = %q{Sifar can be used to check for strong passwords. Apart from the standard tests for length and homogeneity, it can check passwords that sound and spell similar to a given word. Sifar can also generate passwords that satisfy the same criteria.}
|
14
14
|
s.email = %q{contact@meshbrain.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
|
-
"LICENSE",
|
17
|
-
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
"spec/spec.opts",
|
31
|
-
"spec/spec_helper.rb"
|
21
|
+
".rspec",
|
22
|
+
"LICENSE.txt",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/sifar.rb",
|
27
|
+
"sifar.gemspec",
|
28
|
+
"spec/sifar_spec.rb",
|
29
|
+
"spec/spec_helper.rb"
|
32
30
|
]
|
33
31
|
s.homepage = %q{http://github.com/meshbrain/sifar}
|
34
|
-
s.
|
32
|
+
s.licenses = ["MIT"]
|
35
33
|
s.require_paths = ["lib"]
|
36
34
|
s.rubygems_version = %q{1.3.7}
|
37
35
|
s.summary = %q{A library to generate strong passwords and check password strength.}
|
38
36
|
s.test_files = [
|
39
|
-
"spec/
|
40
|
-
|
37
|
+
"spec/sifar_spec.rb",
|
38
|
+
"spec/spec_helper.rb"
|
41
39
|
]
|
42
40
|
|
43
41
|
if s.respond_to? :specification_version then
|
@@ -45,15 +43,21 @@ Gem::Specification.new do |s|
|
|
45
43
|
s.specification_version = 3
|
46
44
|
|
47
45
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
|
-
s.
|
49
|
-
s.
|
46
|
+
s.add_runtime_dependency(%q<text>, ["~> 0.2.0"])
|
47
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
48
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
49
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
50
50
|
else
|
51
|
-
s.add_dependency(%q<
|
52
|
-
s.add_dependency(%q<
|
51
|
+
s.add_dependency(%q<text>, ["~> 0.2.0"])
|
52
|
+
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
53
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
54
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
53
55
|
end
|
54
56
|
else
|
55
|
-
s.add_dependency(%q<
|
56
|
-
s.add_dependency(%q<
|
57
|
+
s.add_dependency(%q<text>, ["~> 0.2.0"])
|
58
|
+
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
59
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
60
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
57
61
|
end
|
58
62
|
end
|
59
63
|
|
data/spec/sifar_spec.rb
CHANGED
@@ -1,7 +1,108 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
describe
|
4
|
-
it
|
5
|
-
|
3
|
+
describe Sifar, 'when testing for minimum length' do
|
4
|
+
it 'accepts words equal or bigger than given length' do
|
5
|
+
checker = Sifar.new :minimum_length => 8
|
6
|
+
|
7
|
+
checker.check(password(8)).should == true
|
8
|
+
checker.check(password(10)).should == true
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'rejects words smaller than given length' do
|
12
|
+
checker = Sifar.new :minimum_length => 8
|
13
|
+
|
14
|
+
checker.check(password(7)).should == false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe Sifar, 'when testing for heterogeneity' do
|
19
|
+
it 'accepts heterogeneous words' do
|
20
|
+
checker = Sifar.new :heterogeneous => true
|
21
|
+
|
22
|
+
checker.check('aA34zX9j').should == true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'rejects homogeneous words' do
|
26
|
+
checker = Sifar.new :heterogeneous => true
|
27
|
+
|
28
|
+
checker.check(password(10)).should == false
|
6
29
|
end
|
7
30
|
end
|
31
|
+
|
32
|
+
describe Sifar, 'when testing for dictionary words' do
|
33
|
+
it 'rejects words in dictionary' do
|
34
|
+
word = password(10)
|
35
|
+
|
36
|
+
temporary_file(word) do |dictionary|
|
37
|
+
checker = Sifar.new :dictionary => dictionary
|
38
|
+
checker.check(word).should == false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'accepts words not in dictionary' do
|
43
|
+
temporary_file('indictionary') do |dictionary|
|
44
|
+
checker = Sifar.new :dictionary => dictionary
|
45
|
+
checker.check('notindictionary').should == true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe Sifar, 'when testing for blacklisted characters' do
|
51
|
+
it 'rejects words with blacklisted characters' do
|
52
|
+
checker = Sifar.new :character_blacklist => %w(& % $)
|
53
|
+
checker.check('blacklisted&character').should == false
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'accepts words without blacklisted characters' do
|
57
|
+
checker = Sifar.new :character_blacklist => %w(& % $)
|
58
|
+
checker.check('blacklistedcharacter').should == true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe Sifar, 'when testing for similar spelling' do
|
63
|
+
it 'rejects words with similarity equal or more than the given threshold' do
|
64
|
+
checker = Sifar.new :similarity => 1, :name => 'shoeman'
|
65
|
+
checker.check('showman').should == false
|
66
|
+
checker.check('anothershoeman').should == false
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'accepts words with similarity less than the given threshold' do
|
70
|
+
checker = Sifar.new :similarity => 1, :name => 'password'
|
71
|
+
checker.check('pa55word').should == true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe Sifar, 'when testing for similar pronounciation' do
|
76
|
+
it 'rejects words with phonetic similarity equal or more than the given threshold' do
|
77
|
+
checker = Sifar.new :phonetic_similarity => 1, :name => 'suman'
|
78
|
+
checker.check('showman').should == false
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'accepts words with phonetic similarity less than the given threshold' do
|
82
|
+
checker = Sifar.new :phonetic_similarity => 1, :name => 'password'
|
83
|
+
checker.check('firstpassword').should == true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe Sifar, 'when generating passwords' do
|
88
|
+
it 'passes all checks' do
|
89
|
+
temporary_file('indictionary') do |dictionary|
|
90
|
+
checks = {
|
91
|
+
:minimum_length => 8,
|
92
|
+
:dictionary => dictionary,
|
93
|
+
:character_blacklist => %w(& % $),
|
94
|
+
:phonetic_similarity => 2,
|
95
|
+
:similarity => 2
|
96
|
+
}
|
97
|
+
|
98
|
+
c = checks.keys
|
99
|
+
(1..c.size).each do |n|
|
100
|
+
c.combination(n).each do |options|
|
101
|
+
checker = Sifar.new checks.reject{|key, value| !options.include?(key)}.merge({:name => 'suman'})
|
102
|
+
password = checker.generate
|
103
|
+
checker.check(password).should == true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
3
4
|
require 'sifar'
|
4
|
-
require '
|
5
|
-
require 'spec/autorun'
|
5
|
+
require 'tmpdir'
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
module SifarHelperMethods
|
8
|
+
def password(num)
|
9
|
+
(0...num).map{ ('a'..'z').to_a[rand(26)] }.join
|
10
|
+
end
|
11
|
+
|
12
|
+
def temporary_file(text, &block)
|
13
|
+
temp_file = File.join(Dir.tmpdir, 'sifar_temporary_file')
|
14
|
+
open(temp_file, 'w'){ |f| f.write text }
|
15
|
+
block.call temp_file
|
16
|
+
File.delete temp_file
|
17
|
+
end
|
9
18
|
end
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
config.before(:each) {}
|
22
|
+
include SifarHelperMethods
|
23
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sifar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Suman Debnath
|
@@ -15,40 +15,71 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-01-28 00:00:00 +05:30
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: text
|
23
23
|
prerelease: false
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 23
|
30
30
|
segments:
|
31
|
-
-
|
31
|
+
- 0
|
32
32
|
- 2
|
33
|
-
|
34
|
-
|
33
|
+
- 0
|
34
|
+
version: 0.2.0
|
35
|
+
type: :runtime
|
35
36
|
version_requirements: *id001
|
36
37
|
- !ruby/object:Gem::Dependency
|
37
|
-
name:
|
38
|
+
name: rspec
|
38
39
|
prerelease: false
|
39
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
40
41
|
none: false
|
41
42
|
requirements:
|
42
|
-
- -
|
43
|
+
- - ~>
|
43
44
|
- !ruby/object:Gem::Version
|
44
|
-
hash:
|
45
|
+
hash: 3
|
45
46
|
segments:
|
47
|
+
- 2
|
48
|
+
- 3
|
46
49
|
- 0
|
47
|
-
|
48
|
-
|
49
|
-
version: 0.4.0
|
50
|
-
type: :runtime
|
50
|
+
version: 2.3.0
|
51
|
+
type: :development
|
51
52
|
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: jeweler
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 7
|
62
|
+
segments:
|
63
|
+
- 1
|
64
|
+
- 5
|
65
|
+
- 2
|
66
|
+
version: 1.5.2
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rcov
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
52
83
|
description: Sifar can be used to check for strong passwords. Apart from the standard tests for length and homogeneity, it can check passwords that sound and spell similar to a given word. Sifar can also generate passwords that satisfy the same criteria.
|
53
84
|
email: contact@meshbrain.com
|
54
85
|
executables: []
|
@@ -56,28 +87,26 @@ executables: []
|
|
56
87
|
extensions: []
|
57
88
|
|
58
89
|
extra_rdoc_files:
|
59
|
-
- LICENSE
|
90
|
+
- LICENSE.txt
|
60
91
|
- README.rdoc
|
61
92
|
files:
|
62
93
|
- .document
|
63
|
-
- .
|
64
|
-
- LICENSE
|
94
|
+
- .rspec
|
95
|
+
- LICENSE.txt
|
65
96
|
- README.rdoc
|
66
97
|
- Rakefile
|
67
98
|
- VERSION
|
68
|
-
- init.rb
|
69
99
|
- lib/sifar.rb
|
70
100
|
- sifar.gemspec
|
71
101
|
- spec/sifar_spec.rb
|
72
|
-
- spec/spec.opts
|
73
102
|
- spec/spec_helper.rb
|
74
103
|
has_rdoc: true
|
75
104
|
homepage: http://github.com/meshbrain/sifar
|
76
|
-
licenses:
|
77
|
-
|
105
|
+
licenses:
|
106
|
+
- MIT
|
78
107
|
post_install_message:
|
79
|
-
rdoc_options:
|
80
|
-
|
108
|
+
rdoc_options: []
|
109
|
+
|
81
110
|
require_paths:
|
82
111
|
- lib
|
83
112
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -106,5 +135,5 @@ signing_key:
|
|
106
135
|
specification_version: 3
|
107
136
|
summary: A library to generate strong passwords and check password strength.
|
108
137
|
test_files:
|
109
|
-
- spec/spec_helper.rb
|
110
138
|
- spec/sifar_spec.rb
|
139
|
+
- spec/spec_helper.rb
|
data/.gitignore
DELETED
data/init.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'sifar'
|