encrypted_attributes 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,12 @@
1
1
  == master
2
2
 
3
+ == 0.4.1 / 2010-03-07
4
+
5
+ * Release gems via rake-gemcutter instead of rubyforge
6
+ * Allow an application-wide default be set for :embed_salt in SHA ciphers
7
+ * Add support for embedding the salt using the various SHA ciphers
8
+ * Allow multiple attributes to be encrypted in a single call
9
+
3
10
  == 0.4.0 / 2009-05-02
4
11
 
5
12
  * Replace dynamic :salt option with :embed_salt option and utilizing the #encrypts block
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2009 Aaron Pfeifer
1
+ Copyright (c) 2006-2010 Aaron Pfeifer
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -1,11 +1,12 @@
1
+ require 'rubygems'
2
+ require 'rake'
1
3
  require 'rake/testtask'
2
4
  require 'rake/rdoctask'
3
5
  require 'rake/gempackagetask'
4
- require 'rake/contrib/sshpublisher'
5
6
 
6
7
  spec = Gem::Specification.new do |s|
7
8
  s.name = 'encrypted_attributes'
8
- s.version = '0.4.0'
9
+ s.version = '0.4.1'
9
10
  s.platform = Gem::Platform::RUBY
10
11
  s.summary = 'Adds support for automatically encrypting ActiveRecord attributes'
11
12
  s.description = s.summary
@@ -14,7 +15,7 @@ spec = Gem::Specification.new do |s|
14
15
  s.require_path = 'lib'
15
16
  s.has_rdoc = true
16
17
  s.test_files = Dir['test/**/*_test.rb']
17
- s.add_dependency 'encrypted_strings', '>= 0.3.0'
18
+ s.add_dependency 'encrypted_strings', '>= 0.3.3'
18
19
 
19
20
  s.author = 'Aaron Pfeifer'
20
21
  s.email = 'aaron@pluginaweek.org'
@@ -51,23 +52,30 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
51
52
  rdoc.rdoc_dir = 'rdoc'
52
53
  rdoc.title = spec.name
53
54
  rdoc.template = '../rdoc_template.rb'
54
- rdoc.options << '--line-numbers'
55
+ rdoc.options << '--line-numbers' << '--inline-source'
55
56
  rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb')
56
57
  end
57
-
58
+
59
+ desc 'Generate a gemspec file.'
60
+ task :gemspec do
61
+ File.open("#{spec.name}.gemspec", 'w') do |f|
62
+ f.write spec.to_ruby
63
+ end
64
+ end
65
+
58
66
  Rake::GemPackageTask.new(spec) do |p|
59
67
  p.gem_spec = spec
60
- p.need_tar = true
61
- p.need_zip = true
62
68
  end
63
69
 
64
70
  desc 'Publish the beta gem.'
65
71
  task :pgem => [:package] do
72
+ require 'rake/contrib/sshpublisher'
66
73
  Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
67
74
  end
68
75
 
69
76
  desc 'Publish the API documentation.'
70
77
  task :pdoc => [:rdoc] do
78
+ require 'rake/contrib/sshpublisher'
71
79
  Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
72
80
  end
73
81
 
@@ -76,15 +84,8 @@ task :publish => [:pgem, :pdoc, :release]
76
84
 
77
85
  desc 'Publish the release files to RubyForge.'
78
86
  task :release => [:gem, :package] do
79
- require 'rubyforge'
80
-
81
- ruby_forge = RubyForge.new.configure
82
- ruby_forge.login
87
+ require 'rake/gemcutter'
83
88
 
84
- %w(gem tgz zip).each do |ext|
85
- file = "pkg/#{spec.name}-#{spec.version}.#{ext}"
86
- puts "Releasing #{File.basename(file)}..."
87
-
88
- ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
89
- end
89
+ Rake::Gemcutter::Tasks.new(spec)
90
+ Rake::Task['gem:push'].invoke
90
91
  end
