attr_encryptor 1.0.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/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ pkg
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ rdoc
8
+ spec/reports
9
+ test/tmp
10
+ test/version_tmp
11
+ tmp
12
+
13
+ # YARD artifacts
14
+ .yardoc
15
+ _yardoc
16
+ doc/
data/.rvmrc ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
+ # development environment upon cd'ing into the directory
5
+
6
+ # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
+ environment_id="ruby-1.9.2-p290@attr_encrypted"
8
+
9
+ #
10
+ # Uncomment following line if you want options to be set only for given project.
11
+ #
12
+ # PROJECT_JRUBY_OPTS=( --1.9 )
13
+
14
+ #
15
+ # First we attempt to load the desired environment directly from the environment
16
+ # file. This is very fast and efficient compared to running through the entire
17
+ # CLI and selector. If you want feedback on which environment was used then
18
+ # insert the word 'use' after --create as this triggers verbose mode.
19
+ #
20
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
21
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
22
+ then
23
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
24
+
25
+ if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
26
+ then
27
+ . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
28
+ fi
29
+ else
30
+ # If the environment file has not yet been created, use the RVM CLI to select.
31
+ if ! rvm --create "$environment_id"
32
+ then
33
+ echo "Failed to create RVM environment '${environment_id}'."
34
+ exit 1
35
+ fi
36
+ fi
37
+
38
+ #
39
+ # If you use an RVM gemset file to install a list of gems (*.gems), you can have
40
+ # it be automatically loaded. Uncomment the following and adjust the filename if
41
+ # necessary.
42
+ #
43
+ # filename=".gems"
44
+ # if [[ -s "$filename" ]] ; then
45
+ # rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
46
+ # fi
47
+
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.md ADDED
@@ -0,0 +1,291 @@
1
+ # attr_encryptor
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 `ActiveRecord`, `DataMapper`, or `Sequel`
6
+
7
+
8
+ ## Installation
9
+
10
+ gem install attr_encryptor
11
+
12
+ ## Usage
13
+
14
+ ### Basic
15
+
16
+ Encrypting attributes has never been easier:
17
+
18
+ ### You database
19
+
20
+ add a `encrypted_ssn`, `encrypted_ssn_salt`, `encrypted_ssn_iv`. All of
21
+ them will be populated automatically
22
+
23
+ create_table :google_apps_admins do |t|
24
+ t.string :username
25
+ t.string :encrypted_password
26
+ t.string :encrypted_password_iv
27
+ t.string :domain
28
+ t.timestamps
29
+ end
30
+
31
+ ### Your model
32
+
33
+ class User
34
+ attr_accessor :name
35
+ attr_encrypted :ssn, :key => 'a secret key'
36
+
37
+ end
38
+
39
+ ### Controllers/views
40
+
41
+
42
+ @user = User.new
43
+ @user.ssn = '123-45-6789'
44
+ @user.ssn # returns the unencrypted version of :ssn
45
+ @user.save
46
+
47
+ @user = User.load
48
+ @user.ssn # decrypts :encrypted_ssn and returns '123-45-6789'
49
+
50
+ The `attr_encrypted` method is also aliased as `attr_encryptor` to conform to Ruby's `attr_` naming conventions.
51
+
52
+
53
+ ### Specifying the encrypted attribute name
54
+
55
+ By default, the encrypted attribute name is `encrypted_#{attribute}` (e.g. `attr_encrypted :email` would create an attribute named `encrypted_email`). So, if you're storing the encrypted attribute in the database, you need to make sure the `encrypted_#{attribute}` field exists in your table(as well as `encrypted_#{attribute}_iv` and `encrypted_#{attribute}_salt`). You have a couple of options if you want to name your attribute something else.
56
+
57
+ #### The `:attribute` option
58
+
59
+ You can simply pass the name of the encrypted attribute as the `:attribute` option:
60
+
61
+ class User
62
+ attr_encrypted :email, :key => 'a secret key', :attribute => 'email_encrypted'
63
+ end
64
+
65
+ This would generate an attribute named `email_encrypted`
66
+
67
+ #### The `:prefix` and `:suffix` options
68
+
69
+ If you're planning on encrypting a few different attributes and you don't like the `encrypted_#{attribute}` naming convention then you can specify your own:
70
+
71
+ class User
72
+ attr_encrypted :email, :credit_card, :ssn, :key => 'a secret key', :prefix => 'secret_', :suffix => '_crypted'
73
+ end
74
+
75
+ This would generate the following attributes: `secret_email_crypted`, `secret_credit_card_crypted`, and `secret_ssn_crypted`.
76
+
77
+
78
+ ### Encryption keys
79
+
80
+ Although a `:key` option may not be required (see custom encryptor below), it has a few special features
81
+
82
+ #### Unique keys for each attribute
83
+
84
+ You can specify unique keys for each attribute if you'd like:
85
+
86
+ class User
87
+ attr_encrypted :email, :key => 'a secret key'
88
+ attr_encrypted :ssn, :key => 'a different secret key'
89
+ end
90
+
91
+
92
+ #### Symbols representing instance methods as keys
93
+
94
+ If your class has an instance method that determines the encryption key to use, simply pass a symbol representing it like so:
95
+
96
+ class User
97
+ attr_encrypted :email, :key => :encryption_key
98
+
99
+ def encryption_key
100
+ # does some fancy logic and returns an encryption key
101
+ end
102
+ end
103
+
104
+
105
+ #### Procs as keys
106
+
107
+ You can pass a proc/lambda object as the `:key` option as well:
108
+
109
+ class User
110
+ attr_accessor :key
111
+ attr_encrypted :email, :key => proc { |user| user.key }
112
+ end
113
+
114
+ However when calling `User.new`, `User.create`, `User.update_attributes` the `:key` attribute has to precede the `:email` or key will be nil and you'll get an Exception,
115
+
116
+ ### Conditional encrypting
117
+
118
+ 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
119
+ attributes when you're in development mode. You can specify conditions like this:
120
+
121
+ class User < ActiveRecord::Base
122
+ attr_encrypted :email, :key => 'a secret key', :unless => Rails.env.development?
123
+ end
124
+
125
+ You can specify both `:if` and `:unless` options. If you pass a symbol representing an instance method then the result of the method will be evaluated. Any objects that respond to `:call` are evaluated as well.
126
+
127
+
128
+ ### Custom encryptor
129
+
130
+ The `Encryptor` (see http://github.com/shuber/encryptor) class is used by default. You may use your own custom encryptor by specifying
131
+ the `:encryptor`, `:encrypt_method`, and `:decrypt_method` options
132
+
133
+ Lets suppose you'd like to use this custom encryptor class:
134
+
135
+ class SillyEncryptor
136
+ def self.silly_encrypt(options)
137
+ (options[:value] + options[:secret_key]).reverse
138
+ end
139
+
140
+ def self.silly_decrypt(options)
141
+ options[:value].reverse.gsub(/#{options[:secret_key]}$/, '')
142
+ end
143
+ end
144
+
145
+ Simply set up your class like so:
146
+
147
+ class User
148
+ attr_encrypted :email, :secret_key => 'a secret key', :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt
149
+ end
150
+
151
+ Any options that you pass to `attr_encrypted` will be passed to the encryptor along with the `:value` option which contains the string to encrypt/decrypt. Notice it uses `:secret_key` instead of `:key`.
152
+
153
+
154
+ ### Custom algorithms
155
+
156
+ The default `Encryptor` uses the standard ruby OpenSSL library. It's default algorithm is `aes-256-cbc`. You can modify this by passing the `:algorithm` option to the `attr_encrypted` call like so:
157
+
158
+ class User
159
+ attr_encrypted :email, :key => 'a secret key', :algorithm => 'bf'
160
+ end
161
+
162
+ Run `openssl list-cipher-commands` to view a list of algorithms supported on your platform. See http://github.com/danpal/encryptor for more information.
163
+
164
+ aes-128-cbc
165
+ aes-128-ecb
166
+ aes-192-cbc
167
+ aes-192-ecb
168
+ aes-256-cbc
169
+ aes-256-ecb
170
+ base64
171
+ bf
172
+ bf-cbc
173
+ bf-cfb
174
+ bf-ecb
175
+ bf-ofb
176
+ cast
177
+ cast-cbc
178
+ cast5-cbc
179
+ cast5-cfb
180
+ cast5-ecb
181
+ cast5-ofb
182
+ des
183
+ des-cbc
184
+ des-cfb
185
+ des-ecb
186
+ des-ede
187
+ des-ede-cbc
188
+ des-ede-cfb
189
+ des-ede-ofb
190
+ des-ede3
191
+ des-ede3-cbc
192
+ des-ede3-cfb
193
+ des-ede3-ofb
194
+ des-ofb
195
+ des3
196
+ desx
197
+ idea
198
+ idea-cbc
199
+ idea-cfb
200
+ idea-ecb
201
+ idea-ofb
202
+ rc2
203
+ rc2-40-cbc
204
+ rc2-64-cbc
205
+ rc2-cbc
206
+ rc2-cfb
207
+ rc2-ecb
208
+ rc2-ofb
209
+ rc4
210
+ rc4-40
211
+
212
+
213
+ ### Default options
214
+
215
+ Let's imagine that you have a few attributes that you want to encrypt with different keys, but you don't like the `encrypted_#{attribute}` naming convention. Instead of having to define your class like this:
216
+
217
+ class User
218
+ attr_encrypted :email, :key => 'a secret key', :prefix => '', :suffix => '_crypted'
219
+ attr_encrypted :ssn, :key => 'a different secret key', :prefix => '', :suffix => '_crypted'
220
+ attr_encrypted :credit_card, :key => 'another secret key', :prefix => '', :suffix => '_crypted'
221
+ end
222
+
223
+ You can simply define some default options like so:
224
+
225
+ class User
226
+ attr_encrypted_options.merge!(:prefix => '', :suffix => '_crypted')
227
+ attr_encrypted :email, :key => 'a secret key'
228
+ attr_encrypted :ssn, :key => 'a different secret key'
229
+ attr_encrypted :credit_card, :key => 'another secret key'
230
+ end
231
+
232
+ This should help keep your classes clean and DRY.
233
+
234
+
235
+ ### Encoding
236
+
237
+ 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
238
+ encrypted string. I've had this problem myself using MySQL. You can simply pass the `:encode` option to automatically encode/decode when encrypting/decrypting.
239
+
240
+ class User
241
+ attr_encrypted :email, :key => 'some secret key', :encode => true
242
+ end
243
+
244
+ The default encoding is `m*` (base64). You can change this by setting `:encode => 'some encoding'`. See the `Array#pack` method at http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding options.
245
+
246
+
247
+ ### Marshaling
248
+
249
+ You may want to encrypt objects other than strings (e.g. hashes, arrays, etc). If this is the case, simply pass the `:marshal` option to automatically marshal when encrypting/decrypting.
250
+
251
+ class User
252
+ attr_encrypted :credentials, :key => 'some secret key', :marshal => true
253
+ end
254
+
255
+ You may also optionally specify `:marshaler`, `:dump_method`, and `:load_method` if you want to use something other than the default `Marshal` object.
256
+
257
+
258
+ ### Encrypt/decrypt attribute methods
259
+
260
+ If you use the same key to encrypt every record (per attribute) like this:
261
+
262
+ class User
263
+ attr_encrypted :email, :key => 'a secret key'
264
+ end
265
+
266
+ Then you'll have these two class methods available for each attribute: `User.encrypt_email(email_to_encrypt)` and `User.decrypt_email(email_to_decrypt)`. This can be useful when you're using `ActiveRecord` (see below).
267
+
268
+
269
+ ### ActiveRecord
270
+
271
+ If you're using this gem with `ActiveRecord`, you get a few extra features:
272
+
273
+
274
+ #### Default options
275
+
276
+ For your convenience, the `:encode` option is set to true by default since you'll be storing everything in a database.
277
+
278
+
279
+ ### DataMapper and Sequel
280
+
281
+ Just like the default options for `ActiveRecord`, the `:encode` option is set to true by default since you'll be storing everything in a database.
282
+
283
+
284
+ ## Note on Patches/Pull Requests
285
+
286
+ * Fork the project.
287
+ * Make your feature addition or bug fix.
288
+ * Add tests for it. This is important so I don't break it in a
289
+ future version unintentionally.
290
+ * 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)
291
+ * Send me a pull request. Bonus points for topic branches.
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_encryptor 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_encryptor gem.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'attr_encryptor'
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,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib/', __FILE__)
4
+ $:.unshift lib unless $:.include?(lib)
5
+
6
+ require 'attr_encryptor/version'
7
+ require 'date'
8
+
9
+ Gem::Specification.new do |s|
10
+ s.name = 'attr_encryptor'
11
+ s.version = AttrEncryptor::Version.string
12
+ s.date = Date.today
13
+
14
+ s.summary = 'Encrypt and decrypt attributes'
15
+ s.description = 'Generates attr_accessors that encrypt and decrypt attributes transparently'
16
+
17
+ s.author = 'Daniel Palacio'
18
+ s.email = 'danpal@gmail.com'
19
+ s.homepage = 'http://github.com/danpal/attr_encrypted'
20
+
21
+ s.has_rdoc = false
22
+ s.rdoc_options = ['--line-numbers', '--inline-source', '--main', 'README.rdoc']
23
+
24
+ s.require_paths = ['lib']
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+
29
+ s.add_dependency('encryptor2', ['>= 1.1.1'])
30
+ s.add_development_dependency('activerecord', ['>= 2.0.0'])
31
+ s.add_development_dependency('datamapper')
32
+ s.add_development_dependency('mocha')
33
+ s.add_development_dependency('sequel')
34
+ s.add_development_dependency('dm-sqlite-adapter')
35
+ s.add_development_dependency('sqlite3')
36
+ end
@@ -0,0 +1,326 @@
1
+ require 'encryptor'
2
+ require 'openssl'
3
+
4
+ # Adds attr_accessors that encrypt and decrypt an object's attributes
5
+ module AttrEncryptor
6
+ autoload :Version, 'attr_encryptor/version'
7
+
8
+ def self.extended(base) # :nodoc:
9
+ base.class_eval do
10
+ include InstanceMethods
11
+ attr_writer :attr_encrypted_options
12
+ @attr_encrypted_options, @encrypted_attributes = {}, {}
13
+ end
14
+ end
15
+
16
+ # Generates attr_accessors that encrypt and decrypt attributes transparently
17
+ #
18
+ # Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
19
+ #
20
+ # :attribute => The name of the referenced encrypted attribute. For example
21
+ # <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
22
+ # attribute named 'ee' to store the encrypted email. This is useful when defining
23
+ # one attribute to encrypt at a time or when the :prefix and :suffix options
24
+ # aren't enough. Defaults to nil.
25
+ #
26
+ # :prefix => A prefix used to generate the name of the referenced encrypted attributes.
27
+ # For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
28
+ # generate attributes named 'crypted_email' and 'crypted_password' to store the
29
+ # encrypted email and password. Defaults to 'encrypted_'.
30
+ #
31
+ # :suffix => A suffix used to generate the name of the referenced encrypted attributes.
32
+ # For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
33
+ # would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
34
+ # encrypted email. Defaults to ''.
35
+ #
36
+ # :key => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
37
+ # a symbol representing an instance method then the :key option will be replaced with the result of the
38
+ # method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
39
+ # Any other key types will be passed directly to the encryptor.
40
+ #
41
+ # :encode => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
42
+ # planning on storing the encrypted attributes in a database. The default encoding is 'm' (base64),
43
+ # however this can be overwritten by setting the :encode option to some other encoding string instead of
44
+ # just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
45
+ # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
46
+ #
47
+ # :default_encoding => Defaults to 'm' (base64).
48
+ #
49
+ # :marshal => If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning
50
+ # on encrypting something other than a string. Defaults to false unless you're using it with ActiveRecord
51
+ # or DataMapper.
52
+ #
53
+ # :marshaler => The object to use for marshaling. Defaults to Marshal.
54
+ #
55
+ # :dump_method => The dump method name to call on the <tt>:marshaler</tt> object to. Defaults to 'dump'.
56
+ #
57
+ # :load_method => The load method name to call on the <tt>:marshaler</tt> object. Defaults to 'load'.
58
+ #
59
+ # :encryptor => The object to use for encrypting. Defaults to Encryptor.
60
+ #
61
+ # :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'encrypt'.
62
+ #
63
+ # :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'decrypt'.
64
+ #
65
+ # :if => Attributes are only encrypted if this option evaluates to true. If you pass a symbol representing an instance
66
+ # method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
67
+ # Defaults to true.
68
+ #
69
+ # :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
70
+ # method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
71
+ # Defaults to false.
72
+ #
73
+ # You can specify your own default options
74
+ #
75
+ # class User
76
+ # # now all attributes will be encoded and marshaled by default
77
+ # attr_encrypted_options.merge!(:encode => true, :marshal => true, :some_other_option => true)
78
+ # attr_encrypted :configuration, :key => 'my secret key'
79
+ # end
80
+ #
81
+ #
82
+ # Example
83
+ #
84
+ # class User
85
+ # attr_encrypted :email, :credit_card, :key => 'some secret key'
86
+ # attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
87
+ # end
88
+ #
89
+ # @user = User.new
90
+ # @user.encrypted_email # nil
91
+ # @user.email? # false
92
+ # @user.email = 'test@example.com'
93
+ # @user.email? # true
94
+ # @user.encrypted_email # returns the encrypted version of 'test@example.com'
95
+ #
96
+ # @user.configuration = { :time_zone => 'UTC' }
97
+ # @user.encrypted_configuration # returns the encrypted version of configuration
98
+ #
99
+ # See README for more examples
100
+ def attr_encrypted(*attributes)
101
+ options = {
102
+ :prefix => 'encrypted_',
103
+ :suffix => '',
104
+ :if => true,
105
+ :unless => false,
106
+ :encode => false,
107
+ :default_encoding => 'm',
108
+ :marshal => false,
109
+ :marshaler => Marshal,
110
+ :dump_method => 'dump',
111
+ :load_method => 'load',
112
+ :encryptor => Encryptor,
113
+ :encrypt_method => 'encrypt',
114
+ :decrypt_method => 'decrypt'
115
+ }.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})
116
+
117
+ options[:encode] = options[:default_encoding] if options[:encode] == true
118
+
119
+ attributes.each do |attribute|
120
+ encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
121
+
122
+ instance_methods_as_symbols = instance_methods.collect { |method| method.to_sym }
123
+ attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
124
+ attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
125
+
126
+ attr_reader (encrypted_attribute_name.to_s + "_iv").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_iv").to_sym )
127
+ attr_writer (encrypted_attribute_name.to_s + "_iv").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_iv").to_sym )
128
+
129
+ attr_reader (encrypted_attribute_name.to_s + "_salt").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_salt").to_sym )
130
+ attr_writer (encrypted_attribute_name.to_s + "_salt").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_salt").to_sym )
131
+
132
+
133
+
134
+ define_method(attribute) do
135
+ instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
136
+ end
137
+
138
+ define_method("#{attribute}=") do |value|
139
+ iv = send("#{encrypted_attribute_name.to_s + "_iv"}")
140
+ if(iv == nil)
141
+ begin
142
+ algorithm = options[:algorithm] || "aes-256-cbc"
143
+ algo = OpenSSL::Cipher::Cipher.new(algorithm)
144
+ iv = [algo.random_iv].pack("m")
145
+ send("#{encrypted_attribute_name.to_s + "_iv"}=", iv)
146
+ rescue RuntimeError
147
+ end
148
+ end
149
+
150
+ salt = send("#{encrypted_attribute_name.to_s + "_salt"}") || send("#{encrypted_attribute_name.to_s + "_salt"}=", Time.now.to_i.to_s)
151
+ #this add's the iv and salt on the options for this instance
152
+ self.class.encrypted_attributes[attribute.to_sym] = self.class.encrypted_attributes[attribute.to_sym].merge(:iv => iv.unpack("m").first) if (iv && !iv.empty?)
153
+ self.class.encrypted_attributes[attribute.to_sym] = self.class.encrypted_attributes[attribute.to_sym].merge(:salt => salt)
154
+ send("#{encrypted_attribute_name}=", encrypt(attribute, value))
155
+ instance_variable_set("@#{attribute}", value)
156
+ end
157
+
158
+ define_method("#{attribute}?") do
159
+ value = send(attribute)
160
+ value.respond_to?(:empty?) ? !value.empty? : !!value
161
+ end
162
+ encrypted_attributes[attribute.to_sym] = options.merge(:attribute => encrypted_attribute_name)
163
+ end
164
+ end
165
+ alias_method :attr_encryptor, :attr_encrypted
166
+
167
+ # Default options to use with calls to <tt>attr_encrypted</tt>
168
+ #
169
+ # It will inherit existing options from its superclass
170
+ def attr_encrypted_options
171
+ @attr_encrypted_options ||= superclass.attr_encrypted_options.dup
172
+ end
173
+
174
+ # Checks if an attribute is configured with <tt>attr_encrypted</tt>
175
+ #
176
+ # Example
177
+ #
178
+ # class User
179
+ # attr_accessor :name
180
+ # attr_encrypted :email
181
+ # end
182
+ #
183
+ # User.attr_encrypted?(:name) # false
184
+ # User.attr_encrypted?(:email) # true
185
+ def attr_encrypted?(attribute)
186
+ encrypted_attributes.has_key?(attribute.to_sym)
187
+ end
188
+
189
+ # Decrypts a value for the attribute specified
190
+ #
191
+ # Example
192
+ #
193
+ # class User
194
+ # attr_encrypted :email
195
+ # end
196
+ #
197
+ # email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
198
+ def decrypt(attribute, encrypted_value, options = {})
199
+ options = encrypted_attributes[attribute.to_sym].merge(options)
200
+ if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
201
+ encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
202
+ value = options[:encryptor].send(options[:decrypt_method], options.merge!(:value => encrypted_value))
203
+ value = options[:marshaler].send(options[:load_method], value) if options[:marshal]
204
+ value
205
+ else
206
+ encrypted_value
207
+ end
208
+ end
209
+
210
+ # Encrypts a value for the attribute specified
211
+ #
212
+ # Example
213
+ #
214
+ # class User
215
+ # attr_encrypted :email
216
+ # end
217
+ #
218
+ # encrypted_email = User.encrypt(:email, 'test@example.com')
219
+ def encrypt(attribute, value, options = {})
220
+ options = encrypted_attributes[attribute.to_sym].merge(options)
221
+ if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
222
+ value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
223
+ encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(:value => value))
224
+ encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
225
+ encrypted_value
226
+ else
227
+ value
228
+ end
229
+ end
230
+
231
+ # Contains a hash of encrypted attributes with virtual attribute names as keys
232
+ # and their corresponding options as values
233
+ #
234
+ # Example
235
+ #
236
+ # class User
237
+ # attr_encrypted :email, :key => 'my secret key'
238
+ # end
239
+ #
240
+ # User.encrypted_attributes # { :email => { :attribute => 'encrypted_email', :key => 'my secret key' } }
241
+ def encrypted_attributes
242
+ @encrypted_attributes ||= superclass.encrypted_attributes.dup
243
+ end
244
+
245
+ # Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
246
+ # if attribute was configured with attr_encrypted
247
+ #
248
+ # Example
249
+ #
250
+ # class User
251
+ # attr_encrypted :email, :key => 'my secret key'
252
+ # end
253
+ #
254
+ # User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
255
+ def method_missing(method, *arguments, &block)
256
+ if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
257
+ send($1, $3, *arguments)
258
+ else
259
+ super
260
+ end
261
+ end
262
+
263
+ module InstanceMethods
264
+ # Decrypts a value for the attribute specified using options evaluated in the current object's scope
265
+ #
266
+ # Example
267
+ #
268
+ # class User
269
+ # attr_accessor :secret_key
270
+ # attr_encrypted :email, :key => :secret_key
271
+ #
272
+ # def initialize(secret_key)
273
+ # self.secret_key = secret_key
274
+ # end
275
+ # end
276
+ #
277
+ # @user = User.new('some-secret-key')
278
+ # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
279
+ def decrypt(attribute, encrypted_value)
280
+ self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
281
+ end
282
+
283
+ # Encrypts a value for the attribute specified using options evaluated in the current object's scope
284
+ #
285
+ # Example
286
+ #
287
+ # class User
288
+ # attr_accessor :secret_key
289
+ # attr_encrypted :email, :key => :secret_key
290
+ #
291
+ # def initialize(secret_key)
292
+ # self.secret_key = secret_key
293
+ # end
294
+ # end
295
+ #
296
+ # @user = User.new('some-secret-key')
297
+ # @user.encrypt(:email, 'test@example.com')
298
+ def encrypt(attribute, value)
299
+ self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
300
+ end
301
+
302
+ protected
303
+
304
+ # Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
305
+ def evaluated_attr_encrypted_options_for(attribute)
306
+ self.class.encrypted_attributes[attribute.to_sym].inject({}) { |hash, (option, value)| hash.merge!(option => evaluate_attr_encrypted_option(value)) }
307
+ end
308
+
309
+ # Evaluates symbol (method reference) or proc (responds to call) options
310
+ #
311
+ # If the option is not a symbol or proc then the original option is returned
312
+ def evaluate_attr_encrypted_option(option)
313
+ if option.is_a?(Symbol) && respond_to?(option)
314
+ send(option)
315
+ elsif option.respond_to?(:call)
316
+ option.call(self)
317
+ else
318
+ option
319
+ end
320
+ end
321
+ end
322
+ end
323
+
324
+ Object.extend AttrEncryptor
325
+
326
+ Dir[File.join(File.dirname(__FILE__), 'attr_encryptor', 'adapters', '*.rb')].each { |adapter| require adapter }