attr_encrypted 1.1.2 → 1.2.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 +5 -11
- data/lib/attr_encrypted.rb +257 -189
- data/lib/attr_encrypted/adapters/active_record.rb +10 -8
- data/lib/attr_encrypted/adapters/data_mapper.rb +4 -4
- data/lib/attr_encrypted/adapters/sequel.rb +2 -2
- data/lib/attr_encrypted/version.rb +17 -0
- data/test/active_record_test.rb +30 -20
- data/test/attr_encrypted_test.rb +59 -60
- data/test/data_mapper_test.rb +10 -10
- data/test/sequel_test.rb +7 -7
- data/test/test_helper.rb +7 -3
- metadata +56 -18
data/README.rdoc
CHANGED
@@ -12,7 +12,6 @@ It works with ANY class, however, you get a few extra features when you're using
|
|
12
12
|
|
13
13
|
== Usage
|
14
14
|
|
15
|
-
|
16
15
|
=== Basic
|
17
16
|
|
18
17
|
Encrypting attributes has never been easier:
|
@@ -38,7 +37,7 @@ Encrypting attributes has never been easier:
|
|
38
37
|
@user = User.load
|
39
38
|
@user.ssn # decrypts :encrypted_ssn and returns '123-45-6789'
|
40
39
|
|
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
|
40
|
+
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 should have called this project <tt>attr_encryptor</tt> but it was too late when I realized it ='(.
|
42
41
|
|
43
42
|
|
44
43
|
=== Specifying the encrypted attribute name
|
@@ -246,6 +245,8 @@ You may want to encrypt objects other than strings (e.g. hashes, arrays, etc). I
|
|
246
245
|
attr_encrypted :credentials, :key => 'some secret key', :marshal => true
|
247
246
|
end
|
248
247
|
|
248
|
+
You may also optionally specify <tt>:marshaler</tt>, <tt>:dump_method</tt>, and <tt>:load_method</tt> if you want to use something other than the default <tt>Marshal</tt> object.
|
249
|
+
|
249
250
|
|
250
251
|
=== Encrypt/decrypt attribute methods
|
251
252
|
|
@@ -297,12 +298,5 @@ Just like the default options for <tt>ActiveRecord</tt>, the <tt>:encode</tt> op
|
|
297
298
|
* Make your feature addition or bug fix.
|
298
299
|
* Add tests for it. This is important so I don't break it in a
|
299
300
|
future version unintentionally.
|
300
|
-
* Commit, do not mess with rakefile, version, or history.
|
301
|
-
|
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
|
301
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
302
|
+
* Send me a pull request. Bonus points for topic branches.
|
data/lib/attr_encrypted.rb
CHANGED
@@ -1,26 +1,162 @@
|
|
1
|
-
require 'eigenclass'
|
2
1
|
require 'encryptor'
|
3
2
|
|
3
|
+
# Adds attr_accessors that encrypt and decrypt an object's attributes
|
4
4
|
module AttrEncrypted
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
autoload :Version, 'attr_encrypted/version'
|
6
|
+
|
7
|
+
def self.extended(base) # :nodoc:
|
8
|
+
base.class_eval do
|
9
|
+
include InstanceMethods
|
10
|
+
attr_writer :attr_encrypted_options
|
11
|
+
@attr_encrypted_options, @encrypted_attributes = {}, {}
|
12
|
+
end
|
8
13
|
end
|
9
|
-
|
10
|
-
#
|
14
|
+
|
15
|
+
# Generates attr_accessors that encrypt and decrypt attributes transparently
|
16
|
+
#
|
17
|
+
# Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
|
18
|
+
#
|
19
|
+
# :attribute => The name of the referenced encrypted attribute. For example
|
20
|
+
# <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
|
21
|
+
# attribute named 'ee' to store the encrypted email. This is useful when defining
|
22
|
+
# one attribute to encrypt at a time or when the :prefix and :suffix options
|
23
|
+
# aren't enough. Defaults to nil.
|
24
|
+
#
|
25
|
+
# :prefix => A prefix used to generate the name of the referenced encrypted attributes.
|
26
|
+
# For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
|
27
|
+
# generate attributes named 'crypted_email' and 'crypted_password' to store the
|
28
|
+
# encrypted email and password. Defaults to 'encrypted_'.
|
29
|
+
#
|
30
|
+
# :suffix => A suffix used to generate the name of the referenced encrypted attributes.
|
31
|
+
# For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
|
32
|
+
# would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
|
33
|
+
# encrypted email. Defaults to ''.
|
34
|
+
#
|
35
|
+
# :key => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
|
36
|
+
# a symbol representing an instance method then the :key option will be replaced with the result of the
|
37
|
+
# method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
|
38
|
+
# Any other key types will be passed directly to the encryptor.
|
39
|
+
#
|
40
|
+
# :encode => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
|
41
|
+
# planning on storing the encrypted attributes in a database. The default encoding is 'm' (base64),
|
42
|
+
# however this can be overwritten by setting the :encode option to some other encoding string instead of
|
43
|
+
# just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
|
44
|
+
# Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
|
45
|
+
#
|
46
|
+
# :default_encoding => Defaults to 'm' (base64).
|
47
|
+
#
|
48
|
+
# :marshal => If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning
|
49
|
+
# on encrypting something other than a string. Defaults to false unless you're using it with ActiveRecord
|
50
|
+
# or DataMapper.
|
51
|
+
#
|
52
|
+
# :marshaler => The object to use for marshaling. Defaults to Marshal.
|
53
|
+
#
|
54
|
+
# :dump_method => The dump method name to call on the <tt>:marshaler</tt> object to. Defaults to 'dump'.
|
55
|
+
#
|
56
|
+
# :load_method => The load method name to call on the <tt>:marshaler</tt> object. Defaults to 'load'.
|
11
57
|
#
|
12
|
-
#
|
58
|
+
# :encryptor => The object to use for encrypting. Defaults to Encryptor.
|
59
|
+
#
|
60
|
+
# :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'encrypt'.
|
61
|
+
#
|
62
|
+
# :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'decrypt'.
|
63
|
+
#
|
64
|
+
# :if => Attributes are only encrypted if this option evaluates to true. If you pass a symbol representing an instance
|
65
|
+
# method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
|
66
|
+
# Defaults to true.
|
67
|
+
#
|
68
|
+
# :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
|
69
|
+
# method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
|
70
|
+
# Defaults to false.
|
71
|
+
#
|
72
|
+
# You can specify your own default options
|
73
|
+
#
|
74
|
+
# class User
|
75
|
+
# # now all attributes will be encoded and marshaled by default
|
76
|
+
# attr_encrypted_options.merge!(:encode => true, :marshal => true, :some_other_option => true)
|
77
|
+
# attr_encrypted :configuration, :key => 'my secret key'
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
#
|
81
|
+
# Example
|
82
|
+
#
|
83
|
+
# class User
|
84
|
+
# attr_encrypted :email, :credit_card, :key => 'some secret key'
|
85
|
+
# attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# @user = User.new
|
89
|
+
# @user.encrypted_email # returns nil
|
90
|
+
# @user.email = 'test@example.com'
|
91
|
+
# @user.encrypted_email # returns the encrypted version of 'test@example.com'
|
92
|
+
#
|
93
|
+
# @user.configuration = { :time_zone => 'UTC' }
|
94
|
+
# @user.encrypted_configuration # returns the encrypted version of configuration
|
95
|
+
#
|
96
|
+
# See README for more examples
|
97
|
+
def attr_encrypted(*attributes)
|
98
|
+
options = {
|
99
|
+
:prefix => 'encrypted_',
|
100
|
+
:suffix => '',
|
101
|
+
:if => true,
|
102
|
+
:unless => false,
|
103
|
+
:encode => false,
|
104
|
+
:default_encoding => 'm',
|
105
|
+
:marshal => false,
|
106
|
+
:marshaler => Marshal,
|
107
|
+
:dump_method => 'dump',
|
108
|
+
:load_method => 'load',
|
109
|
+
:encryptor => Encryptor,
|
110
|
+
:encrypt_method => 'encrypt',
|
111
|
+
:decrypt_method => 'decrypt'
|
112
|
+
}.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})
|
113
|
+
|
114
|
+
options[:encode] = options[:default_encoding] if options[:encode] == true
|
115
|
+
|
116
|
+
attributes.each do |attribute|
|
117
|
+
encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
|
118
|
+
|
119
|
+
instance_methods_as_symbols = instance_methods.collect { |method| method.to_sym }
|
120
|
+
attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
|
121
|
+
attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
|
122
|
+
|
123
|
+
define_method(attribute) do
|
124
|
+
instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
|
125
|
+
end
|
126
|
+
|
127
|
+
define_method("#{attribute}=") do |value|
|
128
|
+
send("#{encrypted_attribute_name}=", encrypt(attribute, value))
|
129
|
+
instance_variable_set("@#{attribute}", value)
|
130
|
+
end
|
131
|
+
|
132
|
+
encrypted_attributes[attribute.to_sym] = options.merge!(:attribute => encrypted_attribute_name)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
alias_method :attr_encryptor, :attr_encrypted
|
136
|
+
|
137
|
+
# Default options to use with calls to <tt>attr_encrypted</tt>
|
138
|
+
#
|
139
|
+
# It will inherit existing options from its superclass
|
13
140
|
def attr_encrypted_options
|
14
|
-
@attr_encrypted_options ||= superclass.attr_encrypted_options.
|
141
|
+
@attr_encrypted_options ||= superclass.attr_encrypted_options.dup
|
15
142
|
end
|
16
|
-
|
17
|
-
#
|
18
|
-
|
19
|
-
|
143
|
+
|
144
|
+
# Checks if an attribute is configured with <tt>attr_encrypted</tt>
|
145
|
+
#
|
146
|
+
# Example
|
147
|
+
#
|
148
|
+
# class User
|
149
|
+
# attr_accessor :name
|
150
|
+
# attr_encrypted :email
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# User.attr_encrypted?(:name) # false
|
154
|
+
# User.attr_encrypted?(:email) # true
|
155
|
+
def attr_encrypted?(attribute)
|
156
|
+
encrypted_attributes.has_key?(attribute.to_sym)
|
20
157
|
end
|
21
|
-
|
22
|
-
#
|
23
|
-
# names as values
|
158
|
+
|
159
|
+
# Decrypts a value for the attribute specified
|
24
160
|
#
|
25
161
|
# Example
|
26
162
|
#
|
@@ -28,201 +164,133 @@ module AttrEncrypted
|
|
28
164
|
# attr_encrypted :email
|
29
165
|
# end
|
30
166
|
#
|
31
|
-
# User.
|
32
|
-
def
|
33
|
-
|
167
|
+
# email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
|
168
|
+
def decrypt(attribute, encrypted_value, options = {})
|
169
|
+
options = encrypted_attributes[attribute.to_sym].merge(options)
|
170
|
+
if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
|
171
|
+
encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
|
172
|
+
value = options[:encryptor].send(options[:decrypt_method], options.merge!(:value => encrypted_value))
|
173
|
+
value = options[:marshaler].send(options[:load_method], value) if options[:marshal]
|
174
|
+
value
|
175
|
+
else
|
176
|
+
encrypted_value
|
177
|
+
end
|
34
178
|
end
|
35
|
-
|
36
|
-
#
|
179
|
+
|
180
|
+
# Encrypts a value for the attribute specified
|
37
181
|
#
|
38
182
|
# Example
|
39
183
|
#
|
40
184
|
# class User
|
41
|
-
# attr_accessor :name
|
42
185
|
# attr_encrypted :email
|
43
186
|
# end
|
44
187
|
#
|
45
|
-
# User.
|
46
|
-
|
47
|
-
|
48
|
-
|
188
|
+
# encrypted_email = User.encrypt(:email, 'test@example.com')
|
189
|
+
def encrypt(attribute, value, options = {})
|
190
|
+
options = encrypted_attributes[attribute.to_sym].merge(options)
|
191
|
+
if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
|
192
|
+
value = options[:marshaler].send(options[:dump_method], value) if options[:marshal]
|
193
|
+
encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(:value => value))
|
194
|
+
encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
|
195
|
+
encrypted_value
|
196
|
+
else
|
197
|
+
value
|
198
|
+
end
|
49
199
|
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
#
|
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.
|
200
|
+
|
201
|
+
# Contains a hash of encrypted attributes with virtual attribute names as keys
|
202
|
+
# and their corresponding options as values
|
203
|
+
#
|
204
|
+
# Example
|
205
|
+
#
|
206
|
+
# class User
|
207
|
+
# attr_encrypted :email, :key => 'my secret key'
|
208
|
+
# end
|
209
|
+
#
|
210
|
+
# User.encrypted_attributes # { :email => { :attribute => 'encrypted_email', :key => 'my secret key' } }
|
211
|
+
def encrypted_attributes
|
212
|
+
@encrypted_attributes ||= superclass.encrypted_attributes.dup
|
213
|
+
end
|
214
|
+
|
215
|
+
# Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
|
216
|
+
# if attribute was configured with attr_encrypted
|
217
|
+
#
|
218
|
+
# Example
|
219
|
+
#
|
220
|
+
# class User
|
221
|
+
# attr_encrypted :email, :key => 'my secret key'
|
222
|
+
# end
|
223
|
+
#
|
224
|
+
# User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
|
225
|
+
def method_missing(method, *arguments, &block)
|
226
|
+
if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
|
227
|
+
send($1, $3, *arguments)
|
228
|
+
else
|
229
|
+
super
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
module InstanceMethods
|
234
|
+
# Decrypts a value for the attribute specified using options evaluated in the current object's scope
|
97
235
|
#
|
98
|
-
#
|
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.
|
236
|
+
# Example
|
101
237
|
#
|
102
|
-
#
|
238
|
+
# class User
|
239
|
+
# attr_accessor :secret_key
|
240
|
+
# attr_encrypted :email, :key => :secret_key
|
103
241
|
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
# end
|
242
|
+
# def initialize(secret_key)
|
243
|
+
# self.secret_key = secret_key
|
244
|
+
# end
|
245
|
+
# end
|
109
246
|
#
|
247
|
+
# @user = User.new('some-secret-key')
|
248
|
+
# @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
|
249
|
+
def decrypt(attribute, encrypted_value)
|
250
|
+
self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
|
251
|
+
end
|
252
|
+
|
253
|
+
# Encrypts a value for the attribute specified using options evaluated in the current object's scope
|
110
254
|
#
|
111
255
|
# Example
|
112
256
|
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
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'
|
257
|
+
# class User
|
258
|
+
# attr_accessor :secret_key
|
259
|
+
# attr_encrypted :email, :key => :secret_key
|
122
260
|
#
|
123
|
-
#
|
124
|
-
#
|
261
|
+
# def initialize(secret_key)
|
262
|
+
# self.secret_key = secret_key
|
263
|
+
# end
|
264
|
+
# end
|
125
265
|
#
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
266
|
+
# @user = User.new('some-secret-key')
|
267
|
+
# @user.encrypt(:email, 'test@example.com')
|
268
|
+
def encrypt(attribute, value)
|
269
|
+
self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
|
209
270
|
end
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
271
|
+
|
272
|
+
protected
|
273
|
+
|
274
|
+
# Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
|
275
|
+
def evaluated_attr_encrypted_options_for(attribute)
|
276
|
+
self.class.encrypted_attributes[attribute.to_sym].inject({}) { |hash, (option, value)| hash.merge!(option => evaluate_attr_encrypted_option(value)) }
|
222
277
|
end
|
223
|
-
|
278
|
+
|
279
|
+
# Evaluates symbol (method reference) or proc (responds to call) options
|
280
|
+
#
|
281
|
+
# If the option is not a symbol or proc then the original option is returned
|
282
|
+
def evaluate_attr_encrypted_option(option)
|
283
|
+
if option.is_a?(Symbol) && respond_to?(option)
|
284
|
+
send(option)
|
285
|
+
elsif option.respond_to?(:call)
|
286
|
+
option.call(self)
|
287
|
+
else
|
288
|
+
option
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
224
292
|
end
|
225
293
|
|
226
294
|
Object.extend AttrEncrypted
|
227
295
|
|
228
|
-
Dir[File.join(File.dirname(__FILE__), 'attr_encrypted', 'adapters', '*.rb')].each { |
|
296
|
+
Dir[File.join(File.dirname(__FILE__), 'attr_encrypted', 'adapters', '*.rb')].each { |adapter| require adapter }
|