@@ -103,45 +103,51 @@ module EncryptedAttributes
103
103
  # In the above example, the SHA encryption's <tt>salt</tt> is configured
104
104
  # dynamically based on the user's login and the time at which it was
105
105
  # encrypted. This helps improve the security of the user's password.
106
- def encrypts(attr_name, options = {}, &config)
107
- config ||= options
108
- attr_name = attr_name.to_s
109
- to_attr_name = (options.delete(:to) || attr_name).to_s
106
+ def encrypts(*attr_names, &config)
107
+ base_options = attr_names.last.is_a?(Hash) ? attr_names.pop : {}
110
108
 
111
- # Figure out what cipher is being configured for the attribute
112
- mode = options.delete(:mode) || :sha
113
- class_name = "#{mode.to_s.classify}Cipher"
114
- if EncryptedAttributes.const_defined?(class_name)
115
- cipher_class = EncryptedAttributes.const_get(class_name)
116
- else
117
- cipher_class = EncryptedStrings.const_get(class_name)
118
- end
119
-
120
- # Define encryption hooks
121
- define_callbacks("before_encrypt_#{attr_name}", "after_encrypt_#{attr_name}")
122
- send("before_encrypt_#{attr_name}", options.delete(:before)) if options.include?(:before)
123
- send("after_encrypt_#{attr_name}", options.delete(:after)) if options.include?(:after)
124
-
125
- # Set the encrypted value on the configured callback
126
- callback = options.delete(:on) || :before_validation
127
- send(callback, :if => options.delete(:if), :unless => options.delete(:unless)) do |record|
128
- record.send(:write_encrypted_attribute, attr_name, to_attr_name, cipher_class, config)
129
- true
130
- end
131
-
132
- # Define virtual source attribute
133
- if attr_name != to_attr_name && !column_names.include?(attr_name)
134
- attr_reader attr_name unless method_defined?(attr_name)
135
- attr_writer attr_name unless method_defined?("#{attr_name}=")
136
- end
137
-
138
- # Define the reader when reading the encrypted attribute from the database
139
- define_method(to_attr_name) do
140
- read_encrypted_attribute(to_attr_name, cipher_class, config)
141
- end
142
-
143
- unless included_modules.include?(EncryptedAttributes::InstanceMethods)
144
- include EncryptedAttributes::InstanceMethods
109
+ attr_names.each do |attr_name|
110
+ options = base_options.dup
111
+ attr_name = attr_name.to_s
112
+ to_attr_name = (options.delete(:to) || attr_name).to_s
113
+
114
+ # Figure out what cipher is being configured for the attribute
115
+ mode = options.delete(:mode) || :sha
116
+ class_name = "#{mode.to_s.classify}Cipher"
117
+ if EncryptedAttributes.const_defined?(class_name)
118
+ cipher_class = EncryptedAttributes.const_get(class_name)
119
+ else
120
+ cipher_class = EncryptedStrings.const_get(class_name)
121
+ end
122
+
123
+ # Define encryption hooks
124
+ define_callbacks("before_encrypt_#{attr_name}", "after_encrypt_#{attr_name}")
125
+ send("before_encrypt_#{attr_name}", options.delete(:before)) if options.include?(:before)
126
+ send("after_encrypt_#{attr_name}", options.delete(:after)) if options.include?(:after)
127
+
128
+ # Set the encrypted value on the configured callback
129
+ callback = options.delete(:on) || :before_validation
130
+
131
+ # Create a callback method to execute on the callback event
132
+ send(callback, :if => options.delete(:if), :unless => options.delete(:unless)) do |record|
133
+ record.send(:write_encrypted_attribute, attr_name, to_attr_name, cipher_class, config || options)
134
+ true
135
+ end
136
+
137
+ # Define virtual source attribute
138
+ if attr_name != to_attr_name && !column_names.include?(attr_name)
139
+ attr_reader attr_name unless method_defined?(attr_name)
140
+ attr_writer attr_name unless method_defined?("#{attr_name}=")
141
+ end
142
+
143
+ # Define the reader when reading the encrypted attribute from the database
144
+ define_method(to_attr_name) do
145
+ read_encrypted_attribute(to_attr_name, cipher_class, config || options)
146
+ end
147
+
148
+ unless included_modules.include?(EncryptedAttributes::InstanceMethods)
149
+ include EncryptedAttributes::InstanceMethods
150
+ end
145
151
  end
