attr_encryptor 1.0.0

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