encrypted_attributes 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,5 +1,15 @@
1
1
  *SVN*
2
2
 
3
+ *0.1.0* (May 5th, 2008)
4
+
5
+ * Don't extend encrypted_string's encryptors, instead creating subclasses
6
+
7
+ * Don't assume anything about attribute confirmations
8
+
9
+ * Support storing the salt for SHA encryption in the same string as the original value
10
+
11
+ * Update documentation
12
+
3
13
  *0.0.2* (September 26th, 2007)
4
14
 
5
15
  * Move test fixtures out of the test application root directory
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2007 Aaron Pfeifer & Neil Abraham
1
+ Copyright (c) 2006-2008 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
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
17
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README CHANGED
@@ -5,25 +5,21 @@ attributes.
5
5
 
6
6
  == Resources
7
7
 
8
- API
9
-
10
- * http://api.pluginaweek.org/encrypted_attributes
11
-
12
8
  Wiki
13
9
 
14
10
  * http://wiki.pluginaweek.org/Encrypted_attributes
15
11
 
16
- Announcement
12
+ API
17
13
 
18
- * http://www.pluginaweek.org
14
+ * http://api.pluginaweek.org/encrypted_attributes
19
15
 
20
- Source
16
+ Development
21
17
 
22
- * http://svn.pluginaweek.org/trunk/plugins/active_record/miscellaneous/encrypted_attributes
18
+ * http://dev.pluginaweek.org/browser/trunk/encrypted_attributes
23
19
 
24
- Development
20
+ Source
25
21
 
26
- * http://dev.pluginaweek.org/browser/trunk/plugins/active_record/miscellaneous/encrypted_attributes
22
+ * http://svn.pluginaweek.org/trunk/encrypted_attributes
27
23
 
28
24
  == Description
29
25
 
@@ -42,9 +38,7 @@ into the +encrypts+ method. Examples of this are show in the Usage section.
42
38
  === SHA Encryption
43
39
 
44
40
  For SHA encryption, you can either use the default salt, a custom salt, or
45
- generate one based on the attributes of the model. If the latter option is
46
- being used, a column in the table should be created to store the salt that is
47
- generated.
41
+ generate one based on the attributes of the model.
48
42
 
49
43
  With the default salt:
50
44
  class User < ActiveRecord::Base
@@ -53,13 +47,18 @@ With the default salt:
53
47
 
54
48
  With a custom salt:
55
49
  class User < ActiveRecord::Base
56
- encrypts :password, :salt => true
50
+ encrypts :password, :salt => :create_salt
57
51
 
58
- def create_salt
59
- "#{login}_salt"
60
- end
52
+ private
53
+ def create_salt
54
+ "#{login}_salt"
55
+ end
61
56
  end
62
57
 
58
+ The salt and password values are combined and stored in the attributed being
59
+ encrypted. Therefore, there's no need to add a second column for storing the
60
+ salt value.
61
+
63
62
  === Symmetric Encryption
64
63
 
65
64
  With the default key:
@@ -84,19 +83,23 @@ With custom key files:
84
83
  encrypts :password, :mode => :asymmetric, :public_key_file => '/keys/public', :private_key_file => '/keys/private'
85
84
  end
86
85
 
87
- === The crypted attribute
88
86
 
89
- The attribute which stores the unencrypted value is a "virtual" attribute.
90
- This means that there is no column with the same name in the database.
91
- Instead, the encrypted value is stored in the crypted attributed. By default,
92
- this is assumed to be +crypted_#{attr_name}+. Therefore, if you are encrypting
93
- the "password" attribute, the encrypted value would be stored in
94
- +crypted_password+.
87
+ === Targeted Encryption
88
+
89
+ If you want to store the encrypted value in a different attribute than the
90
+ attribute being encrypted, you can do so like so:
91
+
92
+ class User < ActiveRecord::Base
93
+ encrypts :password, :to => :crypted_password
94
+ end
95
+
96
+ === Conditional Encryption
95
97
 
96
- The crypted attribute name can be customized like so:
98
+ Like ActiveRecord validations, +encrypts+ can take <tt>:if</tt> and <tt>:unless</tt>
99
+ parameters that determine whether the encryption should occur. For example,
97
100
 
98
101
  class User < ActiveRecord::Base
99
- encrypts :password, :crypted_name => 'protected_password'
102
+ encrypts :password, :if => Proc.new {Rails.env != 'development'}
100
103
  end
101
104
 
102
105
  === Additional information
@@ -112,5 +115,4 @@ Before you can run any tests, the following gem must be installed:
112
115
 
113
116
  == Dependencies
114
117
 