146
152
  end
147
153
  end
@@ -1,6 +1,24 @@
1
1
  module EncryptedAttributes
2
2
  # Adds support for embedding salts in the encrypted value
3
3
  class ShaCipher < EncryptedStrings::ShaCipher
4
+ class << self
5
+ # Whether to embed the salt by default
6
+ attr_accessor :default_embed_salt
7
+ end
8
+
9
+ # Set defaults
10
+ @default_embed_salt = false
11
+
12
+ # Tracks the lengths generated for each hashing algorithm
13
+ @@algorithm_lengths = {
14
+ 'MD5' => 32,
15
+ 'SHA1' => 40,
16
+ 'SHA2' => 64,
17
+ 'SHA256' => 64,
18
+ 'SHA384' => 96,
19
+ 'SHA512' => 128
20
+ }
21
+
4
22
  # Encrypts a string using a Secure Hash Algorithm (SHA), specifically SHA-1.
5
23
  #
6
24
  # Configuration options:
@@ -10,9 +28,10 @@ module EncryptedAttributes
10
28
  # encrypted value. Default is false. This is useful for storing both
11
29
  # the salt and the encrypted value in the same attribute.
12
30
  def initialize(value, options = {}) #:nodoc:
13
- if @embed_salt = options.delete(:embed_salt)
31
+ if @embed_salt = options.delete(:embed_salt) || self.class.default_embed_salt
14
32
  # The salt is at the end of the value
15
- salt = value[40..-1]
33
+ algorithm = (options[:algorithm] || EncryptedStrings::ShaCipher.default_algorithm).upcase
34
+ salt = value[@@algorithm_lengths[algorithm]..-1]
16
35
  options[:salt] = salt unless salt.blank?
17
36
  end
18
37
 
@@ -1,7 +1,7 @@
1
1
  class CreateUsers < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :users do |t|
4
- t.string :login, :password, :crypted_password, :salt
4
+ t.string :login, :password, :crypted_password, :salt, :password_reminder
5
5
  end
6
6
  end
7
7
 
@@ -61,6 +61,82 @@ class EncryptedAttributesTest < ActiveSupport::TestCase
61
61
  end
62
62
  end
63
63
 
