encrypted_attributes 0.2.0 → 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/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,9 @@
1
1
  == master
2
2
 
3
+ == 0.3.0 / 2008-12-14
4
+
5
+ * Remove the PluginAWeek namespace
6
+
3
7
  == 0.2.0 / 2008-12-4
4
8
 
5
9
  * Update to be compatible with encrypted_strings 0.2.1
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'rake/contrib/sshpublisher'
5
5
 
6
6
  spec = Gem::Specification.new do |s|
7
7
  s.name = 'encrypted_attributes'
8
- s.version = '0.2.0'
8
+ s.version = '0.3.0'
9
9
  s.platform = Gem::Platform::RUBY
10
10
  s.summary = 'Adds support for automatically encrypting ActiveRecord attributes'
11
11
 
@@ -13,7 +13,7 @@ spec = Gem::Specification.new do |s|
13
13
  s.require_path = 'lib'
14
14
  s.has_rdoc = true
15
15
  s.test_files = Dir['test/**/*_test.rb']
16
- s.add_dependency 'encrypted_strings', '>= 0.2.1'
16
+ s.add_dependency 'encrypted_strings', '>= 0.3.0'
17
17
 
18
18
  s.author = 'Aaron Pfeifer'
19
19
  s.email = 'aaron@pluginaweek.org'
@@ -1,50 +1,48 @@
1
- module PluginAWeek #:nodoc:
2
- module EncryptedAttributes
3
- # Adds support for dynamically generated salts
4
- class ShaCipher < PluginAWeek::EncryptedStrings::ShaCipher
5
- # Encrypts a string using a Secure Hash Algorithm (SHA), specifically SHA-1.
6
- #
7
- # The <tt>:salt</tt> configuration option can be any one of the following types:
8
- # * +symbol+ - Calls the method on the object whose value is being encrypted
9
- # * +proc+ - A block that will be invoked, providing it with the object whose value is being encrypted
10
- # * +string+ - The actual salt value to use
11
- def initialize(object, value, operation, options = {}) #:nodoc:
12
- if operation == :write
13
- # Figure out the actual salt value
14
- if salt = options[:salt]
15
- options[:salt] =
16
- case salt
17
- when Symbol
18
- object.send(salt)
19
- when Proc
20
- salt.call(object)
21
- else
22
- salt
23
- end
24
- end
25
-
26
- # Track whether or not the salt was generated dynamically
27
- @dynamic_salt = salt != options[:salt]
28
-
29
- super(options)
30
- else
31
- # The salt is at the end of the value if it's dynamic
32
- salt = value[40..-1]
33
- if @dynamic_salt = !salt.blank?
34
- options[:salt] = salt
35
- end
36
-
37
- super(options)
1
+ module EncryptedAttributes
2
+ # Adds support for dynamically generated salts
3
+ class ShaCipher < EncryptedStrings::ShaCipher
4
+ # Encrypts a string using a Secure Hash Algorithm (SHA), specifically SHA-1.
5
+ #
6
+ # The <tt>:salt</tt> configuration option can be any one of the following types:
7
+ # * +symbol+ - Calls the method on the object whose value is being encrypted
8
+ # * +proc+ - A block that will be invoked, providing it with the object whose value is being encrypted
9
+ # * +string+ - The actual salt value to use
10
+ def initialize(object, value, operation, options = {}) #:nodoc:
11
+ if operation == :write
12
+ # Figure out the actual salt value
13
+ if salt = options[:salt]
14
+ options[:salt] =
15
+ case salt
16
+ when Symbol
17
+ object.send(salt)
18
+ when Proc
19
+ salt.call(object)
20
+ else
21
+ salt
22
+ end
38
23
  end
24
+
25
+ # Track whether or not the salt was generated dynamically
26
+ @dynamic_salt = salt != options[:salt]
27
+
28
+ super(options)
29
+ else
30
+ # The salt is at the end of the value if it's dynamic
31
+ salt = value[40..-1]
32
+ if @dynamic_salt = !salt.blank?
33
+ options[:salt] = salt
34
+ end
35
+
36
+ super(options)
39
37
  end
40
-
41
- # Encrypts the data, appending the salt to the end of the string if it
42
- # was created dynamically
43
- def encrypt(data)
44
- encrypted_data = super
45
- encrypted_data << salt if @dynamic_salt
46
- encrypted_data
47
- end
38
+ end
39
+
40
+ # Encrypts the data, appending the salt to the end of the string if it
41
+ # was created dynamically
42
+ def encrypt(data)
43
+ encrypted_data = super
44
+ encrypted_data << salt if @dynamic_salt
45
+ encrypted_data
48
46
  end
