n8-attr_encrypted 1.1.2

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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Sean Huber - shuber@huberry.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,308 @@
1
+ = attr_encrypted
2
+
3
+ Generates attr_accessors that encrypt and decrypt attributes transparently
4
+
5
+ It works with ANY class, however, you get a few extra features when you're using it with <tt>ActiveRecord</tt>, <tt>DataMapper</tt>, or <tt>Sequel</tt>
6
+
7
+
8
+ == Installation
9
+
10
+ gem install attr_encrypted
11
+
12
+
13
+ == Usage
14
+
15
+
16
+ === Basic
17
+
18
+ Encrypting attributes has never been easier:
19
+
20
+ class User
21
+ attr_accessor :name
22
+ attr_encrypted :ssn, :key => 'a secret key'
23
+
24
+ def load
25
+ # loads the stored data
26
+ end
27
+
28
+ def save
29
+ # saves the :name and :encrypted_ssn attributes somewhere (e.g. filesystem, database, etc)
30
+ end
31
+ end
32
+
33
+ @user = User.new
34
+ @user.ssn = '123-45-6789'
35
+ @user.encrypted_ssn # returns the encrypted version of :ssn
36
+ @user.save
37
+
38
+ @user = User.load
39
+ @user.ssn # decrypts :encrypted_ssn and returns '123-45-6789'
40
+
41
+ The <tt>attr_encrypted</tt> method is also aliased as <tt>attr_encryptor</tt> to conform to Ruby's <tt>attr_</tt> naming conventions. I know that I should have called this project <tt>attr_encryptor</tt> but it was too late when I realized it ='(.
42
+
43
+
44
+ === Specifying the encrypted attribute name
45
+
46
+ By default, the encrypted attribute name is <tt>encrypted_#{attribute}</tt> (e.g. <tt>attr_encrypted :email</tt> would create an attribute named <tt>encrypted_email</tt>). So, if you're storing the encrypted attribute in the database, you need to make sure the <tt>encrypted_#{attribute}</tt> field exists in your table. You have a couple of options if you want to name your attribute something else.
47
+
48
+
49
+ ==== The <tt>:attribute</tt> option
50
+
51
+ You can simply pass the name of the encrypted attribute as the <tt>:attribute</tt> option:
52
+
53
+ class User
54
+ attr_encrypted :email, :key => 'a secret key', :attribute => 'email_encrypted'
55
+ end
56
+
57
+ This would generate an attribute named <tt>email_encrypted</tt>
58
+
59
+
60
+ ==== The <tt>:prefix</tt> and <tt>:suffix</tt> options
61
+
62
+ If you're planning on encrypting a few different attributes and you don't like the <tt>encrypted_#{attribute}</tt> naming convention then you can specify your own:
63
+
64
+ class User
65
+ attr_encrypted :email, :credit_card, :ssn, :key => 'a secret key', :prefix => 'secret_', :suffix => '_crypted'
66
+ end
67
+
68
+ This would generate the following attributes: <tt>secret_email_crypted</tt>, <tt>secret_credit_card_crypted</tt>, and <tt>secret_ssn_crypted</tt>.
69
+
70
+
71
+ === Encryption keys
72
+
73
+ Although a <tt>:key</tt> option may not be required (see custom encryptor below), it has a few special features
74
+
75
+
76
+ ==== Unique keys for each attribute
77
+
78
+ You can specify unique keys for each attribute if you'd like:
79
+
80
+ class User
81
+ attr_encrypted :email, :key => 'a secret key'
82
+ attr_encrypted :ssn, :key => 'a different secret key'
83
+ end
84
+
85
+
86
+ ==== Symbols representing instance methods as keys
87
+
88
+ If your class has an instance method that determines the encryption key to use, simply pass a symbol representing it like so:
89
+
90
+ class User
91
+ attr_encrypted :email, :key => :encryption_key
92
+
93
+ def encryption_key
94
+ # does some fancy logic and returns an encryption key
95
+ end
96
+ end
97
+
98
+
99
+ ==== Procs as keys
100
+
101
+ You can pass a proc/lambda object as the <tt>:key</tt> option as well:
102
+
103
+ class User
104
+ attr_encrypted :email, :key => proc { |user| user.key }
105
+ end
106
+
107
+ This can be used to create asymmetrical encryption by requiring users to provide their own encryption keys.
108
+
109
+
110
+ === Conditional encrypting
111
+
112
+ There may be times that you want to only encrypt when certain conditions are met. For example maybe you're using rails and you don't want to encrypt
113
+ attributes when you're in development mode. You can specify conditions like this:
114
+
115
+ class User < ActiveRecord::Base
116
+ attr_encrypted :email, :key => 'a secret key', :unless => Rails.env.development?
117
+ end
118
+
119
+ You can specify both <tt>:if</tt> and <tt>:unless</tt> options. If you pass a symbol representing an instance method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
120
+
121
+
122
+ === Custom encryptor
123
+
124
+ The <tt>Encryptor</tt> (see http://github.com/shuber/encryptor) class is used by default. You may use your own custom encryptor by specifying
125
+ the <tt>:encryptor</tt>, <tt>:encrypt_method</tt>, and <tt>:decrypt_method</tt> options
126
+
127
+ Lets suppose you'd like to use this custom encryptor class:
128
+
129
+ class SillyEncryptor
130
+ def self.silly_encrypt(options)
131
+ (options[:value] + options[:secret_key]).reverse
132
+ end
133
+
134
+ def self.silly_decrypt(options)
135
+ options[:value].reverse.gsub(/#{options[:secret_key]}$/, '')
136
+ end
137
+ end
138
+
139
+ Simply set up your class like so:
140
+
141
+ class User
142
+ attr_encrypted :email, :secret_key => 'a secret key', :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt
143
+ end
144
+
145
+ Any options that you pass to <tt>attr_encrypted</tt> will be passed to the encryptor along with the <tt>:value</tt> option which contains the string to encrypt/decrypt. Notice it uses <tt>:secret_key</tt> instead of <tt>:key</tt>.
146
+
147
+
148
+ === Custom algorithms
149
+
150
+ The default <tt>Encryptor</tt> uses the standard ruby OpenSSL library. It's default algorithm is <tt>aes-256-cbc</tt>. You can modify this by passing the <tt>:algorithm</tt> option to the <tt>attr_encrypted</tt> call like so:
151
+
152
+ class User
153
+ attr_encrypted :email, :key => 'a secret key', :algorithm => 'bf'
154
+ end
155
+
156
+ Run <tt>openssl list-cipher-commands</tt> to view a list of algorithms supported on your platform. See http://github.com/shuber/encryptor for more information.
157
+
158
+ aes-128-cbc
159
+ aes-128-ecb
160
+ aes-192-cbc
161
+ aes-192-ecb
162
+ aes-256-cbc
163
+ aes-256-ecb
164
+ base64
165
+ bf
166
+ bf-cbc
167
+ bf-cfb
168
+ bf-ecb
169
+ bf-ofb
170
+ cast
171
+ cast-cbc
172
+ cast5-cbc
173
+ cast5-cfb
174
+ cast5-ecb
175
+ cast5-ofb
176
+ des
177
+ des-cbc
178
+ des-cfb
179
+ des-ecb
180
+ des-ede
181
+ des-ede-cbc
182
+ des-ede-cfb
183
+ des-ede-ofb
184
+ des-ede3
185
+ des-ede3-cbc
186
+ des-ede3-cfb
187
+ des-ede3-ofb
188
+ des-ofb
189
+ des3
190
+ desx
191
+ idea
192
+ idea-cbc
193
+ idea-cfb
194
+ idea-ecb
195
+ idea-ofb
196
+ rc2
197
+ rc2-40-cbc
198
+ rc2-64-cbc
199
+ rc2-cbc
200
+ rc2-cfb
201
+ rc2-ecb
202
+ rc2-ofb
203
+ rc4
204
+ rc4-40
205
+
206
+
207
+ === Default options
208
+
209
+ Let's imagine that you have a few attributes that you want to encrypt with different keys, but you don't like the <tt>encrypted_#{attribute}</tt> naming convention. Instead of having to define your class like this:
210
+
211
+ class User
212
+ attr_encrypted :email, :key => 'a secret key', :prefix => '', :suffix => '_crypted'
213
+ attr_encrypted :ssn, :key => 'a different secret key', :prefix => '', :suffix => '_crypted'
214
+ attr_encrypted :credit_card, :key => 'another secret key', :prefix => '', :suffix => '_crypted'
215
+ end
216
+
217
+ You can simply define some default options like so:
218
+
219
+ class User
220
+ attr_encrypted_options.merge!(:prefix => '', :suffix => '_crypted')
221
+ attr_encrypted :email, :key => 'a secret key'
222
+ attr_encrypted :ssn, :key => 'a different secret key'
223
+ attr_encrypted :credit_card, :key => 'another secret key'
224
+ end
225
+
226
+ This should help keep your classes clean and DRY.
227
+
228
+
229
+ === Encoding
230
+
231
+ You're probably going to be storing your encrypted attributes somehow (e.g. filesystem, database, etc) and may run into some issues trying to store a weird
232
+ encrypted string. I've had this problem myself using MySQL. You can simply pass the <tt>:encode</tt> option to automatically encode/decode when encrypting/decrypting.
233
+
234
+ class User
235
+ attr_encrypted :email, :key => 'some secret key', :encode => true
236
+ end
237
+
238
+ The default encoding is <tt>m*</tt> (base64). You can change this by setting <tt>:encode => 'some encoding'</tt>. See the <tt>Array#pack</tt> method at http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding options.
239
+
240
+
241
+ === Marshaling
242
+
243
+ You may want to encrypt objects other than strings (e.g. hashes, arrays, etc). If this is the case, simply pass the <tt>:marshal</tt> option to automatically marshal when encrypting/decrypting.
244
+
245
+ class User
246
+ attr_encrypted :credentials, :key => 'some secret key', :marshal => true
247
+ end
248
+
249
+
250
+ === Encrypt/decrypt attribute methods
251
+
252
+ If you use the same key to encrypt every record (per attribute) like this:
253
+
254
+ class User
255
+ attr_encrypted :email, :key => 'a secret key'
256
+ end
257
+
258
+ Then you'll have these two class methods available for each attribute: <tt>User.encrypt_email(email_to_encrypt)</tt> and <tt>User.decrypt_email(email_to_decrypt)</tt>. This can be useful when you're using <tt>ActiveRecord</tt> (see below).
259
+
260
+
261
+ === ActiveRecord
262
+
263
+ If you're using this gem with <tt>ActiveRecord</tt>, you get a few extra features:
264
+
265
+
266
+ ==== Default options
267
+
268
+ For your convenience, the <tt>:encode</tt> option is set to true by default since you'll be storing everything in a database.
269
+
270
+
271
+ ==== Dynamic find_by_ and scoped_by_ methods
272
+
273
+ Let's say you'd like to encrypt your user's email addresses, but you also need a way for them to login. Simply set up your class like so:
274
+
275
+ class User < ActiveRecord::Base
276
+ attr_encrypted :email, :key => 'a secret key'
277
+ attr_encrypted :password, :key => 'some other secret key'
278
+ end
279
+
280
+ You can now lookup and login users like so:
281
+
282
+ User.find_by_email_and_password('test@example.com', 'testing')
283
+
284
+ The call to <tt>find_by_email_and_password</tt> is intercepted and modified to <tt>find_by_encrypted_email_and_encrypted_password('ENCRYPTED EMAIL', 'ENCRYPTED PASSWORD')</tt>. The dynamic scope methods like <tt>scoped_by_email_and_password</tt> work the same way.
285
+
286
+ NOTE: This only works if all records are encrypted with the same encryption key (per attribute).
287
+
288
+
289
+ === DataMapper and Sequel
290
+
291
+ Just like the default options for <tt>ActiveRecord</tt>, the <tt>:encode</tt> option is set to true by default since you'll be storing everything in a database.
292
+
293
+
294
+ == Note on Patches/Pull Requests
295
+
296
+ * Fork the project.
297
+ * Make your feature addition or bug fix.
298
+ * Add tests for it. This is important so I don't break it in a
299
+ future version unintentionally.
300
+ * Commit, do not mess with rakefile, version, or history.
301
+ (if you want to have your own version, that is fine but
302
+ bump version in a commit by itself I can ignore when I pull)
303
+ * Send me a pull request. Bonus points for topic branches.
304
+
305
+
306
+ == Contact
307
+
308
+ Problems, comments, and suggestions all welcome: shuber@huberry.com
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the attr_encrypted gem.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the attr_encrypted gem.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'attr_encrypted'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README*')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,228 @@
1
+ require 'eigenclass'
2
+ require 'encryptor'
3
+
4
+ module AttrEncrypted
5
+ def self.extended(base)
6
+ base.attr_encrypted_options = {}
7
+ base.instance_variable_set('@encrypted_attributes', {})
8
+ end
9
+
10
+ # Default options to use with calls to <tt>attr_encrypted</tt>.
11
+ #
12
+ # It will inherit existing options from its parent class
13
+ def attr_encrypted_options
14
+ @attr_encrypted_options ||= superclass.attr_encrypted_options.nil? ? {} : superclass.attr_encrypted_options.dup
15
+ end
16
+
17
+ # Sets default options to use with calls to <tt>attr_encrypted</tt>.
18
+ def attr_encrypted_options=(options)
19
+ @attr_encrypted_options = options
20
+ end
21
+
22
+ # Contains a hash of encrypted attributes with virtual attribute names as keys and real attribute
23
+ # names as values
24
+ #
25
+ # Example
26
+ #
27
+ # class User
28
+ # attr_encrypted :email
29
+ # end
30
+ #
31
+ # User.encrypted_attributes # { 'email' => 'encrypted_email' }
32
+ def encrypted_attributes
33
+ @encrypted_attributes ||= superclass.encrypted_attributes.nil? ? {} : superclass.encrypted_attributes.dup
34
+ end
35
+
36
+ # Checks if an attribute has been configured to be encrypted
37
+ #
38
+ # Example
39
+ #
40
+ # class User
41
+ # attr_accessor :name
42
+ # attr_encrypted :email
43
+ # end
44
+ #
45
+ # User.attr_encrypted?(:name) # false
46
+ # User.attr_encrypted?(:email) # true
47
+ def attr_encrypted?(attribute)
48
+ encrypted_attributes.keys.include?(attribute.to_s)
49
+ end
50
+
51
+ protected
52
+
53
+ # Generates attr_accessors that encrypt and decrypt attributes transparently
54
+ #
55
+ # Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
56
+ #
57
+ # :attribute => The name of the referenced encrypted attribute. For example
58
+ # <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
59
+ # attribute named 'ee' to store the encrypted email. This is useful when defining
60
+ # one attribute to encrypt at a time or when the :prefix and :suffix options
61
+ # aren't enough. Defaults to nil.
62
+ #
63
+ # :prefix => A prefix used to generate the name of the referenced encrypted attributes.
64
+ # For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
65
+ # generate attributes named 'crypted_email' and 'crypted_password' to store the
66
+ # encrypted email and password. Defaults to 'encrypted_'.
67
+ #
68
+ # :suffix => A suffix used to generate the name of the referenced encrypted attributes.
69
+ # For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
70
+ # would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
71
+ # encrypted email. Defaults to ''.
72
+ #
73
+ # :key => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
74
+ # a symbol representing an instance method then the :key option will be replaced with the result of the
75
+ # method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
76
+ # Any other key types will be passed directly to the encryptor.
77
+ #
78
+ # :encode => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
79
+ # planning on storing the encrypted attributes in a database. The default encoding is 'm*' (base64),
80
+ # however this can be overwritten by setting the :encode option to some other encoding string instead of
81
+ # just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
82
+ # Defaults to false unless you're using it with ActiveRecord or DataMapper.
83
+ #
84
+ # :marshal => If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning
85
+ # on encrypting something other than a string. Defaults to false unless you're using it with ActiveRecord
86
+ # or DataMapper.
87
+ #
88
+ # :encryptor => The object to use for encrypting. Defaults to Encryptor.
89
+ #
90
+ # :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to :encrypt.
91
+ #
92
+ # :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to :decrypt.
93
+ #
94
+ # :if => Attributes are only encrypted if this option evaluates to true. If you pass a symbol representing an instance
95
+ # method then the result of the method will be evaluated. Any objects that respond to :call are evaluated as well.
96
+ # Defaults to true.
97
+ #
98
+ # :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
99
+ # method then the result of the method will be evaluated. Any objects that respond to :call are evaluated as well.
100
+ # Defaults to false.
101
+ #
102
+ # You can specify your own default options
103
+ #
104
+ # class User
105
+ # # now all attributes will be encoded and marshaled by default
106
+ # attr_encrypted_options.merge!(:encode => true, :marshal => true, :some_other_option => true)
107
+ # attr_encrypted :configuration
108
+ # end
109
+ #
110
+ #
111
+ # Example
112
+ #
113
+ # class User
114
+ # attr_encrypted :email, :credit_card, :key => 'some secret key'
115
+ # attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
116
+ # end
117
+ #
118
+ # @user = User.new
119
+ # @user.encrypted_email # returns nil
120
+ # @user.email = 'test@example.com'
121
+ # @user.encrypted_email # returns the encrypted version of 'test@example.com'
122
+ #
123
+ # @user.configuration = { :time_zone => 'UTC' }
124
+ # @user.encrypted_configuration # returns the encrypted version of configuration
125
+ #
126
+ # See README for more examples
127
+ def attr_encrypted(*attrs)
128
+ options = {
129
+ :prefix => 'encrypted_',
130
+ :suffix => '',
131
+ :encryptor => Encryptor,
132
+ :encrypt_method => :encrypt,
133
+ :decrypt_method => :decrypt,
134
+ :encode => false,
135
+ :marshal => false,
136
+ :if => true,
137
+ :unless => false
138
+ }.merge(attr_encrypted_options).merge(attrs.last.is_a?(Hash) ? attrs.pop : {})
139
+ options[:encode] = 'm*' if options[:encode] == true
140
+
141
+ attrs.each do |attribute|
142
+ encrypted_attribute_name = options[:attribute].nil? ? options[:prefix].to_s + attribute.to_s + options[:suffix].to_s : options[:attribute].to_s
143
+
144
+ encrypted_attributes[attribute.to_s] = encrypted_attribute_name
145
+
146
+ attr_reader encrypted_attribute_name.to_sym unless instance_methods.include?(encrypted_attribute_name)
147
+ attr_writer encrypted_attribute_name.to_sym unless instance_methods.include?("#{encrypted_attribute_name}=")
148
+
149
+ define_class_method "encrypt_#{attribute}" do |value|
150
+ if options[:if] && !options[:unless]
151
+ if value.nil? || (value.is_a?(String) && value.empty?)
152
+ encrypted_value = value
153
+ else
154
+ value = Marshal.dump(value) if options[:marshal]
155
+ encrypted_value = options[:encryptor].send options[:encrypt_method], options.merge(:value => value)
156
+ encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
157
+ end
158
+ encrypted_value
159
+ else
160
+ value
161
+ end
162
+ end
163
+
164
+ define_class_method "decrypt_#{attribute}" do |encrypted_value|
165
+ if options[:if] && !options[:unless]
166
+ if encrypted_value.nil? || (encrypted_value.is_a?(String) && encrypted_value.empty?)
167
+ decrypted_value = encrypted_value
168
+ else
169
+ encrypted_value = encrypted_value.unpack(options[:encode]).to_s if options[:encode]
170
+ decrypted_value = options[:encryptor].send(options[:decrypt_method], options.merge(:value => encrypted_value))
171
+ decrypted_value = Marshal.load(decrypted_value) if options[:marshal]
172
+ end
173
+ decrypted_value
174
+ else
175
+ encrypted_value
176
+ end
177
+ end
178
+
179
+ define_method "#{attribute}" do
180
+ begin
181
+ value = instance_variable_get("@#{attribute}")
182
+ encrypted_value = send(encrypted_attribute_name.to_sym)
183
+ original_options = [:key, :if, :unless].inject({}) do |hash, option|
184
+ hash[option] = options[option]
185
+ options[option] = self.class.send :evaluate_attr_encrypted_option, options[option], self
186
+ hash
187
+ end
188
+ value = instance_variable_set("@#{attribute}", self.class.send("decrypt_#{attribute}".to_sym, encrypted_value)) if value.nil? && !encrypted_value.nil?
189
+ ensure
190
+ options.merge!(original_options)
191
+ end
192
+ value
193
+ end
194
+
195
+ define_method "#{attribute}=" do |value|
196
+ begin
197
+ original_options = [:key, :if, :unless].inject({}) do |hash, option|
198
+ hash[option] = options[option]
199
+ options[option] = self.class.send :evaluate_attr_encrypted_option, options[option], self
200
+ hash
201
+ end
202
+ send("#{encrypted_attribute_name}=".to_sym, self.class.send("encrypt_#{attribute}".to_sym, value))
203
+ ensure
204
+ options.merge!(original_options)
205
+ end
206
+ instance_variable_set("@#{attribute}", value)
207
+ end
208
+ end
209
+ end
210
+ alias_method :attr_encryptor, :attr_encrypted
211
+
212
+ # Evaluates an option specified as a symbol representing an instance method or a proc
213
+ #
214
+ # If the option is not a symbol or proc then the original option is returned
215
+ def evaluate_attr_encrypted_option(option, object)
216
+ if option.is_a?(Symbol) && object.respond_to?(option)
217
+ object.send(option)
218
+ elsif option.respond_to?(:call)
219
+ option.call(object)
220
+ else
221
+ option
222
+ end
223
+ end
224
+ end
225
+
226
+ Object.extend AttrEncrypted
227
+
228
+ Dir[File.join(File.dirname(__FILE__), 'attr_encrypted', 'adapters', '*.rb')].each { |file| require file }
@@ -0,0 +1,71 @@
1
+ if defined?(ActiveRecord)
2
+ module AttrEncrypted
3
+ module Adapters
4
+ module ActiveRecord
5
+
6
+ def self.extended(base)
7
+ base.attr_encrypted_options[:encode] = true
8
+ base.eigenclass_eval { alias_method_chain :method_missing, :attr_encrypted }
9
+ end
10
+
11
+
12
+ protected
13
+
14
+ module InstanceMethods
15
+
16
+ def resave_encrypted_attributes
17
+ self.class.encrypted_attributes.each do |attribute, encrypted_attribute_method|
18
+ send("#{attribute}=", instance_variable_get("@#{attribute}"))
19
+ end
20
+ end
21
+ end
22
+
23
+
24
+ # Ensures the attribute methods for db fields have been defined before calling the original
25
+ # <tt>attr_encrypted</tt> method
26
+ def attr_encrypted(*attrs)
27
+ define_attribute_methods rescue nil
28
+ super
29
+ attrs.reject { |attr| attr.is_a?(Hash) }.each { |attr| alias_method "#{attr}_before_type_cast", attr }
30
+
31
+ include InstanceMethods
32
+ before_save :resave_encrypted_attributes
33
+ end
34
+
35
+ # Allows you to use dynamic methods like <tt>find_by_email</tt> or <tt>scoped_by_email</tt> for
36
+ # encrypted attributes
37
+ #
38
+ # NOTE: This only works when the <tt>:key</tt> option is specified as a string (see the README)
39
+ #
40
+ # This is useful for encrypting fields like email addresses. Your user's email addresses
41
+ # are encrypted in the database, but you can still look up a user by email for logging in
42
+ #
43
+ # Example
44
+ #
45
+ # class User < ActiveRecord::Base
46
+ # attr_encrypted :email, :key => 'secret key'
47
+ # end
48
+ #
49
+ # User.find_by_email_and_password('test@example.com', 'testing')
50
+ # # results in a call to
51
+ # User.find_by_encrypted_email_and_password('the_encrypted_version_of_test@example.com', 'testing')
52
+ def method_missing_with_attr_encrypted(method, *args, &block)
53
+ if match = /^(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)$/.match(method.to_s)
54
+ attribute_names = match.captures.last.split('_and_')
55
+ attribute_names.each_with_index do |attribute, index|
56
+ if attr_encrypted?(attribute)
57
+ args[index] = send("encrypt_#{attribute}", args[index])
58
+ attribute_names[index] = encrypted_attributes[attribute]
59
+ end
60
+ end
61
+ method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
62
+ end
63
+ method_missing_without_attr_encrypted(method, *args, &block)
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+
70
+ ActiveRecord::Base.extend AttrEncrypted::Adapters::ActiveRecord
71
+ 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,123 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'ruby-debug'
3
+
4
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
5
+
6
+ def create_test_tables
7
+ silence_stream(STDOUT) do
8
+ ActiveRecord::Schema.define(:version => 1) do
9
+ create_table :people do |t|
10
+ t.string :encrypted_email
11
+ t.string :password
12
+ t.string :encrypted_credentials
13
+ t.string :salt
14
+ end
15
+
16
+ create_table :things do |t|
17
+ t.string :encrypted_name
18
+ t.integer :person_id
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # The table needs to exist before defining the class
25
+ create_test_tables
26
+
27
+ class Person < ActiveRecord::Base
28
+ has_many :things
29
+
30
+ attr_encrypted :email, :key => 'a secret key'
31
+ attr_encrypted :credentials, :key => Proc.new { |user| Encryptor.encrypt(:value => user.salt, :key => 'some private key') }, :marshal => true
32
+
33
+ def after_initialize
34
+ self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(5)).to_s)
35
+ self.credentials ||= { :username => 'example', :password => 'test' }
36
+ rescue ActiveRecord::MissingAttributeError
37
+ end
38
+
39
+ def wants_things_encrypted?
40
+ true
41
+ end
42
+ end
43
+
44
+ class Thing < ActiveRecord::Base
45
+ belongs_to :person
46
+
47
+ attr_encrypted :name, :key => 'a secret key', :if => Proc.new{|thing| thing.person.present? && thing.person.wants_things_encrypted?}
48
+ end
49
+
50
+ class PersonWithValidation < Person
51
+ validates_presence_of :email
52
+ validates_uniqueness_of :encrypted_email
53
+ end
54
+
55
+ class ActiveRecordTest < Test::Unit::TestCase
56
+
57
+ def setup
58
+ ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
59
+ create_test_tables
60
+ end
61
+
62
+ def test_should_encrypt_email
63
+ @person = Person.create :email => 'test@example.com'
64
+ assert_not_nil @person.encrypted_email
65
+ assert_not_equal @person.email, @person.encrypted_email
66
+ assert_equal @person.email, Person.find(:first).email
67
+ end
68
+
69
+ def test_should_marshal_and_encrypt_credentials
70
+ @person = Person.create
71
+ assert_not_nil @person.encrypted_credentials
72
+ assert_not_equal @person.credentials, @person.encrypted_credentials
73
+ assert_equal @person.credentials, Person.find(:first).credentials
74
+ end
75
+
76
+ def test_should_find_by_email
77
+ @person = Person.create(:email => 'test@example.com')
78
+ assert_equal @person, Person.find_by_email('test@example.com')
79
+ end
80
+
81
+ def test_should_find_by_email_and_password
82
+ Person.create(:email => 'test@example.com', :password => 'invalid')
83
+ @person = Person.create(:email => 'test@example.com', :password => 'test')
84
+ assert_equal @person, Person.find_by_email_and_password('test@example.com', 'test')
85
+ end
86
+
87
+ def test_should_scope_by_email
88
+ @person = Person.create(:email => 'test@example.com')
89
+ assert_equal @person, Person.scoped_by_email('test@example.com').find(:first) rescue NoMethodError
90
+ end
91
+
92
+ def test_should_scope_by_email_and_password
93
+ Person.create(:email => 'test@example.com', :password => 'invalid')
94
+ @person = Person.create(:email => 'test@example.com', :password => 'test')
95
+ assert_equal @person, Person.scoped_by_email_and_password('test@example.com', 'test').find(:first) rescue NoMethodError
96
+ end
97
+
98
+ def test_should_encode_by_default
99
+ assert Person.attr_encrypted_options[:encode]
100
+ end
101
+
102
+ def test_should_validate_presence_of_email
103
+ @person = PersonWithValidation.new
104
+ assert !@person.valid?
105
+ assert @person.errors.on(:email)
106
+ end
107
+
108
+ def test_should_validate_uniqueness_of_email
109
+ @person = PersonWithValidation.new :email => 'test@example.com'
110
+ assert @person.save
111
+ @person2 = PersonWithValidation.new :email => @person.email
112
+ assert !@person2.valid?
113
+ assert @person2.errors.on(:encrypted_email)
114
+ end
115
+
116
+ def test_should_not_encrypt_persons_things
117
+ person = Person.create(:email => 'test@example.com', :password => 'password')
118
+ thing = person.things.create(:name => "some test name")
119
+ assert thing.save
120
+ assert_not_equal thing.name, thing.encrypted_name
121
+ end
122
+
123
+ end
@@ -0,0 +1,272 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
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.encrypted_email
97
+ @user.email = 'test@example.com'
98
+ assert_not_nil @user.encrypted_email
99
+ assert_equal User.encrypt_email('test@example.com'), @user.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.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*').to_s)
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').to_s)
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.encrypted_with_marshaling
147
+ assert_equal User.encrypt_with_marshaling([1, 2, 3]), @user.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.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.ssn_encrypted
165
+ @user.ssn = 'testing'
166
+ assert_not_nil @user.ssn_encrypted
167
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => @user.salt), @user.ssn_encrypted
168
+ end
169
+
170
+ def test_should_evaluate_a_key_passed_as_a_proc
171
+ @user = User.new
172
+ assert_nil @user.crypted_password_test
173
+ @user.password = 'testing'
174
+ assert_not_nil @user.crypted_password_test
175
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'User'), @user.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.crypted_password_test
181
+ @user.password = 'testing'
182
+ assert_not_nil @user.crypted_password_test
183
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'User'), @user.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.encrypted_with_true_if
223
+ @user.with_true_if = 'testing'
224
+ assert_not_nil @user.encrypted_with_true_if
225
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key'), @user.encrypted_with_true_if
226
+ end
227
+
228
+ def test_should_not_encrypt_with_false_if
229
+ @user = User.new
230
+ assert_nil @user.encrypted_with_false_if
231
+ @user.with_false_if = 'testing'
232
+ assert_not_nil @user.encrypted_with_false_if
233
+ assert_equal 'testing', @user.encrypted_with_false_if
234
+ end
235
+
236
+ def test_should_encrypt_with_false_unless
237
+ @user = User.new
238
+ assert_nil @user.encrypted_with_false_unless
239
+ @user.with_false_unless = 'testing'
240
+ assert_not_nil @user.encrypted_with_false_unless
241
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key'), @user.encrypted_with_false_unless
242
+ end
243
+
244
+ def test_should_not_encrypt_with_true_unless
245
+ @user = User.new
246
+ assert_nil @user.encrypted_with_true_unless
247
+ @user.with_true_unless = 'testing'
248
+ assert_not_nil @user.encrypted_with_true_unless
249
+ assert_equal 'testing', @user.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.encrypted_with_if_changed
270
+ end
271
+
272
+ end
@@ -0,0 +1,52 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ DataMapper.setup(:default, "sqlite3::memory:")
4
+
5
+ class Client
6
+ include DataMapper::Resource
7
+
8
+ property :id, Serial
9
+ property :encrypted_email, String
10
+ property :encrypted_credentials, Text
11
+ property :salt, String
12
+
13
+ attr_encrypted :email, :key => 'a secret key'
14
+ attr_encrypted :credentials, :key => Proc.new { |client| Encryptor.encrypt(:value => client.salt, :key => 'some private key') }, :marshal => true
15
+
16
+ def initialize(attrs = {})
17
+ super attrs
18
+ self.salt ||= Digest::SHA1.hexdigest((Time.now.to_i * rand(5)).to_s)
19
+ self.credentials ||= { :username => 'example', :password => 'test' }
20
+ end
21
+ end
22
+
23
+ DataMapper.auto_migrate!
24
+
25
+ class DataMapperTest < Test::Unit::TestCase
26
+
27
+ def setup
28
+ Client.all.each(&:destroy)
29
+ end
30
+
31
+ def test_should_encrypt_email
32
+ @client = Client.new :email => 'test@example.com'
33
+ assert @client.save
34
+ assert_not_nil @client.encrypted_email
35
+ assert_not_equal @client.email, @client.encrypted_email
36
+ assert_equal @client.email, Client.first.email
37
+ end
38
+
39
+ def test_should_marshal_and_encrypt_credentials
40
+ @client = Client.new
41
+ assert @client.save
42
+ assert_not_nil @client.encrypted_credentials
43
+ assert_not_equal @client.credentials, @client.encrypted_credentials
44
+ assert_equal @client.credentials, Client.first.credentials
45
+ assert Client.first.credentials.is_a?(Hash)
46
+ end
47
+
48
+ def test_should_encode_by_default
49
+ assert Client.attr_encrypted_options[:encode]
50
+ end
51
+
52
+ end
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ DB = Sequel.sqlite
4
+
5
+ DB.create_table :humans do
6
+ primary_key :id
7
+ column :encrypted_email, :string
8
+ column :password, :string
9
+ column :encrypted_credentials, :string
10
+ column :salt, :string
11
+ end
12
+
13
+ class Human < Sequel::Model(:humans)
14
+ attr_encrypted :email, :key => 'a secret key'
15
+ attr_encrypted :credentials, :key => Proc.new { |human| Encryptor.encrypt(:value => human.salt, :key => 'some private key') }, :marshal => true
16
+
17
+ def after_initialize(attrs = {})
18
+ self.salt ||= Digest::SHA1.hexdigest((Time.now.to_i * rand(5)).to_s)
19
+ self.credentials ||= { :username => 'example', :password => 'test' }
20
+ end
21
+ end
22
+
23
+ class SequelTest < Test::Unit::TestCase
24
+
25
+ def setup
26
+ Human.all.each(&:destroy)
27
+ end
28
+
29
+ def test_should_encrypt_email
30
+ @human = Human.new :email => 'test@example.com'
31
+ assert @human.save
32
+ assert_not_nil @human.encrypted_email
33
+ assert_not_equal @human.email, @human.encrypted_email
34
+ assert_equal @human.email, Human.first.email
35
+ end
36
+
37
+ def test_should_marshal_and_encrypt_credentials
38
+ @human = Human.new
39
+ assert @human.save
40
+ assert_not_nil @human.encrypted_credentials
41
+ assert_not_equal @human.credentials, @human.encrypted_credentials
42
+ assert_equal @human.credentials, Human.first.credentials
43
+ assert Human.first.credentials.is_a?(Hash)
44
+ end
45
+
46
+ def test_should_encode_by_default
47
+ assert Human.attr_encrypted_options[:encode]
48
+ end
49
+
50
+ end
@@ -0,0 +1,10 @@
1
+ require 'test/unit'
2
+ require 'digest/sha2'
3
+
4
+ require 'rubygems'
5
+
6
+ require 'active_record'
7
+ require 'datamapper'
8
+ require 'sequel'
9
+
10
+ require File.dirname(__FILE__) + '/../lib/attr_encrypted'
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: n8-attr_encrypted
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 1
9
+ - 2
10
+ version: 1.1.2
11
+ platform: ruby
12
+ authors:
13
+ - Sean Huber
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-04-26 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: eigenclass
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 17
30
+ segments:
31
+ - 1
32
+ - 1
33
+ - 1
34
+ version: 1.1.1
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: encryptor
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 19
46
+ segments:
47
+ - 1
48
+ - 1
49
+ - 0
50
+ version: 1.1.0
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: mocha
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 43
62
+ segments:
63
+ - 0
64
+ - 9
65
+ - 8
66
+ version: 0.9.8
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ description: Generates attr_accessors that encrypt and decrypt attributes transparently. A fork with a kludge to handle :if/:unless Procs with attr_encrypted that uses attributes that may have not been set yet before the original attr_encrypted does its thing. This basically just resaves all the encrypted_attributes in a before_save callback.
70
+ email: shuber@huberry.com
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files: []
76
+
77
+ files:
78
+ - lib/attr_encrypted.rb
79
+ - lib/attr_encrypted/adapters/active_record.rb
80
+ - lib/attr_encrypted/adapters/data_mapper.rb
81
+ - lib/attr_encrypted/adapters/sequel.rb
82
+ - MIT-LICENSE
83
+ - Rakefile
84
+ - README.rdoc
85
+ - test/active_record_test.rb
86
+ - test/attr_encrypted_test.rb
87
+ - test/data_mapper_test.rb
88
+ - test/sequel_test.rb
89
+ - test/test_helper.rb
90
+ has_rdoc: true
91
+ homepage: http://github.com/shuber/attr_encrypted
92
+ licenses: []
93
+
94
+ post_install_message:
95
+ rdoc_options:
96
+ - --line-numbers
97
+ - --inline-source
98
+ - --main
99
+ - README.rdoc
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ requirements: []
121
+
122
+ rubyforge_project:
123
+ rubygems_version: 1.3.7
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Generates attr_accessors that encrypt and decrypt attributes transparently
127
+ test_files:
128
+ - test/active_record_test.rb
129
+ - test/attr_encrypted_test.rb
130
+ - test/data_mapper_test.rb
131
+ - test/sequel_test.rb
132
+ - test/test_helper.rb