encrypted_attributes 0.3.0 → 0.4.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 +9 -0
- data/LICENSE +1 -1
- data/README.rdoc +24 -24
- data/Rakefile +2 -1
- data/lib/encrypted_attributes/sha_cipher.rb +16 -34
- data/lib/encrypted_attributes.rb +79 -28
- data/test/app_root/config/environment.rb +1 -0
- data/test/app_root/db/migrate/001_create_users.rb +1 -3
- data/test/unit/encrypted_attributes_test.rb +182 -13
- data/test/unit/sha_cipher_test.rb +20 -33
- metadata +4 -4
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
== master
|
2
2
|
|
3
|
+
== 0.4.0 / 2009-05-02
|
4
|
+
|
5
|
+
* Replace dynamic :salt option with :embed_salt option and utilizing the #encrypts block
|
6
|
+
* Allow a block to be used for dynamically defining encryption options
|
7
|
+
* Add :before / :after callbacks for hooking into the encryption process
|
8
|
+
* Allow the callback when encryption occurs to be customized [Sergio Salvatore]
|
9
|
+
* Define source attribute accessor if different than target and not defined [Sergio Salvatore]
|
10
|
+
* Update tests to work with Rails 2.3
|
11
|
+
|
3
12
|
== 0.3.0 / 2008-12-14
|
4
13
|
|
5
14
|
* Remove the PluginAWeek namespace
|
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -25,7 +25,7 @@ Source
|
|
25
25
|
|
26
26
|
Encrypting attributes can be repetitive especially when doing so throughout
|
27
27
|
various models and various projects. encrypted_attributes, in association
|
28
|
-
with the encrypted_strings
|
28
|
+
with the encrypted_strings library, helps make encrypting ActiveRecord
|
29
29
|
attributes easier by automating the process.
|
30
30
|
|
31
31
|
The options that +encrypts+ takes includes all of the encryption options for
|
@@ -35,46 +35,46 @@ into the +encrypts+ method. Examples of this are show in the Usage section.
|
|
35
35
|
|
36
36
|
== Usage
|
37
37
|
|
38
|
-
===
|
38
|
+
=== Encryption Modes
|
39
39
|
|
40
|
-
|
41
|
-
generate one based on the attributes of the model.
|
40
|
+
SHA, symmetric, and asymmetric encryption modes are supported (default is SHA):
|
42
41
|
|
43
|
-
With the default salt:
|
44
42
|
class User < ActiveRecord::Base
|
45
|
-
encrypts :password
|
43
|
+
encrypts :password, :salt => 'secret'
|
44
|
+
# encrypts :password, :mode => :symmetric, :password => 'secret'
|
45
|
+
# encrypts :password, :mode => :asymmetric, :public_key_file => '/keys/public', :private_key_file => '/keys/private'
|
46
46
|
end
|
47
47
|
|
48
|
-
|
49
|
-
class User < ActiveRecord::Base
|
50
|
-
encrypts :password, :salt => :create_salt
|
51
|
-
|
52
|
-
private
|
53
|
-
def create_salt
|
54
|
-
"#{login}_salt"
|
55
|
-
end
|
56
|
-
end
|
48
|
+
=== Dynamic Configuration
|
57
49
|
|
58
|
-
The
|
59
|
-
encrypted. Therefore, there's no need to add a second column for storing the
|
60
|
-
salt value.
|
61
|
-
|
62
|
-
=== Symmetric Encryption
|
50
|
+
The encryption configuration can be dynamically set like so:
|
63
51
|
|
64
52
|
class User < ActiveRecord::Base
|
65
|
-
encrypts :password, :mode => :
|
53
|
+
encrypts :password, :mode => :sha do |user|
|
54
|
+
{:salt => "#{user.login}-#{Time.now}", :embed_salt => true}
|
55
|
+
end
|
66
56
|
end
|
67
57
|
|
68
|
-
|
58
|
+
In this case, the salt and password values are combined and stored in the
|
59
|
+
attribute being encrypted. Therefore, there's no need to add a second column
|
60
|
+
for storing the salt value.
|
61
|
+
|
62
|
+
To store the dynamic salt in a separate column:
|
69
63
|
|
70
64
|
class User < ActiveRecord::Base
|
71
|
-
encrypts :password, :mode => :
|
65
|
+
encrypts :password, :mode => :sha, :before => :create_salt do |user|
|
66
|
+
{:salt => user.salt}
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_salt
|
70
|
+
self.salt = "#{login}-#{Time.now}"
|
71
|
+
end
|
72
72
|
end
|
73
73
|
|
74
74
|
=== Targeted Encryption
|
75
75
|
|
76
76
|
If you want to store the encrypted value in a different attribute than the
|
77
|
-
attribute being encrypted
|
77
|
+
attribute being encrypted:
|
78
78
|
|
79
79
|
class User < ActiveRecord::Base
|
80
80
|
encrypts :password, :to => :crypted_password
|
data/Rakefile
CHANGED
@@ -5,9 +5,10 @@ require 'rake/contrib/sshpublisher'
|
|
5
5
|
|
6
6
|
spec = Gem::Specification.new do |s|
|
7
7
|
s.name = 'encrypted_attributes'
|
8
|
-
s.version = '0.
|
8
|
+
s.version = '0.4.0'
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
10
|
s.summary = 'Adds support for automatically encrypting ActiveRecord attributes'
|
11
|
+
s.description = s.summary
|
11
12
|
|
12
13
|
s.files = FileList['{lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
|
13
14
|
s.require_path = 'lib'
|
@@ -1,47 +1,29 @@
|
|
1
1
|
module EncryptedAttributes
|
2
|
-
# Adds support for
|
2
|
+
# Adds support for embedding salts in the encrypted value
|
3
3
|
class ShaCipher < EncryptedStrings::ShaCipher
|
4
4
|
# Encrypts a string using a Secure Hash Algorithm (SHA), specifically SHA-1.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
# *
|
8
|
-
#
|
9
|
-
# *
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
case salt
|
16
|
-
when Symbol
|
17
|
-
object.send(salt)
|
18
|
-
when Proc
|
19
|
-
salt.call(object)
|
20
|
-
else
|
21
|
-
salt
|
22
|
-
end
|
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
|
6
|
+
# Configuration options:
|
7
|
+
# * <tt>:salt</tt> - Random bytes used as one of the inputs for generating
|
8
|
+
# the encrypted string
|
9
|
+
# * <tt>:embed_salt</tt> - Whether to embed the salt directly within the
|
10
|
+
# encrypted value. Default is false. This is useful for storing both
|
11
|
+
# the salt and the encrypted value in the same attribute.
|
12
|
+
def initialize(value, options = {}) #:nodoc:
|
13
|
+
if @embed_salt = options.delete(:embed_salt)
|
14
|
+
# The salt is at the end of the value
|
31
15
|
salt = value[40..-1]
|
32
|
-
|
33
|
-
options[:salt] = salt
|
34
|
-
end
|
35
|
-
|
36
|
-
super(options)
|
16
|
+
options[:salt] = salt unless salt.blank?
|
37
17
|
end
|
18
|
+
|
19
|
+
super(options)
|
38
20
|
end
|
39
21
|
|
40
|
-
# Encrypts the data,
|
41
|
-
#
|
22
|
+
# Encrypts the data, embedding the salt at the end of the string if
|
23
|
+
# configured to do so
|
42
24
|
def encrypt(data)
|
43
25
|
encrypted_data = super
|
44
|
-
encrypted_data << salt if @
|
26
|
+
encrypted_data << salt if @embed_salt
|
45
27
|
encrypted_data
|
46
28
|
end
|
47
29
|
end
|
data/lib/encrypted_attributes.rb
CHANGED
@@ -3,24 +3,37 @@ require 'encrypted_attributes/sha_cipher'
|
|
3
3
|
|
4
4
|
module EncryptedAttributes
|
5
5
|
module MacroMethods
|
6
|
-
# Encrypts the
|
6
|
+
# Encrypts the given attribute.
|
7
7
|
#
|
8
8
|
# Configuration options:
|
9
|
-
# *
|
10
|
-
#
|
11
|
-
# *
|
12
|
-
#
|
9
|
+
# * <tt>:mode</tt> - The mode of encryption to use. Default is <tt>:sha</tt>.
|
10
|
+
# See EncryptedStrings for other possible modes.
|
11
|
+
# * <tt>:to</tt> - The attribute to write the encrypted value to. Default
|
12
|
+
# is the same attribute being encrypted.
|
13
|
+
# * <tt>:before</tt> - The callback to invoke every time *before* the
|
14
|
+
# attribute is encrypted
|
15
|
+
# * <tt>:after</tt> - The callback to invoke every time *after* the
|
16
|
+
# attribute is encrypted
|
17
|
+
# * <tt>:on</tt> - The ActiveRecord callback to use when triggering the
|
18
|
+
# encryption. By default, this will encrypt on <tt>before_validation</tt>.
|
19
|
+
# See ActiveRecord::Callbacks for a list of possible callbacks.
|
20
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
21
|
+
# if the encryption should occur. The method, proc or string should return
|
22
|
+
# or evaluate to a true or false value.
|
23
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
24
|
+
# determine if the encryption should not occur. The method, proc or string
|
25
|
+
# should return or evaluate to a true or false value.
|
13
26
|
#
|
14
27
|
# For additional configuration options used during the actual encryption,
|
15
28
|
# see the individual cipher class for the specified mode.
|
16
29
|
#
|
17
30
|
# == Encryption timeline
|
18
31
|
#
|
19
|
-
#
|
20
|
-
# This means that you can still validate the presence of the
|
21
|
-
# attribute, but other things like password length cannot be
|
22
|
-
# without either (a) decrypting the value first or (b) using a
|
23
|
-
# encryption target. For example,
|
32
|
+
# By default, attributes are encrypted immediately before a record is
|
33
|
+
# validated. This means that you can still validate the presence of the
|
34
|
+
# encrypted attribute, but other things like password length cannot be
|
35
|
+
# validated without either (a) decrypting the value first or (b) using a
|
36
|
+
# different encryption target. For example,
|
24
37
|
#
|
25
38
|
# class User < ActiveRecord::Base
|
26
39
|
# encrypts :password, :to => :crypted_password
|
@@ -50,7 +63,7 @@ module EncryptedAttributes
|
|
50
63
|
#
|
51
64
|
# class User < ActiveRecord::Base
|
52
65
|
# encrypts :password
|
53
|
-
# # encrypts :password, :salt =>
|
66
|
+
# # encrypts :password, :salt => 'secret'
|
54
67
|
# end
|
55
68
|
#
|
56
69
|
# Symmetric encryption:
|
@@ -66,9 +79,34 @@ module EncryptedAttributes
|
|
66
79
|
# encrypts :password, :mode => :asymmetric
|
67
80
|
# # encrypts :password, :mode => :asymmetric, :public_key_file => '/keys/public', :private_key_file => '/keys/private'
|
68
81
|
# end
|
69
|
-
|
82
|
+
#
|
83
|
+
# == Dynamic configuration
|
84
|
+
#
|
85
|
+
# For better security, the encryption options (such as the salt value)
|
86
|
+
# can be based on values in each individual record. In order to
|
87
|
+
# dynamically configure the encryption options so that individual records
|
88
|
+
# can be referenced, an optional block can be specified.
|
89
|
+
#
|
90
|
+
# For example,
|
91
|
+
#
|
92
|
+
# class User < ActiveRecord::Base
|
93
|
+
# encrypts :password, :mode => :sha, :before => :create_salt do |user|
|
94
|
+
# {:salt => user.salt}
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# private
|
98
|
+
# def create_salt
|
99
|
+
# self.salt = "#{login}-#{Time.now}"
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# In the above example, the SHA encryption's <tt>salt</tt> is configured
|
104
|
+
# dynamically based on the user's login and the time at which it was
|
105
|
+
# encrypted. This helps improve the security of the user's password.
|
106
|
+
def encrypts(attr_name, options = {}, &config)
|
107
|
+
config ||= options
|
70
108
|
attr_name = attr_name.to_s
|
71
|
-
to_attr_name = options.delete(:to) || attr_name
|
109
|
+
to_attr_name = (options.delete(:to) || attr_name).to_s
|
72
110
|
|
73
111
|
# Figure out what cipher is being configured for the attribute
|
74
112
|
mode = options.delete(:mode) || :sha
|
@@ -79,15 +117,27 @@ module EncryptedAttributes
|
|
79
117
|
cipher_class = EncryptedStrings.const_get(class_name)
|
80
118
|
end
|
81
119
|
|
82
|
-
#
|
83
|
-
|
84
|
-
|
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)
|
85
129
|
true
|
86
130
|
end
|
87
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
|
+
|
88
138
|
# Define the reader when reading the encrypted attribute from the database
|
89
139
|
define_method(to_attr_name) do
|
90
|
-
read_encrypted_attribute(to_attr_name, cipher_class,
|
140
|
+
read_encrypted_attribute(to_attr_name, cipher_class, config)
|
91
141
|
end
|
92
142
|
|
93
143
|
unless included_modules.include?(EncryptedAttributes::InstanceMethods)
|
@@ -106,8 +156,10 @@ module EncryptedAttributes
|
|
106
156
|
# Only encrypt values that actually have content and have not already
|
107
157
|
# been encrypted
|
108
158
|
unless value.blank? || value.encrypted?
|
159
|
+
callback("before_encrypt_#{attr_name}")
|
160
|
+
|
109
161
|
# Create the cipher configured for this attribute
|
110
|
-
cipher = create_cipher(cipher_class, options,
|
162
|
+
cipher = create_cipher(cipher_class, options, value)
|
111
163
|
|
112
164
|
# Encrypt the value
|
113
165
|
value = cipher.encrypt(value)
|
@@ -115,6 +167,8 @@ module EncryptedAttributes
|
|
115
167
|
|
116
168
|
# Update the value based on the target attribute
|
117
169
|
send("#{to_attr_name}=", value)
|
170
|
+
|
171
|
+
callback("after_encrypt_#{attr_name}")
|
118
172
|
end
|
119
173
|
end
|
120
174
|
|
@@ -132,21 +186,18 @@ module EncryptedAttributes
|
|
132
186
|
# don't want to encrypt when a new value has been set)
|
133
187
|
unless value.blank? || value.encrypted? || attribute_changed?(to_attr_name)
|
134
188
|
# Create the cipher configured for this attribute
|
135
|
-
value.cipher = create_cipher(cipher_class, options,
|
189
|
+
value.cipher = create_cipher(cipher_class, options, value)
|
136
190
|
end
|
137
191
|
|
138
192
|
value
|
139
193
|
end
|
140
194
|
|
141
|
-
# Creates a new cipher with the given configuration options
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
else
|
148
|
-
klass.new(options.dup)
|
149
|
-
end
|
195
|
+
# Creates a new cipher with the given configuration options
|
196
|
+
def create_cipher(klass, options, value)
|
197
|
+
options = options.is_a?(Proc) ? options.call(self) : options.dup
|
198
|
+
|
199
|
+
# Only use the contextual information for this plugin's ciphers
|
200
|
+
klass.parent == EncryptedAttributes ? klass.new(value, options) : klass.new(options)
|
150
201
|
end
|
151
202
|
end
|
152
203
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../test_helper'
|
2
2
|
|
3
|
-
class EncryptedAttributesTest <
|
3
|
+
class EncryptedAttributesTest < ActiveSupport::TestCase
|
4
4
|
def setup
|
5
5
|
User.encrypts :password
|
6
6
|
end
|
@@ -61,7 +61,7 @@ class EncryptedAttributesTest < Test::Unit::TestCase
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
class EncryptedAttributesWithDifferentTargetTest <
|
64
|
+
class EncryptedAttributesWithDifferentTargetTest < ActiveSupport::TestCase
|
65
65
|
def setup
|
66
66
|
User.encrypts :password, :to => :crypted_password
|
67
67
|
end
|
@@ -113,27 +113,115 @@ class EncryptedAttributesWithDifferentTargetTest < Test::Unit::TestCase
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
-
class
|
116
|
+
class EncryptedAttributesWithVirtualAttributeSourceTest < ActiveSupport::TestCase
|
117
|
+
def setup
|
118
|
+
User.encrypts :raw_password, :to => :crypted_password
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_should_define_source_reader
|
122
|
+
assert User.method_defined?(:raw_password)
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_should_define_source_writer
|
126
|
+
assert User.method_defined?(:raw_password=)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_should_encrypt_from_virtual_attribute
|
130
|
+
user = create_user(:login => 'admin', :raw_password => 'secret')
|
131
|
+
assert user.crypted_password.encrypted?
|
132
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.crypted_password}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def teardown
|
136
|
+
User.class_eval do
|
137
|
+
@before_validation_callbacks = nil
|
138
|
+
|
139
|
+
remove_method(:raw_password)
|
140
|
+
remove_method(:raw_password=)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class EncryptedAttributesWithConflictingVirtualAttributeSourceTest < ActiveSupport::TestCase
|
146
|
+
def setup
|
147
|
+
User.class_eval do
|
148
|
+
def raw_password
|
149
|
+
'raw_password'
|
150
|
+
end
|
151
|
+
|
152
|
+
def raw_password=(value)
|
153
|
+
self.password = value
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
User.encrypts :raw_password, :to => :crypted_password
|
158
|
+
@user = User.new
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_should_not_define_source_reader
|
162
|
+
assert_equal 'raw_password', @user.raw_password
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_should_not_define_source_writer
|
166
|
+
@user.raw_password = 'raw_password'
|
167
|
+
assert_equal 'raw_password', @user.password
|
168
|
+
end
|
169
|
+
|
170
|
+
def teardown
|
171
|
+
User.class_eval do
|
172
|
+
@before_validation_callbacks = nil
|
173
|
+
|
174
|
+
remove_method(:raw_password)
|
175
|
+
remove_method(:raw_password=)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class EncryptedAttributesWithCustomCallbackTest < ActiveSupport::TestCase
|
181
|
+
def setup
|
182
|
+
User.encrypts :password, :on => :before_save
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_should_not_encrypt_on_validation
|
186
|
+
user = new_user(:login => 'admin', :password => 'secret')
|
187
|
+
user.valid?
|
188
|
+
assert_equal 'secret', user.password
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_should_encrypt_on_create
|
192
|
+
user = new_user(:login => 'admin', :password => 'secret')
|
193
|
+
user.save
|
194
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
195
|
+
end
|
196
|
+
|
197
|
+
def teardown
|
198
|
+
User.class_eval do
|
199
|
+
@before_save_callbacks = nil
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class EncryptedAttributesWithConditionalsTest < ActiveSupport::TestCase
|
117
205
|
def test_should_not_encrypt_if_if_conditional_is_false
|
118
|
-
User.encrypts :password, :if => lambda {false}
|
206
|
+
User.encrypts :password, :if => lambda {|user| false}
|
119
207
|
user = create_user(:login => 'admin', :password => 'secret')
|
120
208
|
assert_equal 'secret', user.password
|
121
209
|
end
|
122
210
|
|
123
211
|
def test_should_encrypt_if_if_conditional_is_true
|
124
|
-
User.encrypts :password, :if => lambda {true}
|
212
|
+
User.encrypts :password, :if => lambda {|user| true}
|
125
213
|
user = create_user(:login => 'admin', :password => 'secret')
|
126
214
|
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
127
215
|
end
|
128
216
|
|
129
217
|
def test_should_not_encrypt_if_unless_conditional_is_true
|
130
|
-
User.encrypts :password, :unless => lambda {true}
|
218
|
+
User.encrypts :password, :unless => lambda {|user| true}
|
131
219
|
user = create_user(:login => 'admin', :password => 'secret')
|
132
220
|
assert_equal 'secret', user.password
|
133
221
|
end
|
134
222
|
|
135
223
|
def test_should_encrypt_if_unless_conditional_is_false
|
136
|
-
User.encrypts :password, :unless => lambda {false}
|
224
|
+
User.encrypts :password, :unless => lambda {|user| false}
|
137
225
|
user = create_user(:login => 'admin', :password => 'secret')
|
138
226
|
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
139
227
|
end
|
@@ -145,7 +233,88 @@ class EncryptedAttributesWithConditionalsTest < Test::Unit::TestCase
|
|
145
233
|
end
|
146
234
|
end
|
147
235
|
|
148
|
-
class
|
236
|
+
class EncryptedAttributesWithBeforeCallbacksTest < ActiveSupport::TestCase
|
237
|
+
def setup
|
238
|
+
@password = nil
|
239
|
+
@ran_callback = false
|
240
|
+
User.encrypts :password, :before => lambda {|user| @ran_callback = true; @password = user.password.to_s}
|
241
|
+
|
242
|
+
create_user(:login => 'admin', :password => 'secret')
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_should_run_callback
|
246
|
+
assert @ran_callback
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_should_not_have_encrypted_yet
|
250
|
+
assert_equal 'secret', @password
|
251
|
+
end
|
252
|
+
|
253
|
+
def teardown
|
254
|
+
User.class_eval do
|
255
|
+
@before_validation_callbacks = nil
|
256
|
+
@before_encrypt_password_callbacks = nil
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class EncryptedAttributesWithAfterCallbacksTest < ActiveSupport::TestCase
|
262
|
+
def setup
|
263
|
+
@password = nil
|
264
|
+
@ran_callback = false
|
265
|
+
User.encrypts :password, :after => lambda {|user| @ran_callback = true; @password = user.password.to_s}
|
266
|
+
|
267
|
+
create_user(:login => 'admin', :password => 'secret')
|
268
|
+
end
|
269
|
+
|
270
|
+
def test_should_run_callback
|
271
|
+
assert @ran_callback
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_should_have_encrypted_already
|
275
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', @password
|
276
|
+
end
|
277
|
+
|
278
|
+
def teardown
|
279
|
+
User.class_eval do
|
280
|
+
@before_validation_callbacks = nil
|
281
|
+
@after_encrypt_password_callbacks = nil
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
class EncryptedAttributesWithDynamicConfigurationTest < ActiveSupport::TestCase
|
287
|
+
def setup
|
288
|
+
@salt = nil
|
289
|
+
User.encrypts :password, :before => lambda {|user| user.salt = user.login} do |user|
|
290
|
+
{:salt => @salt = user.salt}
|
291
|
+
end
|
292
|
+
|
293
|
+
@user = create_user(:login => 'admin', :password => 'secret')
|
294
|
+
end
|
295
|
+
|
296
|
+
def test_should_use_dynamic_configuration_during_write
|
297
|
+
assert_equal 'a55d037f385cad22efe7862e07b805938d150154', "#{@user[:password]}"
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_should_use_dynamic_configuration_during_read
|
301
|
+
user = User.find(@user.id)
|
302
|
+
assert_equal 'a55d037f385cad22efe7862e07b805938d150154', "#{user.password}"
|
303
|
+
end
|
304
|
+
|
305
|
+
def test_should_build_configuration_after_before_callbacks_invoked
|
306
|
+
assert_equal 'admin', @salt
|
307
|
+
end
|
308
|
+
|
309
|
+
def teardown
|
310
|
+
User.class_eval do
|
311
|
+
@before_validation_callbacks = nil
|
312
|
+
@before_encrypt_password_callbacks = nil
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class ShaEncryptionTest < ActiveSupport::TestCase
|
149
318
|
def setup
|
150
319
|
User.encrypts :password, :mode => :sha
|
151
320
|
@user = create_user(:login => 'admin', :password => 'secret')
|
@@ -168,7 +337,7 @@ class ShaEncryptionTest < Test::Unit::TestCase
|
|
168
337
|
end
|
169
338
|
|
170
339
|
def test_should_be_able_to_check_password
|
171
|
-
assert_equal 'secret', @user.password
|
340
|
+
assert_equal 'secret', @user. password
|
172
341
|
end
|
173
342
|
|
174
343
|
def teardown
|
@@ -178,9 +347,9 @@ class ShaEncryptionTest < Test::Unit::TestCase
|
|
178
347
|
end
|
179
348
|
end
|
180
349
|
|
181
|
-
class
|
350
|
+
class ShaWithEmbeddedSaltEncryptionTest < ActiveSupport::TestCase
|
182
351
|
def setup
|
183
|
-
User.encrypts :password, :mode => :sha, :salt => :
|
352
|
+
User.encrypts :password, :mode => :sha, :salt => 'admin', :embed_salt => true
|
184
353
|
@user = create_user(:login => 'admin', :password => 'secret')
|
185
354
|
end
|
186
355
|
|
@@ -211,7 +380,7 @@ class ShaWithCustomSaltEncryptionTest < Test::Unit::TestCase
|
|
211
380
|
end
|
212
381
|
end
|
213
382
|
|
214
|
-
class SymmetricEncryptionTest <
|
383
|
+
class SymmetricEncryptionTest < ActiveSupport::TestCase
|
215
384
|
def setup
|
216
385
|
User.encrypts :password, :mode => :symmetric, :password => 'key'
|
217
386
|
@user = create_user(:login => 'admin', :password => 'secret')
|
@@ -244,7 +413,7 @@ class SymmetricEncryptionTest < Test::Unit::TestCase
|
|
244
413
|
end
|
245
414
|
end
|
246
415
|
|
247
|
-
class AsymmetricEncryptionTest <
|
416
|
+
class AsymmetricEncryptionTest < ActiveSupport::TestCase
|
248
417
|
def setup
|
249
418
|
User.encrypts :password, :mode => :asymmetric,
|
250
419
|
:private_key_file => File.dirname(__FILE__) + '/../keys/private',
|
@@ -1,56 +1,43 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../test_helper'
|
2
2
|
|
3
|
-
class
|
3
|
+
class ShaCipherWithoutEmbeddingTest < Test::Unit::TestCase
|
4
4
|
def setup
|
5
|
-
@
|
5
|
+
@cipher = EncryptedAttributes::ShaCipher.new('dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', :salt => 'custom_salt')
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
9
|
-
|
10
|
-
assert_equal 'admin', cipher.salt
|
11
|
-
end
|
12
|
-
|
13
|
-
def test_should_allow_stringified_salt
|
14
|
-
cipher = EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => 'custom_salt')
|
15
|
-
assert_equal 'custom_salt', cipher.salt
|
8
|
+
def test_should_use_configured_salt
|
9
|
+
assert_equal 'custom_salt', @cipher.salt
|
16
10
|
end
|
17
11
|
|
18
|
-
def
|
19
|
-
|
20
|
-
cipher = EncryptedAttributes::ShaCipher.new(@user, 'password', :write, :salt => dynamic_salt)
|
21
|
-
assert_equal 'admin', cipher.salt
|
12
|
+
def test_should_not_embed_salt_in_encrypted_string
|
13
|
+
assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', @cipher.encrypt('secret')
|
22
14
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
15
|
+
end
|
16
|
+
|
17
|
+
class ShaCipherWithNoSaltEmbeddedTest < Test::Unit::TestCase
|
18
|
+
def setup
|
19
|
+
@cipher = EncryptedAttributes::ShaCipher.new('dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', :embed_salt => true, :salt => 'custom_salt')
|
28
20
|
end
|
29
21
|
|
30
|
-
def
|
31
|
-
|
32
|
-
assert_equal 'a55d037f385cad22efe7862e07b805938d150154admin', cipher.encrypt('secret')
|
22
|
+
def test_should_use_configured_salt
|
23
|
+
assert_equal 'custom_salt', @cipher.salt
|
33
24
|
end
|
34
25
|
|
35
|
-
def
|
36
|
-
|
37
|
-
assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', cipher.encrypt('secret')
|
26
|
+
def test_should_embed_salt_in_encrypted_string
|
27
|
+
assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', @cipher.encrypt('secret')
|
38
28
|
end
|
39
29
|
end
|
40
30
|
|
41
|
-
class
|
31
|
+
class ShaCipherWithSaltEmbeddedTest < Test::Unit::TestCase
|
42
32
|
def setup
|
43
|
-
@
|
44
|
-
@cipher = EncryptedAttributes::ShaCipher.new(@user, 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', :read)
|
33
|
+
@cipher = EncryptedAttributes::ShaCipher.new('dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', :embed_salt => true, :salt => 'ignored_salt')
|
45
34
|
end
|
46
35
|
|
47
|
-
def
|
36
|
+
def test_should_use_remaining_characters_after_password_for_salt
|
48
37
|
assert_equal 'custom_salt', @cipher.salt
|
49
38
|
end
|
50
39
|
|
51
|
-
def
|
52
|
-
|
53
|
-
password.cipher = @cipher
|
54
|
-
assert_equal 'secret', password
|
40
|
+
def test_should_embed_salt_in_encrypted_string
|
41
|
+
assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', @cipher.encrypt('secret')
|
55
42
|
end
|
56
43
|
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
|
+
version: 0.4.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:
|
12
|
+
date: 2009-05-02 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,7 +22,7 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 0.3.0
|
24
24
|
version:
|
25
|
-
description:
|
25
|
+
description: Adds support for automatically encrypting ActiveRecord attributes
|
26
26
|
email: aaron@pluginaweek.org
|
27
27
|
executables: []
|
28
28
|
|
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements: []
|
79
79
|
|
80
80
|
rubyforge_project: pluginaweek
|
81
|
-
rubygems_version: 1.
|
81
|
+
rubygems_version: 1.3.1
|
82
82
|
signing_key:
|
83
83
|
specification_version: 2
|
84
84
|
summary: Adds support for automatically encrypting ActiveRecord attributes
|