115
- This plugin depends on the presence of the following plugins:
116
118
  * encrypted_strings[http://wiki.pluginaweek.org/Encrypted_strings]
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'rake/gempackagetask'
4
4
  require 'rake/contrib/sshpublisher'
5
5
 
6
6
  PKG_NAME = 'encrypted_attributes'
7
- PKG_VERSION = '0.0.2'
7
+ PKG_VERSION = '0.1.0'
8
8
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
9
9
  RUBY_FORGE_PROJECT = 'pluginaweek'
10
10
 
@@ -40,8 +40,8 @@ spec = Gem::Specification.new do |s|
40
40
  s.test_files = Dir['test/**/*_test.rb']
41
41
  s.add_dependency 'encrypted_strings', '>= 0.0.1'
42
42
 
43
- s.author = 'Aaron Pfeifer, Neil Abraham'
44
- s.email = 'info@pluginaweek.org'
43
+ s.author = 'Aaron Pfeifer'
44
+ s.email = 'aaron@pluginaweek.org'
45
45
  s.homepage = 'http://www.pluginaweek.org'
46
46
  end
47
47
 
@@ -53,12 +53,12 @@ end
53
53
 
54
54
  desc 'Publish the beta gem'
55
55
  task :pgem => [:package] do
56
- Rake::SshFilePublisher.new('pluginaweek@pluginaweek.org', '/home/pluginaweek/gems.pluginaweek.org/gems', 'pkg', "#{PKG_FILE_NAME}.gem").upload
56
+ Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{PKG_FILE_NAME}.gem").upload
57
57
  end
58
58
 
59
59
  desc 'Publish the API documentation'
60
60
  task :pdoc => [:rdoc] do
61
- Rake::SshDirPublisher.new('pluginaweek@pluginaweek.org', "/home/pluginaweek/api.pluginaweek.org/#{PKG_NAME}", 'rdoc').upload
61
+ Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{PKG_NAME}", 'rdoc').upload
62
62
  end
63
63
 
64
64
  desc 'Publish the API docs and gem'
@@ -70,7 +70,7 @@ task :release => [:gem, :package] do
70
70
 
71
71
  ruby_forge = RubyForge.new
72
72
  ruby_forge.login
73
-
73
+
74
74
  %w( gem tgz zip ).each do |ext|
75
75
  file = "pkg/#{PKG_FILE_NAME}.#{ext}"
76
76
  puts "Releasing #{File.basename(file)}..."
@@ -0,0 +1,42 @@
1
+ module PluginAWeek #:nodoc:
2
+ module EncryptedAttributes
3
+ # Supports encryption for ActiveRecord models by adding dynamically generated
4
+ # salts
5
+ class ShaEncryptor < PluginAWeek::EncryptedStrings::ShaEncryptor
6
+ def initialize(record, value, operation, options = {}) #:nodoc:
7
+ if operation == :write
8
+ # Figure out the actual salt value
9
+ if salt = options[:salt]
10
+ options[:salt] =
11
+ case salt
12
+ when Symbol
13
+ record.send(salt).to_s
14
+ when Proc
15
+ salt.call(record).to_s
16
+ else
17
+ salt
18
+ end
19
+ end
20
+
21
+ @dynamic_salt = salt != options[:salt]
22
+ super(options)
23
+ else
24
+ # The salt is in the value if it's dynamic
25
+ salt = value[40..-1]
26
+ if @dynamic_salt = !salt.blank?
27
+ options.merge!(:salt => salt)
28
+ end
29
+
30
+ super(options)
31
+ end
32
+ end
33
+
34
+ # Encrypts the data, appending the salt to the end of the string
35
+ def encrypt(data)
36
+ encrypted_data = Digest::SHA1.hexdigest(data + salt)
37
+ encrypted_data << salt if @dynamic_salt
38
+ encrypted_data
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,6 +1,5 @@
1
1
  require 'encrypted_strings'
2
- require 'encrypted_attributes/extensions/encryptor'
3
- require 'encrypted_attributes/extensions/sha_encryptor'
2
+ require 'encrypted_attributes/sha_encryptor'
4
3
 
5
4
  module PluginAWeek #:nodoc:
6
5
  module EncryptedAttributes
@@ -13,55 +12,62 @@ module PluginAWeek #:nodoc:
13
12
  #
14
13
  # Configuration options:
15
14
  # * +mode+ - The mode of encryption to use. Default is sha.
16
- # * +crypted_name+ - The name of the attribute to store the crypted value in. Default is "crypted_#{attr_name}".
15
+ # * +to+ - The attribute to write the encrypted value. Default is the same attribute being encryupted.
16
+ # * +if+ - Specifies a method, proc or string to call to determine if the encryption should occur. The method, proc or string should return or evaluate to a true or false value.
17
+ # * +unless+ - Specifies a method, proc or string to call to determine if the encryption should not occur. The method, proc or string should return or evaluate to a true or false value.
17
18
  #
18
19
  # For additional configuration options, see the individual encryptor class.
19
20
  def encrypts(attr_name, options = {})
20
- mode = options.delete(:mode) || :sha
21
- encryptor_class = "PluginAWeek::EncryptedStrings::#{mode.to_s.classify}Encryptor".constantize
22
-
23
- options.reverse_merge!(
24
- :crypted_name => "crypted_#{attr_name}"
25
- )
26
- crypted_attr_name = options.delete(:crypted_name)
27
- raise ArgumentError, 'Attribute name cannot be same as crypted name' if attr_name == crypted_attr_name
21
+ attr_name = attr_name.to_s
22
+ to_attr_name = options.delete(:to) || attr_name
28
23
 
29
- # Creator accessor for the virtual attribute
30
- attr_accessor attr_name
31
-
32
- # Define the reader when reading the crypted value from the db
33
- crypted_var_name = "@#{crypted_attr_name}"
34
- define_method(crypted_attr_name) do
35
- if (value = read_attribute(crypted_attr_name)) && !value.encrypted?
36
- encryptor_options = options.dup
37
- encryptor_class.process_options(self, :read, encryptor_options)
38
- value.encryptor = encryptor_class.new(encryptor_options)
39
- end
40
-
41
- value
24
+ mode = options.delete(:mode) || :sha
25
+ if mode == :sha
26
+ encryptor_class = "PluginAWeek::EncryptedAttributes::#{mode.to_s.classify}Encryptor".constantize
27
+ else
28
+ encryptor_class = "PluginAWeek::EncryptedStrings::#{mode.to_s.classify}Encryptor".constantize
42
29
  end
43
30
 
44
31
  # Set the value immediately before validation takes place
45
- before_validation do |model|
46
- value = model.send(attr_name)
32
+ before_validation(:if => options.delete(:if), :unless => options.delete(:unless)) do |record|
33
+ value = record.send(attr_name)
47
34
 
48
- if !value.blank?
49
- unless value.encrypted?
50
- encryptor_options = options.dup
51
- encryptor_class.process_options(model, :write, encryptor_options)
52
- value = value.encrypt(mode, encryptor_options)
53
- end
35
+ unless value.blank? || value.encrypted?
36
+ # Add contextual information for this plugin's encryptors
37
+ encryptor =
38
+ if encryptor_class.parent == PluginAWeek::EncryptedAttributes
39
+ encryptor_class.new(record, value, :write, options.dup)
40
+ else
41
+ encryptor_class.new(options.dup)
42
+ end
54
43
 
55
- model.send("#{crypted_attr_name}=", value)
44
+ # Encrypt the value and then track the encryptor used
45
+ value = encryptor.encrypt(value)
46
+ value.encryptor = encryptor
47
+
48
+ record.send("#{to_attr_name}=", value)
56
49
  end
50
+
51
+ true
57
52
  end
58
53
 
59
- # After saving, be sure to reset the virtual attribute value. This also
60
- # supported resetting the confirmation field if, for example, the plugin
61
- # is being used for passwords
62
- after_save do |model|
63
- model.send("#{attr_name}=", nil)
64
- model.send("#{attr_name}_confirmation=", nil) if model.respond_to?("#{attr_name}_confirmation=")
54
+ # Define the reader when reading the crypted attribute from the db
55
+ define_method(to_attr_name) do
56
+ value = read_attribute(to_attr_name)
57
+
58
+ # Make sure we set the encryptor for equality comparison when reading
59
+ # from the database
60
+ unless value.blank? || value.encrypted? || attribute_changed?(to_attr_name)
61
+ # Add contextual information for this plugin's encryptors
62
+ value.encryptor =
63
+ if encryptor_class.parent == PluginAWeek::EncryptedAttributes
64
+ encryptor_class.new(self, value, :read, options.dup)
65
+ else
66
+ encryptor_class.new(options.dup)
67
+ end
68
+ end
69
+
70
+ value
65
71
  end
66
72
  end
67
73
  end
@@ -0,0 +1,8 @@
1
+ require 'config/boot'
2
+
3
+ Rails::Initializer.run do |config|
4
+ config.plugin_paths << '..'
5
+ config.plugins = %w(encrypted_strings encrypted_attributes)
6
+ config.cache_classes = false
7
+ config.whiny_nils = true
8
+ end
@@ -1,16 +1,13 @@
1
1
  class CreateUsers < ActiveRecord::Migration
2
2
  def self.up
3
- create_table :users, :force => true do |t|
4
- t.column :protected_password, :string, :limit => 255
5
- t.column :crypted_password, :string, :limit => 255
6
- t.column :salt, :string, :limit => 50
7
- t.column :salt_value, :string, :limit => 50
8
- t.column :login, :string, :limit => 50
9
- t.column :type, :string, :limit => 20
3
+ create_table :users do |t|
4
+ t.string :login
5
+ t.string :password
6
+ t.string :crypted_password
10
7
  end
11
8
  end
12
9
 
13
10
  def self.down
14
11
  drop_table :users
15
12
  end
16
- end
13
+ end
data/test/factory.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Factory
2
+ # Build actions for the class
3
+ def self.build(klass, &block)
4
+ name = klass.to_s.underscore
5
+ define_method("#{name}_attributes", block)
6
+
7
+ module_eval <<-end_eval
8
+ def valid_#{name}_attributes(attributes = {})
9
+ #{name}_attributes(attributes)
10
+ attributes
11
+ end
12
+
13
+ def new_#{name}(attributes = {})
14
+ #{klass}.new(valid_#{name}_attributes(attributes))
15
+ end
16
+
17
+ def create_#{name}(*args)
18
+ record = new_#{name}(*args)
19
+ record.save!
20
+ record.reload
21
+ record
22
+ end
23
+ end_eval
24
+ end
25
+
26
+ build User do |attributes|
27
+ attributes.reverse_merge!(
28
+ :login => 'admin'
29
+ )
30
+ end
31
+ end
data/test/test_helper.rb CHANGED
@@ -1,10 +1,13 @@
1
- # Load local repository plugin paths
2
- $:.unshift("#{File.dirname(__FILE__)}/../../../../ruby/string/encrypted_strings/lib")
3
-
4
1
  # Load the plugin testing framework
5
- $:.unshift("#{File.dirname(__FILE__)}/../../../../test/plugin_test_helper/lib")
2
+ $:.unshift("#{File.dirname(__FILE__)}/../../plugin_test_helper/lib")
6
3
  require 'rubygems'
7
4
  require 'plugin_test_helper'
8
5
 
9
6
  # Run the migrations
10
- ActiveRecord::Migrator.migrate("#{RAILS_ROOT}/db/migrate")
7
+ ActiveRecord::Migrator.migrate("#{RAILS_ROOT}/db/migrate")
8
+
9
+ # Mixin the factory helper
10
+ require File.expand_path("#{File.dirname(__FILE__)}/factory")
11
+ class Test::Unit::TestCase #:nodoc:
12
+ include Factory
13
+ end
@@ -2,129 +2,276 @@ require File.dirname(__FILE__) + '/../test_helper'
2
2
 
3
3
  class EncryptedAttributesTest < Test::Unit::TestCase
4
4
  def setup
5
- PluginAWeek::EncryptedStrings::AsymmetricEncryptor.default_private_key_file = File.join(File.dirname(__FILE__), '..', 'keys', 'private')
6
- PluginAWeek::EncryptedStrings::AsymmetricEncryptor.default_public_key_file = File.join(File.dirname(__FILE__), '..', 'keys', 'public')
7
-
8
- PluginAWeek::EncryptedStrings::SymmetricEncryptor.default_key = 'key'
9
- end
10
-
11
- def test_encryption_with_default_options
12
- {
13
- ShaUser => PluginAWeek::EncryptedStrings::ShaEncryptor,
14
- AsymmetricUser => PluginAWeek::EncryptedStrings::AsymmetricEncryptor,
15
- SymmetricUser => PluginAWeek::EncryptedStrings::SymmetricEncryptor
16
- }.each do |user_class, encryptor_class|
17
- user = user_class.new
18
- user.login = 'john doe'
19
- user.password = 'secret'
20
-
21
- assert user.save
22
- assert_not_nil user.crypted_password
23
- assert user.crypted_password.encrypted?
24
- assert_instance_of encryptor_class, user.crypted_password.encryptor
25
- end
5
+ User.encrypts :password
26
6
  end
27
7
 
28
- def test_encryption_with_custom_crypted_attribute
29
- user = ShaUserWithCustomCryptedAttr.new
30
- user.login = 'john doe'
31
- user.password = 'secret'
32
-
33
- assert user.save
34
- assert_nil user.crypted_password
35
- assert_not_nil user.protected_password
36
- assert user.protected_password.encrypted?
37
- assert_instance_of PluginAWeek::EncryptedStrings::ShaEncryptor, user.protected_password.encryptor
8
+ def test_should_use_sha_by_default
9
+ user = create_user(:login => 'admin', :password => 'secret')
10
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
38
11
  end
39
12
 
40
13
  def test_should_encrypt_on_invalid_model
41
- user = ShaUser.new
42
- user.login = nil
43
- user.password = 'secret'
44
-
45
- assert !user.save
46
- assert_not_nil user.password
47
- assert_not_nil user.crypted_password
48
- assert user.crypted_password.encrypted?
49
- assert_instance_of PluginAWeek::EncryptedStrings::ShaEncryptor, user.crypted_password.encryptor
14
+ user = new_user(:login => nil, :password => 'secret')
15
+ assert !user.valid?
16
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
17
+ end
18
+
19
+ def test_should_not_encrypt_if_attribute_is_nil
20
+ user = create_user(:login => 'admin', :password => nil)
21
+ assert_nil user.password
50
22
  end
51
23
 
52
24
  def test_should_not_encrypt_if_attribute_is_blank
53
- user = ShaUser.new
54
- user.login = 'john doe'
55
- user.password = nil
56
- user.save
57
- assert_nil user.crypted_password
58
-
59
- user.password = ''
60
- assert user.save
61
- assert_nil user.crypted_password
25
+ user = create_user(:login => 'admin', :password => '')
26
+ assert_equal '', user.password
62
27
  end
63
28
 
64
29
  def test_should_not_encrypt_if_already_encrypted
65
- encrypted_password = 'secret'.encrypt
66
-
67
- user = ShaUser.new
68
- user.login = 'john doe'
69
- user.password = encrypted_password
70
-
71
- assert user.save
72
- assert_equal encrypted_password, user.crypted_password
73
- end
74
-
75
- def test_should_hide_attribute_after_save
76
- user = ShaUser.new
77
- user.login = 'john doe'
78
- user.password = 'secret'
79
-
80
- assert user.save
81
- assert_nil user.password
30
+ user = create_user(:login => 'admin', :password => 'secret'.encrypt)
31
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
32
+ end
33
+
34
+ def test_should_return_encrypted_attribute_for_saved_record
35
+ user = create_user(:login => 'admin', :password => 'secret')
36
+ user = User.find(user.id)
37
+ assert user.password.encrypted?
38
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
82
39
  end
83
40
 
84
- def test_should_hide_confirmation_attribute_after_save
85
- user = ConfirmedShaUser.new
86
- user.login = 'john doe'
87
- user.password = 'secret'
88
- user.password_confirmation = 'secret'
89
-
90
- assert user.save
41
+ def test_should_not_encrypt_attribute_if_updating_without_any_changes
42
+ user = create_user(:login => 'admin', :password => 'secret')
43
+ user.login = 'Administrator'
44
+ user.save!
45
+ assert user.password.encrypted?
46
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
47
+ end
48
+
49
+ def test_should_encrypt_attribute_if_updating_with_changes
50
+ user = create_user(:login => 'admin', :password => 'secret')
51
+ user.password = 'shhh'
52
+ user.save!
53
+ assert user.password.encrypted?
54
+ assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.password}"
55
+ end
56
+
57
+ def teardown
58
+ User.class_eval do
59
+ @before_validation_callbacks = nil
60
+ end
61
+ end
62
+ end
63
+
64
+ class EncryptedAttributesWithDifferentTargetTest < Test::Unit::TestCase
65
+ def setup
66
+ User.encrypts :password, :to => :crypted_password
67
+ end
68
+
69
+ def test_should_not_encrypt_if_attribute_is_nil
70
+ user = create_user(:login => 'admin', :password => nil)
91
71
  assert_nil user.password
