encrypted_attributes 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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