spectator-attr_encrypted 1.1.3

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