92
- assert_nil user.password_confirmation
72
+ assert_nil user.crypted_password
93
73
  end
94
74
 
95
- def test_sha_encryption_with_generated_salt
96
- user = ShaUserWithSalt.new
97
- user.login = 'john doe'
98
- user.password = 'secret'
99
-
100
- assert user.save
101
- assert_not_nil user.salt
102
- assert_equal 'john doe_salt', user.salt
103
- assert_equal 'secret', user.crypted_password
75
+ def test_should_not_encrypt_if_attribute_is_blank
76
+ user = create_user(:login => 'admin', :password => '')
77
+ assert_equal '', user.password
78
+ assert_nil user.crypted_password
104
79
  end
105
80
 
106
- def test_sha_encryption_with_custom_generated_salt
107
- user = ShaUserWithCustomSalt.new
108
- user.login = 'john doe'
109
- user.password = 'secret'
110
-
111
- assert user.save
112
- assert_not_nil user.salt_value
113
- assert_equal 'john doe_salt_value', user.salt_value
114
- assert_equal 'secret', user.crypted_password
81
+ def test_should_not_encrypt_if_already_encrypted
82
+ user = create_user(:login => 'admin', :crypted_password => 'secret'.encrypt)
83
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.crypted_password}"
115
84
  end
116
85
 
