honkster-attr_encrypted 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,55 @@
1
+ module AttrEncrypted
2
+ module Adapters
3
+ module ActiveRecord
4
+ def self.extended(base) # :nodoc:
5
+ base.class_eval do
6
+ attr_encrypted_options[:encode] = true
7
+ class << self; alias_method_chain :method_missing, :attr_encrypted; end
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
+
21
+ # Allows you to use dynamic methods like <tt>find_by_email</tt> or <tt>scoped_by_email</tt> for
22
+ # encrypted attributes
23
+ #
24
+ # NOTE: This only works when the <tt>:key</tt> option is specified as a string (see the README)
25
+ #
26
+ # This is useful for encrypting fields like email addresses. Your user's email addresses
27
+ # are encrypted in the database, but you can still look up a user by email for logging in
28
+ #
29
+ # Example
30
+ #
31
+ # class User < ActiveRecord::Base
32
+ # attr_encrypted :email, :key => 'secret key'
33
+ # end
34
+ #
35
+ # User.find_by_email_and_password('test@example.com', 'testing')
36
+ # # results in a call to
37
+ # User.find_by_encrypted_email_and_password('the_encrypted_version_of_test@example.com', 'testing')
38
+ def method_missing_with_attr_encrypted(method, *args, &block)
39
+ if match = /^(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)$/.match(method.to_s)
40
+ attribute_names = match.captures.last.split('_and_')
41
+ attribute_names.each_with_index do |attribute, index|
42
+ if attr_encrypted?(attribute)
43
+ args[index] = send("encrypt_#{attribute}", args[index])
44
+ attribute_names[index] = encrypted_attributes[attribute.to_sym][:attribute]
45
+ end
46
+ end
47
+ method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
48
+ end
49
+ method_missing_without_attr_encrypted(method, *args, &block)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ ActiveRecord::Base.extend AttrEncrypted::Adapters::ActiveRecord
@@ -0,0 +1,19 @@
1
+ module AttrEncrypted
2
+ module Adapters
3
+ module DataMapper
4
+ def self.extended(base) # :nodoc:
5
+ class << base
6
+ alias_method :included_without_attr_encrypted, :included
7
+ alias_method :included, :included_with_attr_encrypted
8
+ end
9
+ end
10
+
11
+ def included_with_attr_encrypted(base)
12
+ included_without_attr_encrypted(base)
13
+ base.attr_encrypted_options[:encode] = true
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ DataMapper::Resource.extend AttrEncrypted::Adapters::DataMapper
@@ -0,0 +1,13 @@
1
+ module AttrEncrypted
2
+ module Adapters
3
+ module MongoMapper
4
+ def self.extended(base) # :nodoc:
5
+ base.class_eval do
6
+ attr_encrypted_options[:encode] = true
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ #MongoMapper::Docuemnt.extend AttrEncrypted::Adapters::MongoMapper
@@ -0,0 +1,11 @@
1
+ module AttrEncrypted
2
+ module Adapters
3
+ module Sequel
4
+ def self.extended(base) # :nodoc:
5
+ base.attr_encrypted_options[:encode] = true
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ Sequel::Model.extend AttrEncrypted::Adapters::Sequel
@@ -0,0 +1,17 @@
1
+ module AttrEncrypted
2
+ # Contains information about this gem's version
3
+ module Version
4
+ MAJOR = 1
5
+ MINOR = 2
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,108 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
4
+
5
+ def create_people_table
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.string :salt
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ # The table needs to exist before defining the class
19
+ create_people_table
20
+
21
+ ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
22
+
23
+ class Person < ActiveRecord::Base
24
+ attr_encrypted :email, :key => 'a secret key'
25
+ attr_encrypted :credentials, :key => Proc.new { |user| Encryptor.encrypt(:value => user.salt, :key => 'some private key') }, :marshal => true
26
+
27
+ ActiveSupport::Deprecation.silenced = true
28
+ def after_initialize; end
29
+ ActiveSupport::Deprecation.silenced = false
30
+
31
+ after_initialize :initialize_salt_and_credentials
32
+
33
+ protected
34
+
35
+ def initialize_salt_and_credentials
36
+ self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(5)).to_s)
37
+ self.credentials ||= { :username => 'example', :password => 'test' }
38
+ rescue ActiveRecord::MissingAttributeError
39
+ end
40
+ end
41
+
42
+ class PersonWithValidation < Person
43
+ validates_presence_of :email
44
+ validates_uniqueness_of :encrypted_email
45
+ end
46
+
47
+ class ActiveRecordTest < Test::Unit::TestCase
48
+
49
+ def setup
50
+ ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
51
+ create_people_table
52
+ end
53
+
54
+ def test_should_encrypt_email
55
+ @person = Person.create :email => 'test@example.com'
56
+ assert_not_nil @person.encrypted_email
57
+ assert_not_equal @person.email, @person.encrypted_email
58
+ assert_equal @person.email, Person.find(:first).email
59
+ end
60
+
61
+ def test_should_marshal_and_encrypt_credentials
62
+ @person = Person.create
63
+ assert_not_nil @person.encrypted_credentials
64
+ assert_not_equal @person.credentials, @person.encrypted_credentials
65
+ assert_equal @person.credentials, Person.find(:first).credentials
66
+ end
67
+
68
+ def test_should_find_by_email
69
+ @person = Person.create(:email => 'test@example.com')
70
+ assert_equal @person, Person.find_by_email('test@example.com')
71
+ end
72
+
73
+ def test_should_find_by_email_and_password
74
+ Person.create(:email => 'test@example.com', :password => 'invalid')
75
+ @person = Person.create(:email => 'test@example.com', :password => 'test')
76
+ assert_equal @person, Person.find_by_email_and_password('test@example.com', 'test')
77
+ end
78
+
79
+ def test_should_scope_by_email
80
+ @person = Person.create(:email => 'test@example.com')
81
+ assert_equal @person, Person.scoped_by_email('test@example.com').find(:first) rescue NoMethodError
82
+ end
83
+
84
+ def test_should_scope_by_email_and_password
85
+ Person.create(:email => 'test@example.com', :password => 'invalid')
86
+ @person = Person.create(:email => 'test@example.com', :password => 'test')
87
+ assert_equal @person, Person.scoped_by_email_and_password('test@example.com', 'test').find(:first) rescue NoMethodError
88
+ end
89
+
90
+ def test_should_encode_by_default
91
+ assert Person.attr_encrypted_options[:encode]
92
+ end
93
+
94
+ def test_should_validate_presence_of_email
95
+ @person = PersonWithValidation.new
96
+ assert !@person.valid?
97
+ assert !@person.errors[:email].empty? || @person.errors.on(:email)
98
+ end
99
+
100
+ def test_should_validate_uniqueness_of_email
101
+ @person = PersonWithValidation.new :email => 'test@example.com'
102
+ assert @person.save
103
+ @person2 = PersonWithValidation.new :email => @person.email
104
+ assert !@person2.valid?
105
+ assert !@person2.errors[:encrypted_email].empty? || @person2.errors.on(:encrypted_email)
106
+ end
107
+
108
+ end
@@ -0,0 +1,313 @@
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[:inheritance_check] = 'yup'
15
+
16
+ proc_key = Proc.new { |user| user.class.to_s }
17
+
18
+ attr_encrypted :email, :without_encoding, :key => 'secret key'
19
+ attr_encrypted :password, :prefix => 'crypted_', :suffix => '_test', :key => proc_key
20
+ attr_encrypted :ssn, :key => :salt, :attribute => 'ssn_encrypted'
21
+ attr_encrypted :credit_card, :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt, :some_arg => 'test', :key => proc_key
22
+ attr_encrypted :with_encoding, :key => 'secret key', :encode => true
23
+ attr_encrypted :with_custom_encoding, :key => 'secret key', :encode => 'm'
24
+ attr_encrypted :with_marshaling, :key => 'secret key', :marshal => true
25
+ attr_encrypted :with_true_if, :key => 'secret key', :if => true
26
+ attr_encrypted :with_false_if, :key => 'secret key', :if => false
27
+ attr_encrypted :with_true_unless, :key => 'secret key', :unless => true
28
+ attr_encrypted :with_false_unless, :key => 'secret key', :unless => false
29
+ attr_encrypted :with_if_changed, :key => 'secret key', :if => :should_encrypt
30
+ attr_encrypted :with_key_identifier, :keys => {
31
+ '2011' => 'current secret key',
32
+ '2010' => 'old secret key'
33
+ }, :key_identifier => '2011'
34
+
35
+ attr_encryptor :aliased, :key => 'secret_key'
36
+
37
+ attr_accessor :salt
38
+ attr_accessor :should_encrypt
39
+
40
+ def initialize
41
+ self.salt = Time.now.to_i.to_s
42
+ self.should_encrypt = true
43
+ end
44
+ end
45
+
46
+ class Admin < User
47
+ attr_encrypted :testing
48
+ end
49
+
50
+ class SomeOtherClass
51
+ def self.call(object)
52
+ object.class
53
+ end
54
+ end
55
+
56
+ class AttrEncryptedTest < Test::Unit::TestCase
57
+
58
+ def test_should_store_email_in_encrypted_attributes
59
+ assert User.encrypted_attributes.include?(:email)
60
+ end
61
+
62
+ def test_should_not_store_salt_in_encrypted_attributes
63
+ assert !User.encrypted_attributes.include?(:salt)
64
+ end
65
+
66
+ def test_attr_encrypted_should_return_true_for_email
67
+ assert User.attr_encrypted?('email')
68
+ end
69
+
70
+ def test_attr_encrypted_should_not_use_the_same_attribute_name_for_two_attributes_in_the_same_line
71
+ assert_not_equal User.encrypted_attributes[:email][:attribute], User.encrypted_attributes[:without_encoding][:attribute]
72
+ end
73
+
74
+ def test_attr_encrypted_should_return_false_for_salt
75
+ assert !User.attr_encrypted?('salt')
76
+ end
77
+
78
+ def test_should_generate_an_encrypted_attribute
79
+ assert User.new.respond_to?(:encrypted_email)
80
+ end
81
+
82
+ def test_should_generate_an_encrypted_attribute_with_a_prefix_and_suffix
83
+ assert User.new.respond_to?(:crypted_password_test)
84
+ end
85
+
86
+ def test_should_generate_an_encrypted_attribute_with_the_attribute_option
87
+ assert User.new.respond_to?(:ssn_encrypted)
88
+ end
89
+
90
+ def test_should_not_encrypt_nil_value
91
+ assert_nil User.encrypt_email(nil)
92
+ end
93
+
94
+ def test_should_not_encrypt_empty_string
95
+ assert_equal '', User.encrypt_email('')
96
+ end
97
+
98
+ def test_should_encrypt_email
99
+ assert_not_nil User.encrypt_email('test@example.com')
100
+ assert_not_equal 'test@example.com', User.encrypt_email('test@example.com')
101
+ end
102
+
103
+ def test_should_encrypt_email_when_modifying_the_attr_writer
104
+ @user = User.new
105
+ assert_nil @user.encrypted_email
106
+ @user.email = 'test@example.com'
107
+ assert_not_nil @user.encrypted_email
108
+ assert_equal User.encrypt_email('test@example.com'), @user.encrypted_email
109
+ end
110
+
111
+ def test_should_not_decrypt_nil_value
112
+ assert_nil User.decrypt_email(nil)
113
+ end
114
+
115
+ def test_should_not_decrypt_empty_string
116
+ assert_equal '', User.decrypt_email('')
117
+ end
118
+
119
+ def test_should_decrypt_email
120
+ encrypted_email = User.encrypt_email('test@example.com')
121
+ assert_not_equal 'test@test.com', encrypted_email
122
+ assert_equal 'test@example.com', User.decrypt_email(encrypted_email)
123
+ end
124
+
125
+ def test_should_decrypt_email_when_reading
126
+ @user = User.new
127
+ assert_nil @user.email
128
+ @user.encrypted_email = User.encrypt_email('test@example.com')
129
+ assert_equal 'test@example.com', @user.email
130
+ end
131
+
132
+ def test_should_encrypt_with_encoding
133
+ assert_equal User.encrypt_with_encoding('test'), [User.encrypt_without_encoding('test')].pack('m')
134
+ end
135
+
136
+ def test_should_decrypt_with_encoding
137
+ encrypted = User.encrypt_with_encoding('test')
138
+ assert_equal 'test', User.decrypt_with_encoding(encrypted)
139
+ assert_equal User.decrypt_with_encoding(encrypted), User.decrypt_without_encoding(encrypted.unpack('m').first)
140
+ end
141
+
142
+ def test_should_encrypt_with_custom_encoding
143
+ assert_equal User.encrypt_with_encoding('test'), [User.encrypt_without_encoding('test')].pack('m')
144
+ end
145
+
146
+ def test_should_decrypt_with_custom_encoding
147
+ encrypted = User.encrypt_with_encoding('test')
148
+ assert_equal 'test', User.decrypt_with_encoding(encrypted)
149
+ assert_equal User.decrypt_with_encoding(encrypted), User.decrypt_without_encoding(encrypted.unpack('m').first)
150
+ end
151
+
152
+ def test_should_encrypt_with_marshaling
153
+ @user = User.new
154
+ @user.with_marshaling = [1, 2, 3]
155
+ assert_not_nil @user.encrypted_with_marshaling
156
+ assert_equal User.encrypt_with_marshaling([1, 2, 3]), @user.encrypted_with_marshaling
157
+ end
158
+
159
+ def test_should_decrypt_with_marshaling
160
+ encrypted = User.encrypt_with_marshaling([1, 2, 3])
161
+ @user = User.new
162
+ assert_nil @user.with_marshaling
163
+ @user.encrypted_with_marshaling = encrypted
164
+ assert_equal [1, 2, 3], @user.with_marshaling
165
+ end
166
+
167
+ def test_should_use_custom_encryptor_and_crypt_method_names_and_arguments
168
+ assert_equal SillyEncryptor.silly_encrypt(:value => 'testing', :some_arg => 'test'), User.encrypt_credit_card('testing')
169
+ end
170
+
171
+ def test_should_evaluate_a_key_passed_as_a_symbol
172
+ @user = User.new
173
+ assert_nil @user.ssn_encrypted
174
+ @user.ssn = 'testing'
175
+ assert_not_nil @user.ssn_encrypted
176
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => @user.salt), @user.ssn_encrypted
177
+ end
178
+
179
+ def test_should_evaluate_a_key_passed_as_a_proc
180
+ @user = User.new
181
+ assert_nil @user.crypted_password_test
182
+ @user.password = 'testing'
183
+ assert_not_nil @user.crypted_password_test
184
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'User'), @user.crypted_password_test
185
+ end
186
+
187
+ def test_should_use_options_found_in_the_attr_encrypted_options_attribute
188
+ @user = User.new
189
+ assert_nil @user.crypted_password_test
190
+ @user.password = 'testing'
191
+ assert_not_nil @user.crypted_password_test
192
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'User'), @user.crypted_password_test
193
+ end
194
+
195
+ def test_should_inherit_encrypted_attributes
196
+ assert_equal [User.encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, Admin.encrypted_attributes.keys.collect { |key| key.to_s }.sort
197
+ end
198
+
199
+ def test_should_inherit_attr_encrypted_options
200
+ assert !User.attr_encrypted_options.empty?
201
+ assert_equal User.attr_encrypted_options, Admin.attr_encrypted_options
202
+ end
203
+
204
+ def test_should_not_inherit_unrelated_attributes
205
+ assert SomeOtherClass.attr_encrypted_options.empty?
206
+ assert SomeOtherClass.encrypted_attributes.empty?
207
+ end
208
+
209
+ def test_should_evaluate_a_symbol_option
210
+ assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, :class)
211
+ end
212
+
213
+ def test_should_evaluate_a_proc_option
214
+ assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, proc { |object| object.class })
215
+ end
216
+
217
+ def test_should_evaluate_a_lambda_option
218
+ assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, lambda { |object| object.class })
219
+ end
220
+
221
+ def test_should_evaluate_a_method_option
222
+ assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, SomeOtherClass.method(:call))
223
+ end
224
+
225
+ def test_should_return_a_string_option
226
+ assert_equal 'Object', Object.new.send(:evaluate_attr_encrypted_option, 'Object')
227
+ end
228
+
229
+ def test_should_encrypt_with_true_if
230
+ @user = User.new
231
+ assert_nil @user.encrypted_with_true_if
232
+ @user.with_true_if = 'testing'
233
+ assert_not_nil @user.encrypted_with_true_if
234
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key'), @user.encrypted_with_true_if
235
+ end
236
+
237
+ def test_should_not_encrypt_with_false_if
238
+ @user = User.new
239
+ assert_nil @user.encrypted_with_false_if
240
+ @user.with_false_if = 'testing'
241
+ assert_not_nil @user.encrypted_with_false_if
242
+ assert_equal 'testing', @user.encrypted_with_false_if
243
+ end
244
+
245
+ def test_should_encrypt_with_false_unless
246
+ @user = User.new
247
+ assert_nil @user.encrypted_with_false_unless
248
+ @user.with_false_unless = 'testing'
249
+ assert_not_nil @user.encrypted_with_false_unless
250
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key'), @user.encrypted_with_false_unless
251
+ end
252
+
253
+ def test_should_not_encrypt_with_true_unless
254
+ @user = User.new
255
+ assert_nil @user.encrypted_with_true_unless
256
+ @user.with_true_unless = 'testing'
257
+ assert_not_nil @user.encrypted_with_true_unless
258
+ assert_equal 'testing', @user.encrypted_with_true_unless
259
+ end
260
+
261
+ def test_should_work_with_aliased_attr_encryptor
262
+ assert User.encrypted_attributes.include?(:aliased)
263
+ end
264
+
265
+ def test_should_always_reset_options
266
+ @user = User.new
267
+ @user.with_if_changed = "encrypt_stuff"
268
+ @user.stubs(:instance_variable_get).returns(nil)
269
+ @user.stubs(:instance_variable_set).raises("BadStuff")
270
+ assert_raise RuntimeError do
271
+ @user.with_if_changed
272
+ end
273
+
274
+ @user = User.new
275
+ @user.should_encrypt = false
276
+ @user.with_if_changed = "not_encrypted_stuff"
277
+ assert_equal "not_encrypted_stuff", @user.with_if_changed
278
+ assert_equal "not_encrypted_stuff", @user.encrypted_with_if_changed
279
+ end
280
+
281
+ def test_should_cast_values_as_strings_before_encrypting
282
+ string_encrypted_email = User.encrypt_email('3')
283
+ assert_equal string_encrypted_email, User.encrypt_email(3)
284
+ assert_equal '3', User.decrypt_email(string_encrypted_email)
285
+ end
286
+
287
+ def test_should_create_query_accessor
288
+ @user = User.new
289
+ assert !@user.email?
290
+ @user.email = ''
291
+ assert !@user.email?
292
+ @user.email = 'test@example.com'
293
+ assert @user.email?
294
+ end
295
+
296
+ def test_should_prefix_encryption_key_identifier_with_a_separator
297
+ string_encrypted_with_key_identifier = User.encrypt_with_key_identifier('blah')
298
+ assert_match /^2011::/, string_encrypted_with_key_identifier
299
+ end
300
+
301
+ def test_should_decrypt_with_appropriate_encryption_key
302
+ encrypted_2011 = '2011::' << Encryptor.encrypt(:value => 'testing', :key => 'current secret key')
303
+ assert_equal 'testing', User.decrypt_with_key_identifier(encrypted_2011)
304
+ end
305
+
306
+ def test_should_accept_many_keys_with_a_key_identifier
307
+ @user = User.new
308
+ assert_nil @user.with_key_identifier
309
+ @user.with_key_identifier = 'testing'
310
+ assert_not_nil @user.encrypted_with_key_identifier
311
+ assert_equal "2011::#{Encryptor.encrypt(:value => 'testing', :key => 'current secret key')}", @user.encrypted_with_key_identifier
312
+ end
313
+ end