49
47
  end
50
48
  end
@@ -1,158 +1,156 @@
1
1
  require 'encrypted_strings'
2
2
  require 'encrypted_attributes/sha_cipher'
3
3
 
4
- module PluginAWeek #:nodoc:
5
- module EncryptedAttributes
6
- module MacroMethods
7
- # Encrypts the specified attribute.
8
- #
9
- # Configuration options:
10
- # * +mode+ - The mode of encryption to use. Default is sha. See PluginAWeek::EncryptedStrings for other possible modes.
11
- # * +to+ - The attribute to write the encrypted value to. Default is the same attribute being encrypted.
12
- # * +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.
13
- # * +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.
14
- #
15
- # For additional configuration options used during the actual encryption,
16
- # see the individual cipher class for the specified mode.
17
- #
18
- # == Encryption timeline
19
- #
20
- # Attributes are encrypted immediately before a record is validated.
21
- # This means that you can still validate the presence of the encrypted
22
- # attribute, but other things like password length cannot be validated
23
- # without either (a) decrypting the value first or (b) using a different
24
- # encryption target. For example,
25
- #
26
- # class User < ActiveRecord::Base
27
- # encrypts :password, :to => :crypted_password
28
- #
29
- # validates_presence_of :password, :crypted_password
30
- # validates_length_of :password, :maximum => 16
31
- # end
32
- #
33
- # In the above example, the actual encrypted password will be stored in
34
- # the +crypted_password+ attribute. This means that validations can
35
- # still run against the model for the original password value.
36
- #
37
- # user = User.new(:password => 'secret')
38
- # user.password # => "secret"
39
- # user.crypted_password # => nil
40
- # user.valid? # => true
41
- # user.crypted_password # => "8152bc582f58c854f580cb101d3182813dec4afe"
42
- #
43
- # user = User.new(:password => 'longer_than_the_maximum_allowed')
44
- # user.valid? # => false
45
- # user.crypted_password # => "e80a709f25798f87d9ca8005a7f64a645964d7c2"
46
- # user.errors[:password] # => "is too long (maximum is 16 characters)"
47
- #
48
- # == Encryption mode examples
49
- #
50
- # SHA encryption:
51
- #
52
- # class User < ActiveRecord::Base
53
- # encrypts :password
54
- # # encrypts :password, :salt => :create_salt
55
- # end
56
- #
57
- # Symmetric encryption:
58
- #
59
- # class User < ActiveRecord::Base
60
- # encrypts :password, :mode => :symmetric
61
- # # encrypts :password, :mode => :symmetric, :key => 'custom'
62
- # end
63
- #
64
- # Asymmetric encryption:
65
- #
66
- # class User < ActiveRecord::Base
67
- # encrypts :password, :mode => :asymmetric
68
- # # encrypts :password, :mode => :asymmetric, :public_key_file => '/keys/public', :private_key_file => '/keys/private'
69
- # end
70
- def encrypts(attr_name, options = {})
71
- attr_name = attr_name.to_s
72
- to_attr_name = options.delete(:to) || attr_name
73
-
74
- # Figure out what cipher is being configured for the attribute
75
- mode = options.delete(:mode) || :sha
76
- class_name = "#{mode.to_s.classify}Cipher"
77
- if PluginAWeek::EncryptedAttributes.const_defined?(class_name)
78
- cipher_class = PluginAWeek::EncryptedAttributes.const_get(class_name)
79
- else
80
- cipher_class = PluginAWeek::EncryptedStrings.const_get(class_name)
81
- end
82
-
83
- # Set the encrypted value right before validation takes place
84
- before_validation(:if => options.delete(:if), :unless => options.delete(:unless)) do |record|
85
- record.send(:write_encrypted_attribute, attr_name, to_attr_name, cipher_class, options)
86
- true
87
- end
88
-
89
- # Define the reader when reading the encrypted attribute from the database
90
- define_method(to_attr_name) do
91
- read_encrypted_attribute(to_attr_name, cipher_class, options)
92
- end
93
-
94
- unless included_modules.include?(PluginAWeek::EncryptedAttributes::InstanceMethods)
95
- include PluginAWeek::EncryptedAttributes::InstanceMethods
96
- end
4
+ module EncryptedAttributes
5
+ module MacroMethods
6
+ # Encrypts the specified attribute.
7
+ #
8
+ # Configuration options:
9
+ # * +mode+ - The mode of encryption to use. Default is sha. See EncryptedStrings for other possible modes.
10
+ # * +to+ - The attribute to write the encrypted value to. Default is the same attribute being encrypted.
11
+ # * +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.
12
+ # * +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.
13
+ #
14
+ # For additional configuration options used during the actual encryption,
15
+ # see the individual cipher class for the specified mode.
16
+ #
17
+ # == Encryption timeline
18
+ #
19
+ # Attributes are encrypted immediately before a record is validated.
20
+ # This means that you can still validate the presence of the encrypted
21
+ # attribute, but other things like password length cannot be validated
22
+ # without either (a) decrypting the value first or (b) using a different
23
+ # encryption target. For example,
24
+ #
25
+ # class User < ActiveRecord::Base
26
+ # encrypts :password, :to => :crypted_password
27
+ #
28
+ # validates_presence_of :password, :crypted_password
29
+ # validates_length_of :password, :maximum => 16
30
+ # end
31
+ #
32
+ # In the above example, the actual encrypted password will be stored in
33
+ # the +crypted_password+ attribute. This means that validations can
34
+ # still run against the model for the original password value.
35
+ #
36
+ # user = User.new(:password => 'secret')
37
+ # user.password # => "secret"
38
+ # user.crypted_password # => nil
39
+ # user.valid? # => true
40
+ # user.crypted_password # => "8152bc582f58c854f580cb101d3182813dec4afe"
41
+ #
42
+ # user = User.new(:password => 'longer_than_the_maximum_allowed')
43
+ # user.valid? # => false
44
+ # user.crypted_password # => "e80a709f25798f87d9ca8005a7f64a645964d7c2"
45
+ # user.errors[:password] # => "is too long (maximum is 16 characters)"
46
+ #
47
+ # == Encryption mode examples
48
+ #
49
+ # SHA encryption:
50
+ #
51
+ # class User < ActiveRecord::Base
52
+ # encrypts :password
53
+ # # encrypts :password, :salt => :create_salt
54
+ # end
55
+ #
56
+ # Symmetric encryption:
57
+ #
58
+ # class User < ActiveRecord::Base
59
+ # encrypts :password, :mode => :symmetric
60
+ # # encrypts :password, :mode => :symmetric, :key => 'custom'
61
+ # end
62
+ #
63
+ # Asymmetric encryption:
64
+ #
65
+ # class User < ActiveRecord::Base
66
+ # encrypts :password, :mode => :asymmetric
67
+ # # encrypts :password, :mode => :asymmetric, :public_key_file => '/keys/public', :private_key_file => '/keys/private'
68
+ # end
69
+ def encrypts(attr_name, options = {})
70
+ attr_name = attr_name.to_s
71
+ to_attr_name = options.delete(:to) || attr_name
72
+
73
+ # Figure out what cipher is being configured for the attribute
74
+ mode = options.delete(:mode) || :sha
75
+ class_name = "#{mode.to_s.classify}Cipher"
76
+ if EncryptedAttributes.const_defined?(class_name)
77
+ cipher_class = EncryptedAttributes.const_get(class_name)
78
+ else
79
+ cipher_class = EncryptedStrings.const_get(class_name)
80
+ end
81
+
82
+ # Set the encrypted value right before validation takes place
83
+ before_validation(:if => options.delete(:if), :unless => options.delete(:unless)) do |record|
84
+ record.send(:write_encrypted_attribute, attr_name, to_attr_name, cipher_class, options)
85
+ true
86
+ end
87
+
88
+ # Define the reader when reading the encrypted attribute from the database
89
+ define_method(to_attr_name) do
90
+ read_encrypted_attribute(to_attr_name, cipher_class, options)
91
+ end
92
+
93
+ unless included_modules.include?(EncryptedAttributes::InstanceMethods)
94
+ include EncryptedAttributes::InstanceMethods
97
95
  end