117
86
  def test_should_return_encrypted_attribute_for_saved_record
118
- user = ShaUser.new
119
- user.login = 'john doe'
120
- user.password = 'secret'
121
-
122
- assert user.save
123
-
124
- user = ShaUser.find(user.id)
125
- assert_nil user.password
126
- assert_not_nil user.crypted_password
87
+ user = create_user(:login => 'admin', :password => 'secret')
88
+ user = User.find(user.id)
89
+ assert user.crypted_password.encrypted?
90
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.crypted_password}"
91
+ end
92
+
93
+ def test_should_not_encrypt_attribute_if_updating_without_any_changes
94
+ user = create_user(:login => 'admin', :password => 'secret')
95
+ user.login = 'Administrator'
96
+ user.save!
97
+ assert user.crypted_password.encrypted?
98
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.crypted_password}"
99
+ end
100
+
101
+ def test_should_encrypt_attribute_if_updating_with_changes
102
+ user = create_user(:login => 'admin', :password => 'secret')
103
+ user.password = 'shhh'
104
+ user.save!
127
105
  assert user.crypted_password.encrypted?
128
- assert_instance_of PluginAWeek::EncryptedStrings::ShaEncryptor, user.crypted_password.encryptor
106
+ assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.crypted_password}"
107
+ end
108
+
109
+ def teardown
110
+ User.class_eval do
111
+ @before_validation_callbacks = nil
112
+ end
113
+ end
114
+ end
115
+
116
+ class EncryptedAttributesWithConditionalsTest < Test::Unit::TestCase
117
+ def test_should_not_encrypt_if_if_conditional_is_false
118
+ User.encrypts :password, :if => Proc.new {false}
119
+ user = create_user(:login => 'admin', :password => 'secret')
120
+ assert_equal 'secret', user.password
121
+ end
122
+
123
+ def test_should_encrypt_if_if_conditional_is_true
124
+ User.encrypts :password, :if => Proc.new {true}
125
+ user = create_user(:login => 'admin', :password => 'secret')
126
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
127
+ end
128
+
129
+ def test_should_not_encrypt_if_unless_conditional_is_true
130
+ User.encrypts :password, :unless => Proc.new {true}
131
+ user = create_user(:login => 'admin', :password => 'secret')
132
+ assert_equal 'secret', user.password
133
+ end
134
+
135
+ def test_should_encrypt_if_unless_conditional_is_false
136
+ User.encrypts :password, :unless => Proc.new {false}
137
+ user = create_user(:login => 'admin', :password => 'secret')
138
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
139
+ end
140
+
141
+ def teardown
142
+ User.class_eval do
143
+ @before_validation_callbacks = nil
144
+ end
145
+ end
146
+ end
147
+
148
+ class ShaEncryptionTest < Test::Unit::TestCase
149
+ def setup
150
+ User.encrypts :password, :mode => :sha
151
+ @user = create_user(:login => 'admin', :password => 'secret')
152
+ end
153
+
154
+ def test_should_encrypt_password
155
+ assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{@user.password}"
156
+ end
157
+
158
+ def test_should_be_encrypted
159
+ assert @user.password.encrypted?
160
+ end
161
+
162
+ def test_should_use_sha_encryptor
163
+ assert_instance_of PluginAWeek::EncryptedAttributes::ShaEncryptor, @user.password.encryptor
164
+ end
165
+
166
+ def test_should_use_default_salt
167
+ assert_equal 'salt', @user.password.encryptor.salt
168
+ end
169
+
170
+ def test_should_be_able_to_check_password
171
+ assert_equal 'secret', @user.password
172
+ end
173
+
174
+ def teardown
175
+ User.class_eval do
176
+ @before_validation_callbacks = nil
177
+ end
178
+ end
179
+ end
180
+
181
+ class ShaWithCustomSaltEncryptionTest < Test::Unit::TestCase
182
+ def setup
183
+ User.encrypts :password, :mode => :sha, :salt => :login
184
+ @user = create_user(:login => 'admin', :password => 'secret')
185
+ end
186
+
187
+ def test_should_encrypt_password
188
+ assert_equal 'a55d037f385cad22efe7862e07b805938d150154admin', "#{@user.password}"
189
+ end
190
+
191
+ def test_should_be_encrypted
192
+ assert @user.password.encrypted?
193
+ end
194
+
195
+ def test_should_use_sha_encryptor
196
+ assert_instance_of PluginAWeek::EncryptedAttributes::ShaEncryptor, @user.password.encryptor
197
+ end
198
+
199
+ def test_should_use_custom_salt
200
+ assert_equal 'admin', @user.password.encryptor.salt
201
+ end
202
+
203
+ def test_should_be_able_to_check_password
204
+ assert_equal 'secret', @user.password
205
+ end
206
+
207
+ def teardown
208
+ User.class_eval do
209
+ @before_validation_callbacks = nil
210
+ end
211
+ end
212
+ end
213
+
214
+ class SymmetricEncryptionTest < Test::Unit::TestCase
215
+ def setup
216
+ User.encrypts :password, :mode => :symmetric, :key => 'key'
217
+ @user = create_user(:login => 'admin', :password => 'secret')
218
+ end
219
+
220
+ def test_should_encrypt_password
221
+ assert_equal "+YVKcPbqSWo=\n", @user.password
222
+ end
223
+
224
+ def test_should_be_encrypted
225
+ assert @user.password.encrypted?
226
+ end
227
+
228
+ def test_should_use_sha_encryptor
229
+ assert_instance_of PluginAWeek::EncryptedStrings::SymmetricEncryptor, @user.password.encryptor
230
+ end
231
+
232
+ def test_should_use_custom_key
233
+ assert_equal 'key', @user.password.encryptor.key
234
+ end
235
+
236
+ def test_should_be_able_to_check_password
237
+ assert_equal 'secret', @user.password
238
+ end
239
+
240
+ def teardown
241
+ User.class_eval do
242
+ @before_validation_callbacks = nil
243
+ end
244
+ end
245
+ end
246
+
247
+ class AsymmetricEncryptionTest < Test::Unit::TestCase
248
+ def setup
249
+ User.encrypts :password, :mode => :asymmetric,
250
+ :private_key_file => File.dirname(__FILE__) + '/../keys/private',
251
+ :public_key_file => File.dirname(__FILE__) + '/../keys/public'
252
+ @user = create_user(:login => 'admin', :password => 'secret')
253
+ end
254
+
255
+ def test_should_encrypt_password
256
+ assert_not_equal 'secret', "#{@user.password}"
257
+ assert_equal 90, @user.password.length
258
+ end
259
+
260
+ def test_should_be_encrypted
261
+ assert @user.password.encrypted?
262
+ end
263
+
264
+ def test_should_use_sha_encryptor
265
+ assert_instance_of PluginAWeek::EncryptedStrings::AsymmetricEncryptor, @user.password.encryptor
266
+ end
267
+
268
+ def test_should_be_able_to_check_password
269
+ assert_equal 'secret', @user.password
270
+ end
271
+
272
+ def teardown
273
+ User.class_eval do
274
+ @before_validation_callbacks = nil
275
+ end
129
276
  end
