dionysus 0.2.1 → 0.3.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/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