98
96
  end
99
-
100
- module InstanceMethods #:nodoc:
101
- private
102
- # Encrypts the given attribute to a target location using the encryption
103
- # options configured for that attribute
104
- def write_encrypted_attribute(attr_name, to_attr_name, cipher_class, options)
105
- value = send(attr_name)
106
-
107
- # Only encrypt values that actually have content and have not already
108
- # been encrypted
109
- unless value.blank? || value.encrypted?
110
- # Create the cipher configured for this attribute
111
- cipher = create_cipher(cipher_class, options, :write, value)
112
-
113
- # Encrypt the value
114
- value = cipher.encrypt(value)
115
- value.cipher = cipher
116
-
117
- # Update the value based on the target attribute
118
- send("#{to_attr_name}=", value)
119
- end
120
- end
97
+ end
98
+
99
+ module InstanceMethods #:nodoc:
100
+ private
101
+ # Encrypts the given attribute to a target location using the encryption
102
+ # options configured for that attribute
103
+ def write_encrypted_attribute(attr_name, to_attr_name, cipher_class, options)
104
+ value = send(attr_name)
121
105
 
122
- # Reads the given attribute from the database, adding contextual
123
- # information about how it was encrypted so that equality comparisons
124
- # can be used
125
- def read_encrypted_attribute(to_attr_name, cipher_class, options)
126
- value = read_attribute(to_attr_name)
106
+ # Only encrypt values that actually have content and have not already
107
+ # been encrypted
108
+ unless value.blank? || value.encrypted?
109
+ # Create the cipher configured for this attribute
110
+ cipher = create_cipher(cipher_class, options, :write, value)
127
111
 
