attr_encrypted 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 CHANGED
@@ -17,6 +17,6 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
17
17
  rdoc.rdoc_dir = 'rdoc'
18
18
  rdoc.title = 'attr_encrypted'
19
19
  rdoc.options << '--line-numbers' << '--inline-source'
20
- rdoc.rdoc_files.include('README.markdown')
20
+ rdoc.rdoc_files.include('README*')
21
21
  rdoc.rdoc_files.include('lib/**/*.rb')
22
22
  end
@@ -1,226 +1,222 @@
1
- gem 'eigenclass', '>= 1.0.1'
2
1
  require 'eigenclass'
3
-
4
- gem 'encryptor', '>= 1.0.0'
5
2
  require 'encryptor'
6
3
 
7
- module Huberry
8
- module AttrEncrypted
9
- def self.extended(base)
10
- base.attr_encrypted_options = {}
11
- base.instance_variable_set('@encrypted_attributes', {})
12
- end
13
-
14
- # Default options to use with calls to <tt>attr_encrypted</tt>.
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
15
54
  #
16
- # It will inherit existing options from its parent class
17
- def attr_encrypted_options
18
- @attr_encrypted_options ||= superclass.attr_encrypted_options.nil? ? {} : superclass.attr_encrypted_options.dup
19
- end
20
-
21
- # Sets default options to use with calls to <tt>attr_encrypted</tt>.
22
- def attr_encrypted_options=(options)
23
- @attr_encrypted_options = options
24
- end
25
-
26
- # Contains a hash of encrypted attributes with virtual attribute names as keys and real attribute
27
- # names as values
55
+ # Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
28
56
  #
29
- # Example
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
30
103
  #
31
104
  # class User
32
- # attr_encrypted :email
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
33
108
  # end
34
109
  #
35
- # User.encrypted_attributes # { 'email' => 'encrypted_email' }
36
- def encrypted_attributes
37
- @encrypted_attributes ||= superclass.encrypted_attributes.nil? ? {} : superclass.encrypted_attributes.dup
38
- end
39
-
40
- # Checks if an attribute has been configured to be encrypted
41
110
  #
42
111
  # Example
43
112
  #
44
113
  # class User
45
- # attr_accessor :name
46
- # attr_encrypted :email
114
+ # attr_encrypted :email, :credit_card, :key => 'some secret key'
115
+ # attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
47
116
  # end
48
117
  #
49
- # User.attr_encrypted?(:name) # false
50
- # User.attr_encrypted?(:email) # true
51
- def attr_encrypted?(attribute)
52
- encrypted_attributes.keys.include?(attribute.to_s)
53
- end
54
-
55
- protected
56
-
57
- # Generates attr_accessors that encrypt and decrypt attributes transparently
58
- #
59
- # Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
60
- #
61
- # :attribute => The name of the referenced encrypted attribute. For example
62
- # <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
63
- # attribute named 'ee' to store the encrypted email. This is useful when defining
64
- # one attribute to encrypt at a time or when the :prefix and :suffix options
65
- # aren't enough. Defaults to nil.
66
- #
67
- # :prefix => A prefix used to generate the name of the referenced encrypted attributes.
68
- # For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
69
- # generate attributes named 'crypted_email' and 'crypted_password' to store the
70
- # encrypted email and password. Defaults to 'encrypted_'.
71
- #
72
- # :suffix => A suffix used to generate the name of the referenced encrypted attributes.
73
- # For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
74
- # would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
75
- # encrypted email. Defaults to ''.
76
- #
77
- # :key => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
78
- # a symbol representing an instance method then the :key option will be replaced with the result of the
79
- # method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
80
- # Any other key types will be passed directly to the encryptor.
81
- #
82
- # :encode => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
83
- # planning on storing the encrypted attributes in a database. The default encoding is 'm*' (base64),
84
- # however this can be overwritten by setting the :encode option to some other encoding string instead of
85
- # just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
86
- # Defaults to false unless you're using it with ActiveRecord or DataMapper.
87
- #
88
- # :marshal => If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning
89
- # on encrypting something other than a string. Defaults to false unless you're using it with ActiveRecord
90
- # or DataMapper.
91
- #
92
- # :encryptor => The object to use for encrypting. Defaults to Huberry::Encryptor.
93
- #
94
- # :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to :encrypt.
95
- #
96
- # :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to :decrypt.
97
- #
98
- # :if => Attributes are only encrypted if this option evaluates to true. 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 true.
101
- #
102
- # :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
103
- # method then the result of the method will be evaluated. Any objects that respond to :call are evaluated as well.
104
- # Defaults to false.
105
- #
106
- # You can specify your own default options
107
- #
108
- # class User
109
- # # now all attributes will be encoded and marshaled by default
110
- # attr_encrypted_options.merge!(:encode => true, :marshal => true, :some_other_option => true)
111
- # attr_encrypted :configuration
112
- # end
113
- #
114
- #
115
- # Example
116
- #
117
- # class User
118
- # attr_encrypted :email, :credit_card, :key => 'some secret key'
119
- # attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
120
- # end
121
- #
122
- # @user = User.new
123
- # @user.encrypted_email # returns nil
124
- # @user.email = 'test@example.com'
125
- # @user.encrypted_email # returns the encrypted version of 'test@example.com'
126
- #
127
- # @user.configuration = { :time_zone => 'UTC' }
128
- # @user.encrypted_configuration # returns the encrypted version of configuration
129
- #
130
- # See README for more examples
131
- def attr_encrypted(*attrs)
132
- options = {
133
- :prefix => 'encrypted_',
134
- :suffix => '',
135
- :encryptor => Huberry::Encryptor,
136
- :encrypt_method => :encrypt,
137
- :decrypt_method => :decrypt,
138
- :encode => false,
139
- :marshal => false,
140
- :if => true,
141
- :unless => false
142
- }.merge(attr_encrypted_options).merge(attrs.last.is_a?(Hash) ? attrs.pop : {})
143
- options[:encode] = 'm*' if options[:encode] == true
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
144
145
 
