attr_encryptor 1.0.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.
@@ -0,0 +1,25 @@
1
+ if defined?(ActiveRecord::Base)
2
+ module AttrEncryptor
3
+ module Adapters
4
+ module ActiveRecord
5
+ def self.extended(base) # :nodoc:
6
+ base.class_eval do
7
+ attr_encrypted_options[:encode] = true
8
+ end
9
+ end
10
+
11
+ protected
12
+
13
+ # Ensures the attribute methods for db fields have been defined before calling the original
14
+ # <tt>attr_encrypted</tt> method
15
+ def attr_encrypted(*attrs)
16
+ define_attribute_methods rescue nil
17
+ super
18
+ attrs.reject { |attr| attr.is_a?(Hash) }.each { |attr| alias_method "#{attr}_before_type_cast", attr }
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ ActiveRecord::Base.extend AttrEncryptor::Adapters::ActiveRecord
25
+ end
@@ -0,0 +1,21 @@
1
+ if defined?(DataMapper)
2
+ module AttrEncryptor
3
+ module Adapters
4
+ module DataMapper
5
+ def self.extended(base) # :nodoc:
6
+ class << base
7
+ alias_method :included_without_attr_encrypted, :included
8
+ alias_method :included, :included_with_attr_encrypted
9
+ end
10
+ end
11
+
12
+ def included_with_attr_encrypted(base)
13
+ included_without_attr_encrypted(base)
14
+ base.attr_encrypted_options[:encode] = true
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ DataMapper::Resource.extend AttrEncryptor::Adapters::DataMapper
21
+ end
@@ -0,0 +1,14 @@
1
+ if defined?(Sequel)
2
+ module AttrEncryptor
3
+ module Adapters
4
+ module Sequel
5
+ def self.extended(base) # :nodoc:
6
+ base.attr_encrypted_options[:encode] = true
7
+ end
8
+ end
9
+ end
10
+ end
11
+
12
+ Sequel::Model.extend AttrEncryptor::Adapters::Sequel
13
+
14
+ end
@@ -0,0 +1,17 @@
1
+ module AttrEncryptor
2
+ # Contains information about this gem's version
3
+ module Version
4
+ MAJOR = 1
5
+ MINOR = 0
6
+ PATCH = 0
7
+
8
+ # Returns a version string by joining <tt>MAJOR</tt>, <tt>MINOR</tt>, and <tt>PATCH</tt> with <tt>'.'</tt>
9
+ #
10
+ # Example
11
+ #
12
+ # Version.string # '1.0.2'
13
+ def self.string
14
+ [MAJOR, MINOR, PATCH].join('.')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,98 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
4
+
5
+ def create_tables
6
+ silence_stream(STDOUT) do
7
+ ActiveRecord::Schema.define(:version => 1) do
8
+ create_table :people do |t|
9
+ t.string :encrypted_email
10
+ t.string :password
11
+ t.string :encrypted_credentials
12
+ t.binary :salt
13
+ t.string :encrypted_email_salt
14
+ t.string :encrypted_credentials_salt
15
+ t.string :encrypted_email_iv
16
+ t.string :encrypted_credentials_iv
17
+ end
18
+ create_table :accounts do |t|
19
+ t.string :encrypted_password
20
+ t.string :encrypted_password_iv
21
+ t.string :encrypted_password_salt
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ # The table needs to exist before defining the class
28
+ create_tables
29
+
30
+ ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
31
+
32
+ class Person < ActiveRecord::Base
33
+ attr_encrypted :email, :key => "secret"
34
+ attr_encrypted :credentials, :key => Proc.new { |user| Encryptor.encrypt(:value => user.salt, :key => 'secret_key') }, :marshal => true
35
+
36
+
37
+ after_initialize :initialize_salt_and_credentials
38
+
39
+ protected
40
+
41
+ def initialize_salt_and_credentials
42
+ self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(5)).to_s)
43
+ self.credentials ||= { :username => 'example', :password => 'test' }
44
+ end
45
+ end
46
+
47
+ class PersonWithValidation < Person
48
+ validates_presence_of :email
49
+ end
50
+
51
+ class Account < ActiveRecord::Base
52
+ attr_accessor :key
53
+ attr_encrypted :password, :key => Proc.new {|account| account.key}
54
+ end
55
+
56
+ class ActiveRecordTest < Test::Unit::TestCase
57
+
58
+ def setup
59
+ ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
60
+ create_tables
61
+ end
62
+
63
+ def test_should_encrypt_email
64
+ @person = Person.create :email => 'test@example.com'
65
+ assert_not_nil @person.encrypted_email
66
+ assert_not_equal @person.email, @person.encrypted_email
67
+ assert_equal @person.email, Person.find(:first).email
68
+ end
69
+
70
+ def test_should_marshal_and_encrypt_credentials
71
+ @person = Person.create
72
+ assert_not_nil @person.encrypted_credentials
73
+ assert_not_equal @person.credentials, @person.encrypted_credentials
74
+ assert_equal @person.credentials, Person.find(:first).credentials
75
+ end
76
+
77
+ def test_should_encode_by_default
78
+ assert Person.attr_encrypted_options[:encode]
79
+ end
80
+
81
+ def test_should_validate_presence_of_email
82
+ @person = PersonWithValidation.new
83
+ assert !@person.valid?
84
+ assert !@person.errors[:email].empty? || @person.errors.on(:email)
85
+ end
86
+
87
+ def test_should_encrypt_decrypt_with_iv
88
+ @person = Person.create :email => 'test@example.com'
89
+ @person2 = Person.find(@person.id)
90
+ assert_not_nil @person2.encrypted_email_iv
91
+ assert_equal 'test@example.com', @person2.email
92
+ end
93
+
94
+ def _test_should_create_an_account_regardless_of_arguments_order
95
+ Account.create!(:key => "secret", :password => "password")
96
+ Account.create!(:password => "password" , :key => "secret")
97
+ end
98
+ end
@@ -0,0 +1,290 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class SillyEncryptor
4
+ def self.silly_encrypt(options)
5
+ (options[:value] + options[:some_arg]).reverse
6
+ end
7
+
8
+ def self.silly_decrypt(options)
9
+ options[:value].reverse.gsub(/#{options[:some_arg]}$/, '')
10
+ end
11
+ end
12
+
13
+ class User
14
+ self.attr_encrypted_options[:key] = Proc.new { |user| user.class.to_s } # default key
15
+
16
+ attr_encrypted :email, :without_encoding, :key => 'secret key'
17
+ attr_encrypted :password, :prefix => 'crypted_', :suffix => '_test'
18
+ attr_encrypted :ssn, :key => :salt, :attribute => 'ssn_encrypted'
19
+ attr_encrypted :credit_card, :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt, :some_arg => 'test'
20
+ attr_encrypted :with_encoding, :key => 'secret key', :encode => true
21
+ attr_encrypted :with_custom_encoding, :key => 'secret key', :encode => 'm'
22
+ attr_encrypted :with_marshaling, :key => 'secret key', :marshal => true
23
+ attr_encrypted :with_true_if, :key => 'secret key', :if => true
24
+ attr_encrypted :with_false_if, :key => 'secret key', :if => false
25
+ attr_encrypted :with_true_unless, :key => 'secret key', :unless => true
26
+ attr_encrypted :with_false_unless, :key => 'secret key', :unless => false
27
+ attr_encrypted :with_if_changed, :key => 'secret key', :if => :should_encrypt
28
+
29
+ attr_encryptor :aliased, :key => 'secret_key'
30
+
31
+ attr_accessor :salt
32
+ attr_accessor :should_encrypt
33
+
34
+ def initialize
35
+ self.salt = Time.now.to_i.to_s
36
+ self.should_encrypt = true
37
+ end
38
+ end
39
+
40
+ class Admin < User
41
+ attr_encrypted :testing
42
+ end
43
+
44
+ class SomeOtherClass
45
+ def self.call(object)
46
+ object.class
47
+ end
48
+ end
49
+
50
+ class AttrEncryptorTest < Test::Unit::TestCase
51
+
52
+ def test_should_store_email_in_encrypted_attributes
53
+ assert User.encrypted_attributes.include?(:email)
54
+ end
55
+
56
+ def test_should_not_store_salt_in_encrypted_attributes
57
+ assert !User.encrypted_attributes.include?(:salt)
58
+ end
59
+
60
+ def test_attr_encrypted_should_return_true_for_email
61
+ assert User.attr_encrypted?('email')
62
+ end
63
+
64
+ def test_attr_encrypted_should_not_use_the_same_attribute_name_for_two_attributes_in_the_same_line
65
+ assert_not_equal User.encrypted_attributes[:email][:attribute], User.encrypted_attributes[:without_encoding][:attribute]
66
+ end
67
+
68
+ def test_attr_encrypted_should_return_false_for_salt
69
+ assert !User.attr_encrypted?('salt')
70
+ end
71
+
72
+ def test_should_generate_an_encrypted_attribute
73
+ assert User.new.respond_to?(:encrypted_email)
74
+ end
75
+
76
+ def test_should_generate_an_encrypted_attribute_with_a_prefix_and_suffix
77
+ assert User.new.respond_to?(:crypted_password_test)
78
+ end
79
+
80
+ def test_should_generate_an_encrypted_attribute_with_the_attribute_option
81
+ assert User.new.respond_to?(:ssn_encrypted)
82
+ end
83
+
84
+ def test_should_not_encrypt_nil_value
85
+ assert_nil User.encrypt_email(nil)
86
+ end
87
+
88
+ def test_should_not_encrypt_empty_string
89
+ assert_equal '', User.encrypt_email('')
90
+ end
91
+
92
+ def test_should_encrypt_email
93
+ assert_not_nil User.encrypt_email('test@example.com')
94
+ assert_not_equal 'test@example.com', User.encrypt_email('test@example.com')
95
+ end
96
+
97
+ def test_should_encrypt_email_when_modifying_the_attr_writer
98
+ @user = User.new
99
+ assert_nil @user.encrypted_email
100
+ @user.email = 'test@example.com'
101
+ assert_not_nil @user.encrypted_email
102
+ assert_equal User.encrypt_email('test@example.com'), @user.encrypted_email
103
+ end
104
+
105
+ def test_should_not_decrypt_nil_value
106
+ assert_nil User.decrypt_email(nil)
107
+ end
108
+
109
+ def test_should_not_decrypt_empty_string
110
+ assert_equal '', User.decrypt_email('')
111
+ end
112
+
113
+ def test_should_decrypt_email
114
+ encrypted_email = User.encrypt_email('test@example.com')
115
+ assert_not_equal 'test@test.com', encrypted_email
116
+ assert_equal 'test@example.com', User.decrypt_email(encrypted_email)
117
+ end
118
+
119
+ def test_should_decrypt_email_when_reading
120
+ @user = User.new
121
+ assert_nil @user.email
122
+ @user.encrypted_email = User.encrypt_email('test@example.com')
123
+ assert_equal 'test@example.com', @user.email
124
+ end
125
+
126
+ def test_should_encrypt_with_encoding
127
+ assert_equal User.encrypt_with_encoding('test'), [User.encrypt_without_encoding('test')].pack('m')
128
+ end
129
+
130
+ def test_should_decrypt_with_encoding
131
+ encrypted = User.encrypt_with_encoding('test')
132
+ assert_equal 'test', User.decrypt_with_encoding(encrypted)
133
+ assert_equal User.decrypt_with_encoding(encrypted), User.decrypt_without_encoding(encrypted.unpack('m').first)
134
+ end
135
+
136
+ def test_should_encrypt_with_custom_encoding
137
+ assert_equal User.encrypt_with_encoding('test'), [User.encrypt_without_encoding('test')].pack('m')
138
+ end
139
+
140
+ def test_should_decrypt_with_custom_encoding
141
+ encrypted = User.encrypt_with_encoding('test')
142
+ assert_equal 'test', User.decrypt_with_encoding(encrypted)
143
+ assert_equal User.decrypt_with_encoding(encrypted), User.decrypt_without_encoding(encrypted.unpack('m').first)
144
+ end
145
+
146
+ def test_should_encrypt_with_marshaling
147
+ @user = User.new
148
+ @user.with_marshaling = [1, 2, 3]
149
+ assert_not_nil @user.encrypted_with_marshaling
150
+ assert_equal User.encrypt_with_marshaling([1, 2, 3]), @user.encrypted_with_marshaling
151
+ end
152
+
153
+ def test_should_decrypt_with_marshaling
154
+ encrypted = User.encrypt_with_marshaling([1, 2, 3])
155
+ @user = User.new
156
+ assert_nil @user.with_marshaling
157
+ @user.encrypted_with_marshaling = encrypted
158
+ assert_equal [1, 2, 3], @user.with_marshaling
159
+ end
160
+
161
+ def test_should_use_custom_encryptor_and_crypt_method_names_and_arguments
162
+ assert_equal SillyEncryptor.silly_encrypt(:value => 'testing', :some_arg => 'test'), User.encrypt_credit_card('testing')
163
+ end
164
+
165
+ def test_should_evaluate_a_key_passed_as_a_symbol
166
+ @user = User.new
167
+ assert_nil @user.ssn_encrypted
168
+ @user.ssn = 'testing'
169
+ assert_not_nil @user.ssn_encrypted
170
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => @user.salt, :iv => @user.ssn_encrypted_iv.unpack("m").first, :salt => Time.now.to_i.to_s), @user.ssn_encrypted
171
+ end
172
+
173
+ def test_should_evaluate_a_key_passed_as_a_proc
174
+ @user = User.new
175
+ assert_nil @user.crypted_password_test
176
+ @user.password = 'testing'
177
+ assert_not_nil @user.crypted_password_test
178
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'User', :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => Time.now.to_i.to_s), @user.crypted_password_test
179
+ end
180
+
181
+ def test_should_use_options_found_in_the_attr_encrypted_options_attribute
182
+ @user = User.new
183
+ assert_nil @user.crypted_password_test
184
+ @user.password = 'testing'
185
+ assert_not_nil @user.crypted_password_test
186
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'User', :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => Time.now.to_i.to_s), @user.crypted_password_test
187
+ end
188
+
189
+ def test_should_inherit_encrypted_attributes
190
+ assert_equal [User.encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, Admin.encrypted_attributes.keys.collect { |key| key.to_s }.sort
191
+ end
192
+
193
+ def test_should_inherit_attr_encrypted_options
194
+ assert !User.attr_encrypted_options.empty?
195
+ assert_equal User.attr_encrypted_options, Admin.attr_encrypted_options
196
+ end
197
+
198
+ def test_should_not_inherit_unrelated_attributes
199
+ assert SomeOtherClass.attr_encrypted_options.empty?
200
+ assert SomeOtherClass.encrypted_attributes.empty?
201
+ end
202
+
203
+ def test_should_evaluate_a_symbol_option
204
+ assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, :class)
205
+ end
206
+
207
+ def test_should_evaluate_a_proc_option
208
+ assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, proc { |object| object.class })
209
+ end
210
+
211
+ def test_should_evaluate_a_lambda_option
212
+ assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, lambda { |object| object.class })
213
+ end
214
+
215
+ def test_should_evaluate_a_method_option
216
+ assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, SomeOtherClass.method(:call))
217
+ end
218
+
219
+ def test_should_return_a_string_option
220
+ assert_equal 'Object', Object.new.send(:evaluate_attr_encrypted_option, 'Object')
221
+ end
222
+
223
+ def test_should_encrypt_with_true_if
224
+ @user = User.new
225
+ assert_nil @user.encrypted_with_true_if
226
+ @user.with_true_if = 'testing'
227
+ assert_not_nil @user.encrypted_with_true_if
228
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key', :iv => @user.encrypted_with_true_if_iv.unpack("m").first, :salt => Time.now.to_i.to_s), @user.encrypted_with_true_if
229
+ end
230
+
231
+ def test_should_not_encrypt_with_false_if
232
+ @user = User.new
233
+ assert_nil @user.encrypted_with_false_if
234
+ @user.with_false_if = 'testing'
235
+ assert_not_nil @user.encrypted_with_false_if
236
+ assert_equal 'testing', @user.encrypted_with_false_if
237
+ end
238
+
239
+ def test_should_encrypt_with_false_unless
240
+ @user = User.new
241
+ assert_nil @user.encrypted_with_false_unless
242
+ @user.with_false_unless = 'testing'
243
+ assert_not_nil @user.encrypted_with_false_unless
244
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key', :iv => @user.encrypted_with_false_unless_iv.unpack("m").first, :salt => Time.now.to_i.to_s,), @user.encrypted_with_false_unless
245
+ end
246
+
247
+ def test_should_not_encrypt_with_true_unless
248
+ @user = User.new
249
+ assert_nil @user.encrypted_with_true_unless
250
+ @user.with_true_unless = 'testing'
251
+ assert_not_nil @user.encrypted_with_true_unless
252
+ assert_equal 'testing', @user.encrypted_with_true_unless
253
+ end
254
+
255
+ def test_should_work_with_aliased_attr_encryptor
256
+ assert User.encrypted_attributes.include?(:aliased)
257
+ end
258
+
259
+ def test_should_always_reset_options
260
+ @user = User.new
261
+ @user.with_if_changed = "encrypt_stuff"
262
+ @user.stubs(:instance_variable_get).returns(nil)
263
+ @user.stubs(:instance_variable_set).raises("BadStuff")
264
+ assert_raise RuntimeError do
265
+ @user.with_if_changed
266
+ end
267
+
268
+ @user = User.new
269
+ @user.should_encrypt = false
270
+ @user.with_if_changed = "not_encrypted_stuff"
271
+ assert_equal "not_encrypted_stuff", @user.with_if_changed
272
+ assert_equal "not_encrypted_stuff", @user.encrypted_with_if_changed
273
+ end
274
+
275
+ def test_should_cast_values_as_strings_before_encrypting
276
+ string_encrypted_email = User.encrypt_email('3')
277
+ assert_equal string_encrypted_email, User.encrypt_email(3)
278
+ assert_equal '3', User.decrypt_email(string_encrypted_email)
279
+ end
280
+
281
+ def test_should_create_query_accessor
282
+ @user = User.new
283
+ assert !@user.email?
284
+ @user.email = ''
285
+ assert !@user.email?
286
+ @user.email = 'test@example.com'
287
+ assert @user.email?
288
+ end
289
+
290
+ end