130
277
  end
@@ -1,64 +1,56 @@
1
1
  require File.dirname(__FILE__) + '/../test_helper'
2
2
 
3
- class ShaEncryptorTest < Test::Unit::TestCase
4
- def test_should_respond_to_process_options
5
- assert PluginAWeek::EncryptedStrings::ShaEncryptor.respond_to?(:process_options)
3
+ class ShaEncryptorOnWriteTest < Test::Unit::TestCase
4
+ def setup
5
+ @user = create_user(:login => 'admin')
6
6
  end
7
7
 
8
- def test_process_options_should_not_make_changes_for_salt_string
9
- options = {:salt => 'my_salt_value'}
10
- expected_options = {:salt => 'my_salt_value'}
11
- PluginAWeek::EncryptedStrings::ShaEncryptor.process_options(User.new, :read, options)
12
- PluginAWeek::EncryptedStrings::ShaEncryptor.process_options(User.new, :write, options)
13
-
14
- assert_equal expected_options, options
8
+ def test_should_allow_symbolic_salt
9
+ encryptor = PluginAWeek::EncryptedAttributes::ShaEncryptor.new(@user, 'password', :write, :salt => :login)
10
+ assert_equal 'admin', encryptor.salt
15
11
  end
16
12
 
17
- def test_process_options_should_use_salt_attribute_for_salt_on_read
18
- user = ShaUserWithSalt.new
19
- user.login = 'test'
20
- user.salt = 'existing_salt'
21
-
22
- options = {:salt => true}
23
- expected_options = {:salt => 'existing_salt'}
24
- PluginAWeek::EncryptedStrings::ShaEncryptor.process_options(user, :read, options)
25
-
26
- assert_equal expected_options, options
13
+ def test_should_allow_stringified_salt
14
+ encryptor = PluginAWeek::EncryptedAttributes::ShaEncryptor.new(@user, 'password', :write, :salt => 'custom_salt')
15
+ assert_equal 'custom_salt', encryptor.salt
27
16
  end