128
- # Make sure we set the cipher for equality comparison when reading
129
- # from the database. This should only be done if the value is *not*
130
- # blank, is *not* encrypted, and hasn't changed since it was read from
131
- # the database. The dirty checking is important when the encypted value
132
- # is written to the same attribute as the unencrypted value (i.e. you
133
- # don't want to encrypt when a new value has been set)
134
- unless value.blank? || value.encrypted? || attribute_changed?(to_attr_name)
135
- # Create the cipher configured for this attribute
136
- value.cipher = create_cipher(cipher_class, options, :read, value)
137
- end
112
+ # Encrypt the value
113
+ value = cipher.encrypt(value)
114
+ value.cipher = cipher
138
115
 
139
- value
116
+ # Update the value based on the target attribute
117
+ send("#{to_attr_name}=", value)
140
118
  end
119
+ end
120
+
121
+ # Reads the given attribute from the database, adding contextual
122
+ # information about how it was encrypted so that equality comparisons
123
+ # can be used
124
+ def read_encrypted_attribute(to_attr_name, cipher_class, options)
125
+ value = read_attribute(to_attr_name)
141
126
 
142
- # Creates a new cipher with the given configuration options. The
143
- # operator defines the context in which the cipher will be used.
144
- def create_cipher(klass, options, operator, value)
145
- if klass.parent == PluginAWeek::EncryptedAttributes
146
- # Only use the contextual information for ciphers defined in this plugin
147
- klass.new(self, value, operator, options.dup)
148
- else
149
- klass.new(options.dup)
150
- end
127
+ # Make sure we set the cipher for equality comparison when reading
128
+ # from the database. This should only be done if the value is *not*
129
+ # blank, is *not* encrypted, and hasn't changed since it was read from
130
+ # the database. The dirty checking is important when the encypted value
131
+ # is written to the same attribute as the unencrypted value (i.e. you
132
+ # don't want to encrypt when a new value has been set)
133
+ unless value.blank? || value.encrypted? || attribute_changed?(to_attr_name)
134
+ # Create the cipher configured for this attribute
135
+ value.cipher = create_cipher(cipher_class, options, :read, value)
151
136
  end
152
- end
137
+
138
+ value
139
+ end
140
+
141
+ # Creates a new cipher with the given configuration options. The
142
+ # operator defines the context in which the cipher will be used.
143
+ def create_cipher(klass, options, operator, value)
144
+ if klass.parent == EncryptedAttributes
145
+ # Only use the contextual information for ciphers defined in this plugin
146
+ klass.new(self, value, operator, options.dup)
147
+ else
148
+ klass.new(options.dup)
149
+ end
150
+ end
153
151
  end
154
152
  end
155
153
 
156
154
  ActiveRecord::Base.class_eval do
157
- extend PluginAWeek::EncryptedAttributes::MacroMethods
155
+ extend EncryptedAttributes::MacroMethods
158
156
  end
@@ -160,7 +160,7 @@ class ShaEncryptionTest < Test::Unit::TestCase
160
160
  end
161
161
 
162
162
  def test_should_use_sha_cipher