145
- attrs.each do |attribute|
146
- encrypted_attribute_name = options[:attribute].nil? ? options[:prefix].to_s + attribute.to_s + options[:suffix].to_s : options[:attribute].to_s
147
-
148
- encrypted_attributes[attribute.to_s] = encrypted_attribute_name
149
-
150
- attr_reader encrypted_attribute_name.to_sym unless instance_methods.include?(encrypted_attribute_name)
151
- attr_writer encrypted_attribute_name.to_sym unless instance_methods.include?("#{encrypted_attribute_name}=")
152
-
153
- define_class_method "encrypt_#{attribute}" do |value|
154
- if options[:if] && !options[:unless]
155
- if value.nil? || (value.is_a?(String) && value.empty?)
156
- encrypted_value = value
157
- else
158
- value = Marshal.dump(value) if options[:marshal]
159
- encrypted_value = options[:encryptor].send options[:encrypt_method], options.merge(:value => value)
160
- encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
161
- end
162
- encrypted_value
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
163
153
  else
164
- value
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]
165
157
  end
158
+ encrypted_value
159
+ else
160
+ value
166
161
  end
167
-
168
- define_class_method "decrypt_#{attribute}" do |encrypted_value|
169
- if options[:if] && !options[:unless]
170
- if encrypted_value.nil? || (encrypted_value.is_a?(String) && encrypted_value.empty?)
171
- decrypted_value = encrypted_value
172
- else
173
- encrypted_value = encrypted_value.unpack(options[:encode]).to_s if options[:encode]
174
- decrypted_value = options[:encryptor].send(options[:decrypt_method], options.merge(:value => encrypted_value))
175
- decrypted_value = Marshal.load(decrypted_value) if options[:marshal]
176
- end
177
- decrypted_value
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
178
168
  else
179
- encrypted_value
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]
180
172
  end
173
+ decrypted_value
174
+ else
175
+ encrypted_value
181
176
  end
182
-
183
- define_method "#{attribute}" do
184
- value = instance_variable_get("@#{attribute}")
185
- encrypted_value = send(encrypted_attribute_name.to_sym)
186
- original_options = [:key, :if, :unless].inject({}) do |hash, option|
187
- hash[option] = options[option]
188
- options[option] = self.class.send :evaluate_attr_encrypted_option, options[option], self
189
- hash
190
- end
191
- value = instance_variable_set("@#{attribute}", self.class.send("decrypt_#{attribute}".to_sym, encrypted_value)) if value.nil? && !encrypted_value.nil?
192
- options.merge!(original_options)
193
- value
177
+ end
178
+
179
+ define_method "#{attribute}" do
180
+ value = instance_variable_get("@#{attribute}")
181
+ encrypted_value = send(encrypted_attribute_name.to_sym)
182
+ original_options = [:key, :if, :unless].inject({}) do |hash, option|
183
+ hash[option] = options[option]
184
+ options[option] = self.class.send :evaluate_attr_encrypted_option, options[option], self
185
+ hash
194
186
  end
195
-
196
- define_method "#{attribute}=" do |value|
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
- options.merge!(original_options)
204
- instance_variable_set("@#{attribute}", value)
187
+ value = instance_variable_set("@#{attribute}", self.class.send("decrypt_#{attribute}".to_sym, encrypted_value)) if value.nil? && !encrypted_value.nil?
188
+ options.merge!(original_options)
189
+ value
190
+ end
191
+
192
+ define_method "#{attribute}=" do |value|
193
+ original_options = [:key, :if, :unless].inject({}) do |hash, option|
194
+ hash[option] = options[option]
195
+ options[option] = self.class.send :evaluate_attr_encrypted_option, options[option], self
196
+ hash
205
197
  end
198
+ send("#{encrypted_attribute_name}=".to_sym, self.class.send("encrypt_#{attribute}".to_sym, value))
199
+ options.merge!(original_options)
200
+ instance_variable_set("@#{attribute}", value)
206
201
  end
207
202
  end
208
-
209
- # Evaluates an option specified as a symbol representing an instance method or a proc
210
- #
211
- # If the option is not a symbol or proc then the original option is returned
212
- def evaluate_attr_encrypted_option(option, object)
213
- if option.is_a?(Symbol) && object.respond_to?(option)
214
- object.send(option)
215
- elsif option.respond_to?(:call)
216
- option.call(object)
217
- else
218
- option
219
- end
203
+ end
204
+ alias_method :attr_encryptor, :attr_encrypted
205
+
206
+ # Evaluates an option specified as a symbol representing an instance method or a proc
207
+ #
208
+ # If the option is not a symbol or proc then the original option is returned
209
+ def evaluate_attr_encrypted_option(option, object)
210
+ if option.is_a?(Symbol) && object.respond_to?(option)
211
+ object.send(option)
212
+ elsif option.respond_to?(:call)
213
+ option.call(object)
214
+ else
215
+ option
220
216
  end
221
- end
217
+ end
222
218
  end
223
219
 
224
- Object.extend Huberry::AttrEncrypted
220
+ Object.extend AttrEncrypted
225
221
 
226
- Dir[File.join(File.dirname(__FILE__), 'huberry', 'attr_encrypted', 'adapters', '*.rb')].each { |file| require file }
222
+ Dir[File.join(File.dirname(__FILE__), 'attr_encrypted', 'adapters', '*.rb')].each { |file| require file }