28
17
 
29
- def test_process_options_should_generate_new_salt_on_write
30
- user = ShaUserWithSalt.new
31
- user.login = 'test'
32
- user.salt = 'existing_salt'
33
-
34
- options = {:salt => true}
35
- expected_options = {:salt => 'test_salt'}
36
- PluginAWeek::EncryptedStrings::ShaEncryptor.process_options(user, :write, options)
37
-
38
- assert_equal expected_options, options
18
+ def test_should_allow_block_salt
19
+ dynamic_salt = Proc.new {|user| user.login}
20
+ encryptor = PluginAWeek::EncryptedAttributes::ShaEncryptor.new(@user, 'password', :write, :salt => dynamic_salt)
21
+ assert_equal 'admin', encryptor.salt
39
22
  end
40
23
 
41
- def test_process_options_should_use_custom_attribute_for_salt_on_read
42
- user = ShaUserWithCustomSalt.new
43
- user.login = 'test'
44
- user.salt_value = 'existing_salt_value'
45
-
46
- options = {:salt => :salt_value}
47
- expected_options = {:salt => 'existing_salt_value'}
48
- PluginAWeek::EncryptedStrings::ShaEncryptor.process_options(user, :read, options)
49
-
50
- assert_equal expected_options, options
24
+ def test_should_allow_dynamic_nil_salt
25
+ dynamic_salt = Proc.new {|user| nil}
26
+ encryptor = PluginAWeek::EncryptedAttributes::ShaEncryptor.new(@user, 'password', :write, :salt => dynamic_salt)
27
+ assert_equal '', encryptor.salt
51
28
  end