163
- assert_instance_of PluginAWeek::EncryptedAttributes::ShaCipher, @user.password.cipher
163
+ assert_instance_of EncryptedAttributes::ShaCipher, @user.password.cipher
164
164
  end
165
165
 
166
166
  def test_should_use_default_salt
@@ -193,7 +193,7 @@ class ShaWithCustomSaltEncryptionTest < Test::Unit::TestCase
193
193
  end
194
194
 
195
195
  def test_should_use_sha_cipher
196
- assert_instance_of PluginAWeek::EncryptedAttributes::ShaCipher, @user.password.cipher
196
+ assert_instance_of EncryptedAttributes::ShaCipher, @user.password.cipher
197
197
  end
198
198
 
199
199
  def test_should_use_custom_salt
@@ -226,7 +226,7 @@ class SymmetricEncryptionTest < Test::Unit::TestCase
226
226
  end
227
227
 
228
228
  def test_should_use_sha_cipher
229
- assert_instance_of PluginAWeek::EncryptedStrings::SymmetricCipher, @user.password.cipher
229
+ assert_instance_of EncryptedStrings::SymmetricCipher, @user.password.cipher
230
230
  end
231
231
 
232
232
  def test_should_use_custom_password
@@ -262,7 +262,7 @@ class AsymmetricEncryptionTest < Test::Unit::TestCase
262
262
  end
263
263
 
264
264
  def test_should_use_sha_cipher
265
- assert_instance_of PluginAWeek::EncryptedStrings::AsymmetricCipher, @user.password.cipher
265
+ assert_instance_of EncryptedStrings::AsymmetricCipher, @user.password.cipher
266
266
  end
267
267
 
268
268
  def test_should_be_able_to_check_password
@@ -6,34 +6,34 @@ class ShaCipherOnWriteTest < Test::Unit::TestCase
6
6
  end
7
7
 
8
8
  def test_should_allow_symbolic_salt
9
- cipher = PluginAWeek::EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => :login)
9
+ cipher = EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => :login)
10
10
  assert_equal 'admin', cipher.salt
11
11
  end
12
12
 
13
13
  def test_should_allow_stringified_salt
14
- cipher = PluginAWeek::EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => 'custom_salt')
14
+ cipher = EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => 'custom_salt')
15
15
  assert_equal 'custom_salt', cipher.salt
16
16
  end
17
17
 
18
18
  def test_should_allow_block_salt
19
19
  dynamic_salt = lambda {|user| user.login}
20
- cipher = PluginAWeek::EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => dynamic_salt)
20
+ cipher = EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => dynamic_salt)
21
21
  assert_equal 'admin', cipher.salt
22
22
  end
23
23
 
24
24
  def test_should_allow_dynamic_nil_salt
25
25
  dynamic_salt = lambda {|user| nil}
26
- cipher = PluginAWeek::EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => dynamic_salt)
26
+ cipher = EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => dynamic_salt)
27
27
  assert_equal '', cipher.salt
28
28
  end
29
29
 
30
30
  def test_should_append_salt_to_encrypted_value_if_dynamic
31
- cipher = PluginAWeek::EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => :login)
31
+ cipher = EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => :login)
32
32
  assert_equal 'a55d037f385cad22efe7862e07b805938d150154admin', cipher.encrypt('secret')
33
33
  end
34
34
 
35
35
  def test_should_not_append_salt_to_encrypted_value_if_static
36
- cipher = PluginAWeek::EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => 'custom_salt')
36
+ cipher = EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => 'custom_salt')
37
37
  assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', cipher.encrypt('secret')
38
38
  end
39
39
  end
@@ -41,7 +41,7 @@ end
41
41
  class ShaCipherOnReadTest < Test::Unit::TestCase
42
42
  def setup
43
43
  @user = create_user(:login => 'admin')
44
- @cipher = PluginAWeek::EncryptedAttributes::ShaCipher.new(@user, 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', :read)
44
+ @cipher = EncryptedAttributes::ShaCipher.new(@user, 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', :read)
45
45
  end
46
46
 
47
47
  def test_should_should_use_remaining_characters_after_password_for_salt
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.2.0
4
+ version: 0.3.0
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: 2008-12-04 00:00:00 -05:00
12
+ date: 2008-12-14 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.2.1
23
+ version: 0.3.0
24
24
  version:
25
25
  description:
26
26
  email: aaron@pluginaweek.org