64
+ class EncryptedAttributesWithMultipleAttributesTest < ActiveSupport::TestCase
65
+ def setup
66
+ User.encrypts :password, :password_reminder
67
+ end
68
+
69
+ def test_should_both_using_sha
70
+ user = create_user(:login => 'admin', :password => 'secret', :password_reminder => 'shhh')
71
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
72
+ assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.password_reminder}"
73
+ end
74
+
75
+ def test_should_encrypt_on_invalid_model
76
+ user = new_user(:login => nil, :password => 'secret', :password_reminder => 'shhh')
77
+ assert !user.valid?
78
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
79
+ assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.password_reminder}"
80
+ end
81
+
82
+ def test_should_not_encrypt_if_attributes_are_nil
83
+ user = create_user(:login => 'admin', :password => nil, :password_reminder => nil)
84
+ assert_nil user.password
85
+ assert_nil user.password_reminder
86
+ end
87
+
88
+ def test_should_not_encrypt_if_attributes_are_blank
89
+ user = create_user(:login => 'admin', :password => '', :password_reminder => '')
90
+ assert_equal '', user.password
91
+ assert_equal '', user.password_reminder
92
+ end
93
+
94
+ def test_should_not_encrypt_any_if_already_encrypted
95
+ user = create_user(:login => 'admin', :password => 'secret'.encrypt, :password_reminder => 'shhh'.encrypt)
96
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
97
+ assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.password_reminder}"
98
+ end
99
+
100
+ def test_should_return_encrypted_attributes_for_saved_record
101
+ user = create_user(:login => 'admin', :password => 'secret', :password_reminder => 'shhh')
102
+ user = User.find(user.id)
103
+ assert user.password.encrypted?
104
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
105
+
106
+ assert user.password_reminder.encrypted?
107
+ assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.password_reminder}"
108
+ end
109
+
110
+ def test_should_not_encrypt_attributes_if_updating_without_any_changes
111
+ user = create_user(:login => 'admin', :password => 'secret', :password_reminder => 'shhh')
112
+ user.login = 'Administrator'
113
+ user.save!
114
+ assert user.password.encrypted?
115
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
116
+
117
+ assert user.password_reminder.encrypted?
118
+ assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.password_reminder}"
119
+ end
120
+
121
+ def test_should_encrypt_attributes_if_updating_with_changes
122
+ user = create_user(:login => 'admin', :password => 'secret', :password_reminder => 'shhh')
123
+ user.password = 'shhh'
124
+ user.password_reminder = 'secret'
125
+ user.save!
126
+ assert user.password.encrypted?
127
+ assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.password}"
128
+
129
+ assert user.password_reminder.encrypted?
130
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password_reminder}"
131
+ end
132
+
133
+ def teardown
134
+ User.class_eval do
135
+ @before_validation_callbacks = nil
136
+ end
137
+ end
138
+ end
139
+
64
140
  class EncryptedAttributesWithDifferentTargetTest < ActiveSupport::TestCase
65
141
  def setup
66
142
  User.encrypts :password, :to => :crypted_password
@@ -14,6 +14,27 @@ class ShaCipherWithoutEmbeddingTest < Test::Unit::TestCase
14
14
  end
15
15
  end
16
16
 
17
+ class ShaCipherWithCustomDefaultsTest < Test::Unit::TestCase
18
+ def setup
19
+ @original_default_embed_salt = EncryptedAttributes::ShaCipher.default_embed_salt
20
+
21
+ EncryptedAttributes::ShaCipher.default_embed_salt = true
22
+ @cipher = EncryptedAttributes::ShaCipher.new('dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt')
23
+ end
24
+
25
+ def test_should_use_remaining_characters_after_password_for_salt
26
+ assert_equal 'custom_salt', @cipher.salt
27
+ end
28
+
29
+ def test_should_embed_salt_in_encrypted_string
30
+ assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', @cipher.encrypt('secret')
31
+ end
32
+
33
+ def teardown
34
+ EncryptedAttributes::ShaCipher.default_embed_salt = @original_default_embed_salt
35
+ end
36
+ end
37
+
17
38
  class ShaCipherWithNoSaltEmbeddedTest < Test::Unit::TestCase
18
39
  def setup
19
40
  @cipher = EncryptedAttributes::ShaCipher.new('dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', :embed_salt => true, :salt => 'custom_salt')
@@ -41,3 +62,35 @@ class ShaCipherWithSaltEmbeddedTest < Test::Unit::TestCase
41
62
  assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', @cipher.encrypt('secret')
42
63
  end
43
64
  end