52
29
 
53
- def test_process_options_should_generate_new_salt_from_custom_method_on_write
54
- user = ShaUserWithCustomSalt.new
55
- user.login = 'test'
56
- user.salt_value = 'existing_salt_value'
57
-
58
- options = {:salt => :salt_value}
59
- expected_options = {:salt => 'test_salt_value'}
60
- PluginAWeek::EncryptedStrings::ShaEncryptor.process_options(user, :write, options)
61
-
62
- assert_equal expected_options, options
30
+ def test_should_append_salt_to_encrypted_value_if_dynamic
31
+ encryptor = PluginAWeek::EncryptedAttributes::ShaEncryptor.new(@user, 'password', :write, :salt => :login)
32
+ assert_equal 'a55d037f385cad22efe7862e07b805938d150154admin', encryptor.encrypt('secret')
33
+ end
34
+
35
+ def test_should_not_append_salt_to_encrypted_value_if_static
36
+ encryptor = PluginAWeek::EncryptedAttributes::ShaEncryptor.new(@user, 'password', :write, :salt => 'custom_salt')
37
+ assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', encryptor.encrypt('secret')
38
+ end
39
+ end
40
+
41
+ class ShaEncryptorOnReadTest < Test::Unit::TestCase
42
+ def setup
43
+ @user = create_user(:login => 'admin')
44
+ @encryptor = PluginAWeek::EncryptedAttributes::ShaEncryptor.new(@user, 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', :read)
45
+ end
46
+
47
+ def test_should_should_use_remaining_characters_after_password_for_salt
48
+ assert_equal 'custom_salt', @encryptor.salt
49
+ end
50
+
51
+ def test_should_be_able_to_perform_equality_on_encrypted_strings
52
+ password = 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt'
53
+ password.encryptor = @encryptor
54
+ assert_equal 'secret', password
63
55
  end
64
56
  end
metadata CHANGED
@@ -1,89 +1,86 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
3
- specification_version: 1
4
2
  name: encrypted_attributes
5
3
  version: !ruby/object:Gem::Version
6
- version: 0.0.2
7
- date: 2007-09-26 00:00:00 -04:00
8
- summary: Adds support for automatically encrypting ActiveRecord attributes
9
- require_paths:
10
- - lib
11
- email: info@pluginaweek.org
12
- homepage: http://www.pluginaweek.org
13
- rubyforge_project:
14
- description:
15
- autorequire: encrypted_attributes
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
- version:
4
+ version: 0.1.0
25
5
  platform: ruby
26
- signing_key:
27
- cert_chain:
28
- post_install_message:
29
6
  authors:
30
- - Aaron Pfeifer, Neil Abraham
7
+ - Aaron Pfeifer
8
+ autorequire: encrypted_attributes
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-05 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: encrypted_strings
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.1
23
+ version:
24
+ description:
25
+ email: aaron@pluginaweek.org
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
31
32
  files:
32
- - lib/encrypted_attributes.rb
33
33
  - lib/encrypted_attributes
34
- - lib/encrypted_attributes/extensions
35
- - lib/encrypted_attributes/extensions/sha_encryptor.rb
36
- - lib/encrypted_attributes/extensions/encryptor.rb
37
- - test/test_helper.rb
38
- - test/fixtures
39
- - test/app_root
34
+ - lib/encrypted_attributes/sha_encryptor.rb
35
+ - lib/encrypted_attributes.rb
40
36
  - test/keys
41
- - test/unit
42
- - test/fixtures/users.yml
43
- - test/app_root/app
37
+ - test/keys/private
38
+ - test/keys/public
39
+ - test/factory.rb
40
+ - test/app_root
44
41
  - test/app_root/db
45
- - test/app_root/app/models
46
- - test/app_root/app/models/sha_user_with_salt.rb
47
- - test/app_root/app/models/confirmed_sha_user.rb
48
- - test/app_root/app/models/asymmetric_user.rb
49
- - test/app_root/app/models/sha_user.rb
50
- - test/app_root/app/models/sha_user_with_custom_crypted_attr.rb
51
- - test/app_root/app/models/user.rb
52
- - test/app_root/app/models/sha_user_with_custom_salt.rb
53
- - test/app_root/app/models/symmetric_user.rb
54
42
  - test/app_root/db/migrate
55
43
  - test/app_root/db/migrate/001_create_users.rb
56
- - test/keys/private
57
- - test/keys/public
58
- - test/unit/encrypted_attributes_test.rb
44
+ - test/app_root/config
45
+ - test/app_root/config/environment.rb
46
+ - test/app_root/app
47
+ - test/app_root/app/models
48
+ - test/app_root/app/models/user.rb
49
+ - test/test_helper.rb
50
+ - test/unit
59
51
  - test/unit/sha_encryptor_test.rb
60
- - test/unit/encryptor_test.rb
52
+ - test/unit/encrypted_attributes_test.rb
61
53
  - CHANGELOG
62
54
  - init.rb
63
55
  - MIT-LICENSE
64
56
  - Rakefile
65
57
  - README
66
- test_files:
67
- - test/unit/encrypted_attributes_test.rb
68
- - test/unit/sha_encryptor_test.rb
69
- - test/unit/encryptor_test.rb
58
+ has_rdoc: true
59
+ homepage: http://www.pluginaweek.org
60
+ post_install_message:
70
61
  rdoc_options: []
71
62
 
72
- extra_rdoc_files: []
73
-
74
- executables: []
75
-
76
- extensions: []
77
-
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
78
77
  requirements: []
79
78
 
80
- dependencies:
81
- - !ruby/object:Gem::Dependency
82
- name: encrypted_strings
83
- version_requirement:
84
- version_requirements: !ruby/object:Gem::Version::Requirement
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- version: 0.0.1
89
- version:
79
+ rubyforge_project:
80
+ rubygems_version: 1.1.0
81
+ signing_key:
82
+ specification_version: 2
83
+ summary: Adds support for automatically encrypting ActiveRecord attributes
84
+ test_files:
85
+ - test/unit/sha_encryptor_test.rb
86
+ - test/unit/encrypted_attributes_test.rb
@@ -1,14 +0,0 @@
1
- module PluginAWeek #:nodoc:
2
- module EncryptedAttributes
3
- module Extensions #:nodoc:
4
- module Encryptor #:nodoc:
5
- def process_options(model, operation, options)
6
- end
7
- end
8
- end
9
- end
10
- end
11
-
12
- PluginAWeek::EncryptedStrings::Encryptor.class_eval do
13
- extend PluginAWeek::EncryptedAttributes::Extensions::Encryptor
14
- end
@@ -1,28 +0,0 @@
1
- module PluginAWeek #:nodoc:
2
- module EncryptedAttributes
3
- module Extensions #:nodoc:
4
- module ShaEncryptor
5
- # Adds support for using a salt that is generated based on the model and
6
- # stored in an attribute.
7
- def process_options(model, operation, options)
8
- if (salt_attr_name = options[:salt]) && (salt_attr_name == true || salt_attr_name.is_a?(Symbol))
9
- salt_attr_name = 'salt' if salt_attr_name == true
10
-
11
- if operation == :write
12
- salt_value = model.send("create_#{salt_attr_name}").to_s
13
- model.send("#{salt_attr_name}=", salt_value)
14
- else
15
- salt_value = model.send(salt_attr_name)
16
- end
17
-
18
- options[:salt] = salt_value
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
26
- PluginAWeek::EncryptedStrings::ShaEncryptor.class_eval do
27
- extend PluginAWeek::EncryptedAttributes::Extensions::ShaEncryptor
28
- end
@@ -1,3 +0,0 @@
1
- class AsymmetricUser < User
2
- encrypts :password, :mode => :asymmetric
3
- end
@@ -1,13 +0,0 @@
1
- class ConfirmedShaUser < ShaUser
2
- with_options(:if => :password_required?) do |klass|
3
- validates_presence_of :password,
4
- :crypted_password
5
- validates_length_of :password,
6
- :in => 4..40
7
- validates_confirmation_of :password
8
- end
9
-
10
- def password_required?
11
- crypted_password.blank? || !password.blank?
12
- end
13
- end
@@ -1,3 +0,0 @@
1
- class ShaUser < User
2
- encrypts :password
3
- end
@@ -1,3 +0,0 @@
1
- class ShaUserWithCustomCryptedAttr < User
2
- encrypts :password, :crypted_name => 'protected_password'
3
- end
@@ -1,7 +0,0 @@
1
- class ShaUserWithCustomSalt < User
2
- encrypts :password, :salt => :salt_value
3
-
4
- def create_salt_value
5
- "#{login}_salt_value"
6
- end
7
- end
@@ -1,7 +0,0 @@
1
- class ShaUserWithSalt < User
2
- encrypts :password, :salt => true
3
-
4
- def create_salt
5
- "#{login}_salt"
6
- end
7
- end
@@ -1,3 +0,0 @@
1
- class SymmetricUser < User
2
- encrypts :password, :mode => :symmetric
3
- end
@@ -1,8 +0,0 @@
1
- bob:
2
- id: 1
3
- login: bob
4
- crypted_password: "0XlmUuNpE2k=\n"
5
- type: SymmetricUser
6
- fred:
7
- id: 2
8
- login: fred
@@ -1,13 +0,0 @@
1
- require File.dirname(__FILE__) + '/../test_helper'
2
-
3
- class EncryptorTest < Test::Unit::TestCase
4
- def test_should_not_make_any_changes_on_process_options
5
- assert PluginAWeek::EncryptedStrings::Encryptor.respond_to?(:process_options)
6
-
7
- options = {:salt => 'test'}
8
- expected_options = options.dup
9
- PluginAWeek::EncryptedStrings::Encryptor.process_options(User.new, :read, options)
10
-
11
- assert_equal expected_options, options
12
- end
13
- end