encrypted_attributes 0.4.0 → 0.4.1

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.
@@ -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