dionysus 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  gem.email = "warlickt@operissystems.com"
10
10
  gem.homepage = "http://github.com/tekwiz/dionysus"
11
11
  gem.authors = ["Travis D. Warlick, Jr."]
12
- gem.add_dependency 'activesupport', '3.0.0.beta'
12
+ gem.add_dependency 'activesupport', '~> 3.0.0'
13
13
  gem.add_development_dependency "rspec", ">= 1.2.9"
14
14
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
15
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.3.0
data/dionysus.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dionysus}
8
- s.version = "0.2.1"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Travis D. Warlick, Jr."]
12
- s.date = %q{2010-03-21}
12
+ s.date = %q{2010-04-02}
13
13
  s.email = %q{warlickt@operissystems.com}
14
14
  s.extra_rdoc_files = [
15
15
  "LICENSE",
@@ -25,11 +25,15 @@ Gem::Specification.new do |s|
25
25
  "dionysus.gemspec",
26
26
  "lib/dionysus.rb",
27
27
  "lib/dionysus/configuration.rb",
28
+ "lib/dionysus/digest.rb",
28
29
  "lib/dionysus/security.rb",
30
+ "lib/dionysus/security/password_salt.rb",
29
31
  "lib/dionysus/security/string.rb",
30
32
  "lib/dionysus/string.rb",
31
33
  "spec/configuration_spec.rb",
34
+ "spec/digest_spec.rb",
32
35
  "spec/dionysus_spec.rb",
36
+ "spec/password_salt_spec.rb",
33
37
  "spec/spec.opts",
34
38
  "spec/spec_helper.rb",
35
39
  "spec/string_security_spec.rb",
@@ -41,11 +45,13 @@ Gem::Specification.new do |s|
41
45
  s.rubygems_version = %q{1.3.6}
42
46
  s.summary = %q{A helpful set of utility classes, generators, and command-line tools.}
43
47
  s.test_files = [
44
- "spec/string_spec.rb",
48
+ "spec/configuration_spec.rb",
49
+ "spec/digest_spec.rb",
50
+ "spec/dionysus_spec.rb",
51
+ "spec/password_salt_spec.rb",
45
52
  "spec/spec_helper.rb",
46
- "spec/configuration_spec.rb",
47
53
  "spec/string_security_spec.rb",
48
- "spec/dionysus_spec.rb"
54
+ "spec/string_spec.rb"
49
55
  ]
50
56
 
51
57
  if s.respond_to? :specification_version then
@@ -53,14 +59,14 @@ Gem::Specification.new do |s|
53
59
  s.specification_version = 3
54
60
 
55
61
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
- s.add_runtime_dependency(%q<activesupport>, ["= 3.0.0.beta"])
62
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.0"])
57
63
  s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
58
64
  else
59
- s.add_dependency(%q<activesupport>, ["= 3.0.0.beta"])
65
+ s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
60
66
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
61
67
  end
62
68
  else
63
- s.add_dependency(%q<activesupport>, ["= 3.0.0.beta"])
69
+ s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
64
70
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
65
71
  end
66
72
  end
data/lib/dionysus.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'rubygems'
2
- gem 'activesupport', '3.0.0.beta'
2
+ gem 'activesupport', '~> 3.0.0'
3
3
  require 'active_support/all'
4
4
 
5
5
  ##
@@ -0,0 +1,105 @@
1
+ require 'dionysus'
2
+ require 'digest'
3
+
4
+ ##
5
+ # Convenience methods for the Digest module.
6
+ #
7
+ # require 'dionysus/digest'
8
+ #
9
+ # The <tt>Digest::DEFAULT_DIGESTS</tt> are automatically registered, if they
10
+ # exist. You can register additional digests with Digest.register_digest.
11
+ # The given class must give the digest with the <tt>digest(string)</tt>.
12
+ #
13
+ # TODO add digest detection -- by length and by proc on the digests hash
14
+ module Digest
15
+ DEFAULT_DIGESTS = [:md5, :sha1, :sha2, :sha256, :sha384, :sha512]
16
+ @digests = {}
17
+
18
+ ##
19
+ # Register a digest. Raises an error if the interpreted class doesn't exist.
20
+ # It will interpret the klass as <tt>Digest::SYM</tt> if it's <tt>nil</tt>,
21
+ # and it will run the <tt>digest</tt> method on the klass to determine the
22
+ # digests bit length if bits is <tt>nil</tt>.
23
+ #
24
+ # This will register <tt>:my_digest</tt> and automatically determine the bit
25
+ # length by executing the class's <tt>digest</tt> method on the string
26
+ # <tt>'1'</tt>:
27
+ #
28
+ # Digest.register_digest!( :my_digest, :klass => MyDigestClass )
29
+ #
30
+ # Options:
31
+ # [klass] The digest class (also can be an arbitrary object). Default:
32
+ # <tt>Digest::#{sym.to_s.upcase}</tt>
33
+ # [bit_length] The bit length of the digest. Default: calculated by
34
+ # running the digest on the string <tt>'1'</tt>.
35
+ # [method] The calculation method for the digest. Default:
36
+ # <tt>:digest</tt>
37
+ def self.register_digest!( sym, options = {} )
38
+ options = options.with_indifferent_access
39
+ options[:method] ||= :digest
40
+ options[:klass] ||= "Digest::#{sym.to_s.upcase}".constantize
41
+ options[:bit_length] ||= options[:klass].send(options[:method], '1').length * 8
42
+ @digests[sym.to_sym] = options
43
+ end
44
+
45
+ ##
46
+ # Register a digest. Returns nil if an error occurs.
47
+ def self.register_digest( sym, options = {} )
48
+ self.register_digest!(sym, options)
49
+ rescue LoadError
50
+ nil
51
+ end
52
+
53
+ ##
54
+ # The hash of registered digests.
55
+ def self.digests
56
+ @digests
57
+ end
58
+
59
+ ##
60
+ # The available digests.
61
+ def self.available_digests
62
+ self.digests.keys
63
+ end
64
+
65
+ ##
66
+ # The lengths of the registered digests in the given encoding.
67
+ def self.digest_lengths( encoding = :binary )
68
+ if encoding.is_a?(Symbol)
69
+ bits_per_char = case encoding
70
+ when :binary then 8
71
+ when :base64 then 6
72
+ when :hex, :hexidecimal then 4
73
+ when :bit then 1
74
+ else raise ArgumentError, "Invalid encoding"
75
+ end
76
+ elsif encoding.is_a?(Integer) and encoding > 0
77
+ bits_per_char = encoding
78
+ else
79
+ raise ArgumentError, "Invalid encoding"
80
+ end
81
+
82
+ Hash[ self.digests.collect { |dig, info| [dig, info[:bit_length] / bits_per_char] } ]
83
+ end
84
+
85
+ ##
86
+ # Calculate the given digest of the given string.
87
+ #
88
+ # Examples:
89
+ #
90
+ # Digest.digest(:sha512, 'foobar') #=> binary digest
91
+ # Digest.digest(Digest::SHA512, 'foobar') #=> binary digest
92
+ def self.digest( sym_or_klass, str )
93
+ if sym_or_klass.is_a?(Class)
94
+ sym_or_klass
95
+ else
96
+ Digest.const_get(sym_or_klass.to_s.upcase)
97
+ end.digest(str)
98
+ end
99
+ end
100
+
101
+ # Register some default digests
102
+ Digest::DEFAULT_DIGESTS.each do |dig|
103
+ Digest.register_digest(dig)
104
+ end
105
+
@@ -1 +1,3 @@
1
+ require 'dionysus'
1
2
  require 'dionysus/security/string'
3
+ require 'dionysus/security/password_salt'
@@ -0,0 +1,139 @@
1
+ require 'dionysus'
2
+ require 'active_support/secure_random'
3
+
4
+ ##
5
+ # Encapsulates a password salt.
6
+ #
7
+ # Examples
8
+ #
9
+ # salt = PasswordSalt.new
10
+ # #=> generates random salt of 8 characters
11
+ # salt.salt_password('foobar') #=> 'foobar0PD0oKAj'
12
+ #
13
+ # salt = PasswordSalt.new(:length => 20)
14
+ # #=> generates random salt of 20 characters
15
+ # salt.salt_password('foobar') #=> 'foobar7qvFpfi+3jGVFaA5TaE7'
16
+ #
17
+ # salt = PasswordSalt.new('ABCDEFG', :beginning)
18
+ # #=> generates salt 'abcdef' with beginning placement
19
+ # salt.salt_password('foobar')
20
+ # #=> 'ABCDEFGfoobar'
21
+ #
22
+ # salt = PasswordSalt.new(:split, :length => 10)
23
+ # #=> generates random salt of 10 characters with split placement
24
+ # salt.salt_password('foobar')
25
+ # #=> 'WyVjpfoobarjGYXJ'
26
+ class PasswordSalt
27
+ PLACEMENTS = [:before, :after, :split]
28
+ DEFAULT_LENGTH = 8
29
+ DEFAULT_PLACEMENT = :after
30
+
31
+ @@secure_random = SecureRandom
32
+ cattr_accessor :secure_random
33
+
34
+ attr_accessor :string
35
+ attr_reader :placement
36
+
37
+ ##
38
+ # Generate a salt string of the given length (Default: 8) with the base64
39
+ # character set. Optionally, you may pass the format as <tt>:binary</tt> to generate
40
+ # a binary salt.
41
+ def self.generate( length = DEFAULT_LENGTH, format = :base64 )
42
+ if length < 0
43
+ raise ArgumentError, "Invalid length: #{length}"
44
+ end
45
+
46
+ case format.to_sym
47
+ when :base64
48
+ self.secure_random.base64(length)[0...length]
49
+ when :binary
50
+ self.secure_random.random_bytes(length)
51
+ else
52
+ raise ArgumentError, "Invalid format: #{format}"
53
+ end
54
+ end
55
+
56
+ ##
57
+ # Initialize a new PasswordSalt
58
+ #
59
+ # If you pass the first argument as a string, the string will be set as
60
+ # the salt:
61
+ # PasswordSalt.new('ABCDEFG')
62
+ #
63
+ # If the first argument is <tt>:new</tt> or is an option hash, a random salt
64
+ # will be generated:
65
+ # PasswordSalt.new(:new)
66
+ # PasswordSalt.new(:length => 20)
67
+ # PasswordSalt.new
68
+ #
69
+ # If the first or second argument is a Symbol (other than <tt>:new</tt>), it
70
+ # will be interpreted as the placement:
71
+ # PasswordSalt.new(:before)
72
+ # PasswordSalt.new(:new, :before)
73
+ # PasswordSalt.new('ABCDEFG', :before)
74
+ #
75
+ # You may always pass in an options hash as the last argument.
76
+ # [length] Length of salt to be generated.
77
+ # Default: 8
78
+ def initialize( *args )
79
+ options = args.extract_options!
80
+ if args[0].nil? or args[0] == :new or args[0].is_a?(String)
81
+ self.string = args[0] || :new
82
+ end
83
+
84
+ if args[0].is_a?(Symbol) and args[0] != :new
85
+ self.placement = args[0]
86
+ elsif args[1].is_a?(Symbol)
87
+ self.placement = args[1]
88
+ else
89
+ self.placement = DEFAULT_PLACEMENT
90
+ end
91
+
92
+ if self.string == :new
93
+ self.string = self.class.generate(options[:length] || DEFAULT_LENGTH)
94
+ end
95
+ end
96
+
97
+ ##
98
+ # Returns the given password, but salted.
99
+ def salt_password( password )
100
+ case self.placement
101
+ when :after
102
+ password.to_s + string
103
+ when :before
104
+ string + password.to_s
105
+ when :split
106
+ string[0...(string.length/2).floor] +
107
+ password.to_s +
108
+ string[(string.length/2).floor...string.length]
109
+ else
110
+ raise "Invalid salt placement: #{self.placement}"
111
+ end
112
+ end
113
+
114
+ ##
115
+ # Set the salt's placement
116
+ #
117
+ # [after] Place the salt after the password.
118
+ # [before] Place the salt before the password.
119
+ # [split] Place half of the salt before and half after the password. For
120
+ # salts of odd length, the shorter half will be in front.
121
+ def placement=( sym )
122
+ unless PLACEMENTS.include?(sym.to_sym)
123
+ raise ArgumentError, "Invalid salt placement: #{sym}"
124
+ end
125
+ @placement = sym.to_sym
126
+ end
127
+
128
+ ##
129
+ # Returns the salt string.
130
+ def to_s
131
+ self.string
132
+ end
133
+
134
+ ##
135
+ # Returns +true+ if the given salt is equivilent to this salt, +false+ otherwise
136
+ def eql?( salt )
137
+ self.to_s.eql?(salt.to_s) && self.placement.eql?(salt.placement)
138
+ end
139
+ end
@@ -1,9 +1,8 @@
1
- require 'digest/sha1'
2
- require 'digest/sha2'
3
- require 'digest/md5'
4
-
5
1
  require 'dionysus'
2
+ require 'dionysus/digest'
6
3
  require 'dionysus/string'
4
+ require 'dionysus/security/password_salt'
5
+
7
6
  ##
8
7
  # Adds String hashing and salting convenience methods.
9
8
  #
@@ -20,23 +19,32 @@ class String
20
19
  ##
21
20
  # Generate a random, binary salt with the given length (default 16)
22
21
  def self.salt( length = 16 )
23
- length.times.to_a.collect {|val| rand(255).chr}.join[0...length]
22
+ PasswordSalt.generate(length, :binary).to_s
23
+ end
24
+
25
+ ##
26
+ # Sanitize the String from memory. This is non-reversible. Runs 7 passes
27
+ # by default.
28
+ #
29
+ def sanitize( passes = 7 )
30
+ passes.times do
31
+ (0...self.length).each { |i| self[i] = rand(256) }
32
+ end
33
+ (0...self.length).each { |i| self[i] = 0 }
34
+ self.delete!("\000")
24
35
  end
25
36
 
26
37
  ##
27
38
  # Generate the given digest hash.
28
39
  #
29
40
  # Options:
30
- # [salt] Salt to append to the string.
31
- # Default: ''
32
- # [encoding] Encoding for the hash: <tt>:binary</tt>, <tt>:hex</tt>, <tt>:hexidecimal</tt>, <tt>:base64</tt>
41
+ # [salt] Salt to append to the string. (This may be a String or a
42
+ # PasswordSalt object) Default: ''
43
+ # [encoding] Encoding for the hash: <tt>:binary</tt>, <tt>:hex</tt>,
44
+ # <tt>:hexidecimal</tt>, <tt>:base64</tt>
33
45
  # Default: <tt>:base64</tt>
34
46
  def digest( klass, options = {} )
35
- unless klass.is_a?(Class)
36
- klass = Digest.const_get(klass.to_s.upcase)
37
- end
38
-
39
- digest = klass.digest(_salted(options[:salt]))
47
+ digest = Digest.digest(klass, _salted(options[:salt]))
40
48
  _encode(digest, options[:encoding] || :base64)
41
49
  end
42
50
 
@@ -52,12 +60,30 @@ class String
52
60
  super
53
61
  end
54
62
 
63
+ ##
64
+ # Detect the digest of the string
65
+ def detect_digest( encoding )
66
+ self.class.available_digests.each do |dig|
67
+ if ''.digest(dig, :encoding => encoding).length == self.length
68
+ return dig
69
+ end
70
+ end
71
+ raise "Unknown digest."
72
+ end
73
+
55
74
  private
56
75
 
57
76
  ##
58
- # Salt the string by appending it to the end.
77
+ # Salt the string by appending it to the end or utilizing the
78
+ # PasswordSalt's salt_password method
59
79
  def _salted( salt )
60
- self + (salt || '')
80
+ return self if salt.blank?
81
+
82
+ unless salt.is_a?(PasswordSalt)
83
+ salt = PasswordSalt.new(salt)
84
+ end
85
+
86
+ salt.salt_password(self)
61
87
  end
62
88
 
63
89
  ##
@@ -6,6 +6,9 @@ require 'active_support/base64'
6
6
  #
7
7
  # require 'dionysus/string'
8
8
  class String
9
+ HEX_REGEXP = /^[a-f0-9]+$/
10
+ BASE64_REGEXP = /^[A-Za-z0-9\+\/\=]+$/
11
+
9
12
  ##
10
13
  # Encode the Base64 (without newlines)
11
14
  def encode64s() Base64.encode64s(self); end
@@ -27,4 +30,18 @@ class String
27
30
  # Decode from hexidecimal
28
31
  def decode_hexidecimal() [self].pack('H*'); end
29
32
  alias_method :decode_hex, :decode_hexidecimal
33
+
34
+ ##
35
+ # Detect the encoding of the string
36
+ def detect_encoding
37
+ return nil if blank?
38
+
39
+ if match(HEX_REGEXP)
40
+ :hex
41
+ elsif match(BASE64_REGEXP)
42
+ :base64
43
+ else
44
+ :binary
45
+ end
46
+ end
30
47
  end
@@ -0,0 +1,125 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'dionysus/digest'
4
+
5
+ describe Digest do
6
+ it 'should have the default available digests' do
7
+ Digest.available_digests.length.should == Digest::DEFAULT_DIGESTS.length
8
+ Digest::DEFAULT_DIGESTS.each do |dig|
9
+ Digest.available_digests.should include(dig)
10
+ end
11
+ end
12
+
13
+ describe 'registration' do
14
+ before(:all) do
15
+ Digest.digests.clear
16
+ end
17
+
18
+ after(:each) do
19
+ Digest.digests.clear
20
+ end
21
+
22
+ after(:all) do
23
+ Digest::DEFAULT_DIGESTS.each do |sym|
24
+ Digest.register_digest!(sym)
25
+ end
26
+ end
27
+
28
+ it 'should register an available digest' do
29
+ Digest.register_digest(:md5).should be_a(Hash)
30
+ Digest.digests[:md5].should be_a(Hash)
31
+ end
32
+
33
+ it 'should register an available digest with bang' do
34
+ Digest.register_digest!(:md5).should be_a(Hash)
35
+ Digest.digests[:md5].should be_a(Hash)
36
+ end
37
+
38
+ it 'should automatically interpret the class' do
39
+ Digest.register_digest(:md5)
40
+ Digest.digests[:md5][:klass].should == Digest::MD5
41
+ end
42
+
43
+ it 'should register a digest with a class' do
44
+ Digest.register_digest(:md5, :klass => Digest::SHA256)
45
+ Digest.digests[:md5][:klass].should == Digest::SHA256
46
+ Digest.digests[:md5][:bit_length].should == 256
47
+ end
48
+
49
+ it 'should automatically interpret the bit length' do
50
+ Digest.register_digest(:md5)
51
+ Digest.digests[:md5][:bit_length].should == 128
52
+ end
53
+
54
+ it 'should register a digest with a class and a bit length' do
55
+ Digest.register_digest(:md5, :klass => Digest::SHA256, :bit_length => 1)
56
+ Digest.digests[:md5][:klass].should == Digest::SHA256
57
+ Digest.digests[:md5][:bit_length].should == 1
58
+ end
59
+
60
+ it 'should quietly reject a digest if it does not exist' do
61
+ Digest.register_digest(:foobar).should == nil
62
+ Digest.digests.should == {}
63
+ end
64
+
65
+ it 'should raise an error if a digest does not exist' do
66
+ lambda { Digest.register_digest!(:foobar) }.should
67
+ raise_error(LoadError, 'library not found for class Digest::FOOBAR -- digest/foobar')
68
+ end
69
+
70
+ it 'should take an arbitrary object that responds to digest' do
71
+ my_digest = mock('my_digest')
72
+ my_digest.should_receive(:digest).with('1').and_return('12345')
73
+ Digest.register_digest(:my_digest, :klass => my_digest)
74
+ Digest.digests[:my_digest][:bit_length].should == 5 * 8
75
+ Digest.digests[:my_digest][:klass].should === my_digest
76
+ Digest.digests[:my_digest][:method].should == :digest
77
+ end
78
+
79
+ it 'should take an arbitrary method' do
80
+ my_digest = mock('my_digest')
81
+ my_digest.should_receive(:do_digest).with('1').and_return('12345')
82
+ Digest.register_digest(:my_digest, :klass => my_digest, :method => :do_digest)
83
+ Digest.digests[:my_digest][:bit_length].should == 5 * 8
84
+ Digest.digests[:my_digest][:klass].should === my_digest
85
+ Digest.digests[:my_digest][:method].should == :do_digest
86
+ end
87
+ end
88
+
89
+ describe 'digest lengths' do
90
+ { :md5 => 128, :sha1 => 160, :sha2 => 256,
91
+ :sha256 => 256, :sha384 => 384, :sha512 => 512 }.each do |dig, bits|
92
+ it "should have binary length for #{dig}" do
93
+ Digest.digest_lengths[dig].should == bits / 8
94
+ Digest.digest_lengths(:binary)[dig].should == bits / 8
95
+ Digest.digest_lengths(8)[dig].should == bits / 8
96
+ end
97
+
98
+ it "should have hex length for #{dig}" do
99
+ Digest.digest_lengths(:hex)[dig].should == bits / 4
100
+ Digest.digest_lengths(:hexidecimal)[dig].should == bits / 4
101
+ Digest.digest_lengths(4)[dig].should == bits / 4
102
+ end
103
+
104
+ it "should have base64 length for #{dig}" do
105
+ Digest.digest_lengths(:base64)[dig].should == bits / 6
106
+ Digest.digest_lengths(6)[dig].should == bits / 6
107
+ end
108
+
109
+ it "should have bit length for #{dig}" do
110
+ Digest.digest_lengths(:bit)[dig].should == bits
111
+ Digest.digest_lengths(1)[dig].should == bits
112
+ end
113
+
114
+ it "should have arbitrary length for #{dig}" do
115
+ Digest.digest_lengths(7)[dig].should == bits / 7
116
+ end
117
+ end
118
+
119
+ it 'should reject bit length requests for < 0' do
120
+ lambda { Digest.digest_lengths(0).should raise_error(ArgumentError, 'Invalid encoding') }
121
+ lambda { Digest.digest_lengths(-1).should raise_error(ArgumentError, 'Invalid encoding') }
122
+ lambda { Digest.digest_lengths(-100).should raise_error(ArgumentError, 'Invalid encoding') }
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,159 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'dionysus/security/password_salt'
4
+ describe PasswordSalt do
5
+ it "should not allow placements other than before, after, and split" do
6
+ salt = PasswordSalt.new('ABCDEFG')
7
+ [:before, :after, :split].each do |p|
8
+ salt.placement = p
9
+ salt.placement.should == p
10
+ salt.placement = p.to_s
11
+ salt.placement.should == p
12
+ end
13
+
14
+ lambda { salt.placement = :wowsers }.should raise_error(ArgumentError, "Invalid salt placement: wowsers")
15
+ end
16
+
17
+ it "should should return the salt string with to_s" do
18
+ salt = PasswordSalt.new('ABCDEFG')
19
+ salt.to_s.should == 'ABCDEFG'
20
+ salt = PasswordSalt.new('ABCDEFG', :after)
21
+ salt.to_s.should == 'ABCDEFG'
22
+ salt = PasswordSalt.new()
23
+ salt.to_s.should == salt.string
24
+ end
25
+
26
+ it "should compare equivalance on both string and placement" do
27
+ salt1 = PasswordSalt.new('ABCDEFG')
28
+ salt2 = PasswordSalt.new('ABCDEFG')
29
+ salt1.eql?(salt2).should be_true
30
+ salt2.eql?(salt1).should be_true
31
+
32
+ salt1 = PasswordSalt.new('ABCDEFG', :before)
33
+ salt2 = PasswordSalt.new('ABCDEFG', :before)
34
+ salt1.eql?(salt2).should be_true
35
+ salt2.eql?(salt1).should be_true
36
+
37
+ salt1 = PasswordSalt.new('ABCDEFG', :before)
38
+ salt2 = PasswordSalt.new('ABCDEFG', :split)
39
+ salt1.eql?(salt2).should_not be_true
40
+ salt2.eql?(salt1).should_not be_true
41
+ end
42
+
43
+ describe "initializer" do
44
+ it "should take the first string arg as the literal salt" do
45
+ salt = PasswordSalt.new('ABCDEFG')
46
+ salt.string.should == 'ABCDEFG'
47
+ end
48
+
49
+ it "should take the the second symbold arg as the placement" do
50
+ salt = PasswordSalt.new('ABCDEFG', :before)
51
+ salt.placement.should == :before
52
+ end
53
+
54
+ it "should generate a new salt of 8 characters and :after placement with no arguments" do
55
+ s = PasswordSalt.new
56
+ s.placement.should == :after
57
+ s.string.length.should == 8
58
+ end
59
+
60
+ it "should take a length option" do
61
+ s = PasswordSalt.new(:length => 20)
62
+ s.placement.should == :after
63
+ s.string.length.should == 20
64
+ end
65
+ end
66
+
67
+ describe "salt generation" do
68
+ it "should generate 8 characters by default" do
69
+ salt = PasswordSalt.generate
70
+ salt.length.should == 8
71
+ end
72
+
73
+ it "should generate n characters when told to" do
74
+ (0..20).each do |n|
75
+ PasswordSalt.generate(n).length.should == n
76
+ end
77
+ end
78
+
79
+ it "should raise an error with negative length" do
80
+ lambda { PasswordSalt.generate(-1) }.should raise_error(ArgumentError, "Invalid length: -1")
81
+ end
82
+
83
+ it "should generate ascii characters only by default" do
84
+ 100.times do
85
+ salt = PasswordSalt.generate(100)
86
+ salt.should match(/\A[A-Za-z0-9\+\/]{100}\Z/)
87
+ end
88
+ end
89
+
90
+ it "should generate binary characters" do
91
+ 100.times do
92
+ salt = PasswordSalt.generate(100, :binary)
93
+ salt.should_not match(/\A[A-Za-z0-9\+\/]{100}\Z/)
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "after placement" do
99
+ before(:each) do
100
+ @password = 'foobar'
101
+ @salt = "ABCDEFG"
102
+ end
103
+
104
+ it "should put the salt after the password by default" do
105
+ salt = PasswordSalt.new(@salt)
106
+ salt.salt_password(@password).should == @password+@salt
107
+ end
108
+
109
+ it "should put the salt after the password" do
110
+ salt = PasswordSalt.new(@salt, :after)
111
+ salt.salt_password(@password).should == @password+@salt
112
+ end
113
+ end
114
+
115
+ describe "before placement" do
116
+ before(:each) do
117
+ @password = 'foobar'
118
+ @salt = "ABCDEFG"
119
+ end
120
+
121
+ it "should put the salt before the password" do
122
+ salt = PasswordSalt.new(@salt, :before)
123
+ salt.salt_password(@password).should == @salt+@password
124
+ salt = PasswordSalt.new(@salt)
125
+ salt.placement = :before
126
+ salt.salt_password(@password).should == @salt+@password
127
+ end
128
+ end
129
+
130
+ describe "split placement with even number of characters" do
131
+ before(:each) do
132
+ @password = 'foobar'
133
+ @salt = "ABCDEFGH"
134
+ end
135
+
136
+ it "should put half the salt before and half the salt after the password" do
137
+ salt = PasswordSalt.new(@salt, :split)
138
+ salt.salt_password(@password).should == 'ABCD'+@password+'EFGH'
139
+ salt = PasswordSalt.new(@salt)
140
+ salt.placement = :split
141
+ salt.salt_password(@password).should == 'ABCD'+@password+'EFGH'
142
+ end
143
+ end
144
+
145
+ describe "split placement with odd number of characters" do
146
+ before(:each) do
147
+ @password = 'foobar'
148
+ @salt = "ABCDEFG"
149
+ end
150
+
151
+ it "should put half the salt before and half the salt after the password" do
152
+ salt = PasswordSalt.new(@salt, :split)
153
+ salt.salt_password(@password).should == 'ABC'+@password+'DEFG'
154
+ salt = PasswordSalt.new(@salt)
155
+ salt.placement = :split
156
+ salt.salt_password(@password).should == 'ABC'+@password+'DEFG'
157
+ end
158
+ end
159
+ end
@@ -11,6 +11,24 @@ describe String do
11
11
  String.salt(1).length.should == 1
12
12
  end
13
13
 
14
+ it "should sanitize the string" do
15
+ str = 'abcdefg'
16
+ str.sanitize
17
+ str.should == ''
18
+ end
19
+
20
+ it "should sanitize the string with 0 passes" do
21
+ str = 'abcdefg'
22
+ str.sanitize(0)
23
+ str.should == ''
24
+ end
25
+
26
+ it "should sanitize the string with 100 passes" do
27
+ str = 'abcdefg'
28
+ str.sanitize(100)
29
+ str.should == ''
30
+ end
31
+
14
32
  it "should calculate sha512 in base64" do
15
33
  digest = "KHErTuI/5urCF3vi6yTDxvAHBydW1CN3zVfBWDP/ItbmgeVi5IFq47ew0hPHzyK3AuMDh+Z9MBBlf3/mACgVMQ=="
16
34
  "abcde\240z405".digest(:sha512).should == digest
data/spec/string_spec.rb CHANGED
@@ -24,4 +24,23 @@ describe String do
24
24
  '6162636465a07a343035'.decode_hex.should == "abcde\240z405"
25
25
  '6162636465a07a343035'.decode_hexidecimal.should == "abcde\240z405"
26
26
  end
27
- end
27
+
28
+ describe 'encoding detection' do
29
+ it 'should return nil for blank' do
30
+ ''.detect_encoding.should be_nil
31
+ end
32
+
33
+ it 'should match hex' do
34
+ '6162636465a07a343035'.detect_encoding.should == :hex
35
+ end
36
+
37
+ it 'should match base64' do
38
+ "YWJjZGWgejQwNQ==".detect_encoding.should == :base64
39
+ "YWJjZGWgejQwNQ==\n".detect_encoding.should == :base64
40
+ end
41
+
42
+ it 'should default to binary' do
43
+ "abcde\240z405".detect_encoding.should == :binary
44
+ end
45
+ end
46
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 1
9
- version: 0.2.1
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Travis D. Warlick, Jr.
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-21 00:00:00 -05:00
17
+ date: 2010-04-02 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -22,14 +22,13 @@ dependencies:
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - "="
25
+ - - ~>
26
26
  - !ruby/object:Gem::Version
27
27
  segments:
28
28
  - 3
29
29
  - 0
30
30
  - 0
31
- - beta
32
- version: 3.0.0.beta
31
+ version: 3.0.0
33
32
  type: :runtime
34
33
  version_requirements: *id001
35
34
  - !ruby/object:Gem::Dependency
@@ -65,11 +64,15 @@ files:
65
64
  - dionysus.gemspec
66
65
  - lib/dionysus.rb
67
66
  - lib/dionysus/configuration.rb
67
+ - lib/dionysus/digest.rb
68
68
  - lib/dionysus/security.rb
69
+ - lib/dionysus/security/password_salt.rb
69
70
  - lib/dionysus/security/string.rb
70
71
  - lib/dionysus/string.rb
71
72
  - spec/configuration_spec.rb
73
+ - spec/digest_spec.rb
72
74
  - spec/dionysus_spec.rb
75
+ - spec/password_salt_spec.rb
73
76
  - spec/spec.opts
74
77
  - spec/spec_helper.rb
75
78
  - spec/string_security_spec.rb
@@ -105,8 +108,10 @@ signing_key:
105
108
  specification_version: 3
106
109
  summary: A helpful set of utility classes, generators, and command-line tools.
107
110
  test_files:
108
- - spec/string_spec.rb
109
- - spec/spec_helper.rb
110
111
  - spec/configuration_spec.rb
111
- - spec/string_security_spec.rb
112
+ - spec/digest_spec.rb
112
113
  - spec/dionysus_spec.rb
114
+ - spec/password_salt_spec.rb
115
+ - spec/spec_helper.rb
116
+ - spec/string_security_spec.rb
117
+ - spec/string_spec.rb