65
+
66
+ class ShaCipherWithSaltEmbeddedAndCustomAlgorithmTest < Test::Unit::TestCase
67
+ def test_should_support_md5
68
+ cipher = EncryptedAttributes::ShaCipher.new('3b5ba11611dc1ba8bcdb0ff41aa693dcmd5_salt', :algorithm => 'md5', :embed_salt => true)
69
+ assert_equal 'md5_salt', cipher.salt
70
+ end
71
+
72
+ def test_should_support_sha1
73
+ cipher = EncryptedAttributes::ShaCipher.new('f8f70e06fed41c86c49766e6963ed4544647d638sha1_salt', :algorithm => 'sha1', :embed_salt => true)
74
+ assert_equal 'sha1_salt', cipher.salt
75
+ end
76
+
77
+ def test_should_support_sha2
78
+ cipher = EncryptedAttributes::ShaCipher.new('f2c5bd7ef9317004cca8680e7c12fa8a0b8ea9e7ebab8834ad9d818244d7c1d4sha2_salt', :algorithm => 'sha2', :embed_salt => true)
79
+ assert_equal 'sha2_salt', cipher.salt
80
+ end
81
+
82
+ def test_should_support_sha256
83
+ cipher = EncryptedAttributes::ShaCipher.new('1b75f1ebde621856c036118f296bae779548401566c982f2ec1efc089a587689sha256_salt', :algorithm => 'sha256', :embed_salt => true)
84
+ assert_equal 'sha256_salt', cipher.salt
85
+ end
86
+
87
+ def test_should_support_sha384
88
+ cipher = EncryptedAttributes::ShaCipher.new('d80570a23a1534d6a32201b01fa84b5d433a275f0aecd9cbdca2c726826e9034f90feb76fcdf49b9eafb21973962c75dsha384_salt', :algorithm => 'sha384', :embed_salt => true)
89
+ assert_equal 'sha384_salt', cipher.salt
90
+ end
91
+
92
+ def test_should_support_sha512
93
+ cipher = EncryptedAttributes::ShaCipher.new('44ffca1b3e63f0f1047f42656f43206d7d006c36d44f9f5ffde6c4679dc140f27ff4c8d310bbec902a9231a081e1c9d04236563331df29383e27037bb746df7fsha512_salt', :algorithm => 'sha512', :embed_salt => true)
94
+ assert_equal 'sha512_salt', cipher.salt
95
+ end
96
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: encrypted_attributes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Pfeifer
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-02 00:00:00 -04:00
12
+ date: 2010-03-07 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.3.0
23
+ version: 0.3.3
24
24
  version:
25
25
  description: Adds support for automatically encrypting ActiveRecord attributes
26
26
  email: aaron@pluginaweek.org
@@ -31,26 +31,17 @@ extensions: []
31
31
  extra_rdoc_files: []
32
32
 
33
33
  files:
34
- - lib/encrypted_attributes.rb
35
- - lib/encrypted_attributes
36
34
  - lib/encrypted_attributes/sha_cipher.rb
37
- - test/factory.rb
38
- - test/test_helper.rb
39
- - test/unit
35
+ - lib/encrypted_attributes.rb
40
36
  - test/unit/sha_cipher_test.rb
41
37
  - test/unit/encrypted_attributes_test.rb
42
- - test/keys
43
- - test/keys/private
44
- - test/keys/public
45
- - test/app_root
46
- - test/app_root/db
47
- - test/app_root/db/migrate
48
38
  - test/app_root/db/migrate/001_create_users.rb
49
- - test/app_root/config
50
- - test/app_root/config/environment.rb
51
- - test/app_root/app
52
- - test/app_root/app/models
53
39
  - test/app_root/app/models/user.rb
40
+ - test/app_root/config/environment.rb
41
+ - test/test_helper.rb
42
+ - test/factory.rb
43
+ - test/keys/public
44
+ - test/keys/private
54
45
  - CHANGELOG.rdoc
55
46
  - init.rb
56
47
  - LICENSE
@@ -58,6 +49,8 @@ files:
58
49
  - README.rdoc
59
50
  has_rdoc: true
60
51
  homepage: http://www.pluginaweek.org
52
+ licenses: []
53
+
61
54
  post_install_message:
62
55
  rdoc_options: []
63
56
 
@@ -78,9 +71,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
71
  requirements: []
79
72
 
80
73
  rubyforge_project: pluginaweek
81
- rubygems_version: 1.3.1
74
+ rubygems_version: 1.3.5
82
75
  signing_key:
83
- specification_version: 2
76
+ specification_version: 3
84
77
  summary: Adds support for automatically encrypting ActiveRecord attributes
85
78
  test_files:
86
79
  - test/unit/sha_cipher_test.rb