attr_encryption 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +68 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +34 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/attr_encryption.rb +345 -0
- data/lib/attr_encryption/adapters/active_record.rb +58 -0
- data/lib/attr_encryption/date_extensions.rb +13 -0
- data/lib/attr_encryption/mysql_encryption.rb +73 -0
- data/lib/attr_encryption/mysql_encryptor.rb +33 -0
- data/spec/mysql_encryption_spec.rb +109 -0
- data/spec/mysql_encryptor_spec.rb +132 -0
- data/spec/spec_helper.rb +14 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: acd5e4eab70392658a813fc269594f9172725646
|
4
|
+
data.tar.gz: b517d95ae98b5cd7537ff0cf40ed52275c632b85
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: caf52c3ef1935397886f25c58290c78a3344632c8373bd223a9cbd5d1398a87ed1be5351fbd4fb33142f892ff7c86761a50fc57b935e9f1c61a0026ee0a2373c
|
7
|
+
data.tar.gz: 87e5a93df2785e93b0d83af5bee82808cff9753f755ff8e27fbb3fe3083751070752c9adafa5f6f8d7a1f27d0a4220493244378cb797440e026aa811b966c36b
|
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
gem "activesupport", ">= 3.2.14"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec"
|
10
|
+
gem "rdoc", "~> 3.12"
|
11
|
+
gem "bundler", "~> 1.0"
|
12
|
+
gem "jeweler", "~> 1.8.7"
|
13
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (3.2.14)
|
5
|
+
i18n (~> 0.6, >= 0.6.4)
|
6
|
+
multi_json (~> 1.0)
|
7
|
+
addressable (2.3.5)
|
8
|
+
builder (3.2.2)
|
9
|
+
diff-lcs (1.2.4)
|
10
|
+
faraday (0.8.8)
|
11
|
+
multipart-post (~> 1.2.0)
|
12
|
+
git (1.2.6)
|
13
|
+
github_api (0.10.1)
|
14
|
+
addressable
|
15
|
+
faraday (~> 0.8.1)
|
16
|
+
hashie (>= 1.2)
|
17
|
+
multi_json (~> 1.4)
|
18
|
+
nokogiri (~> 1.5.2)
|
19
|
+
oauth2
|
20
|
+
hashie (2.0.5)
|
21
|
+
highline (1.6.19)
|
22
|
+
httpauth (0.2.0)
|
23
|
+
i18n (0.6.5)
|
24
|
+
jeweler (1.8.7)
|
25
|
+
builder
|
26
|
+
bundler (~> 1.0)
|
27
|
+
git (>= 1.2.5)
|
28
|
+
github_api (= 0.10.1)
|
29
|
+
highline (>= 1.6.15)
|
30
|
+
nokogiri (= 1.5.10)
|
31
|
+
rake
|
32
|
+
rdoc
|
33
|
+
json (1.8.0)
|
34
|
+
jwt (0.1.8)
|
35
|
+
multi_json (>= 1.5)
|
36
|
+
multi_json (1.8.0)
|
37
|
+
multi_xml (0.5.5)
|
38
|
+
multipart-post (1.2.0)
|
39
|
+
nokogiri (1.5.10)
|
40
|
+
oauth2 (0.9.2)
|
41
|
+
faraday (~> 0.8)
|
42
|
+
httpauth (~> 0.2)
|
43
|
+
jwt (~> 0.1.4)
|
44
|
+
multi_json (~> 1.0)
|
45
|
+
multi_xml (~> 0.5)
|
46
|
+
rack (~> 1.2)
|
47
|
+
rack (1.5.2)
|
48
|
+
rake (10.1.0)
|
49
|
+
rdoc (3.12.2)
|
50
|
+
json (~> 1.4)
|
51
|
+
rspec (2.14.1)
|
52
|
+
rspec-core (~> 2.14.0)
|
53
|
+
rspec-expectations (~> 2.14.0)
|
54
|
+
rspec-mocks (~> 2.14.0)
|
55
|
+
rspec-core (2.14.5)
|
56
|
+
rspec-expectations (2.14.3)
|
57
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
58
|
+
rspec-mocks (2.14.3)
|
59
|
+
|
60
|
+
PLATFORMS
|
61
|
+
ruby
|
62
|
+
|
63
|
+
DEPENDENCIES
|
64
|
+
activesupport (>= 3.2.14)
|
65
|
+
bundler (~> 1.0)
|
66
|
+
jeweler (~> 1.8.7)
|
67
|
+
rdoc (~> 3.12)
|
68
|
+
rspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Dave
|
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,34 @@
|
|
1
|
+
= attr_encryption
|
2
|
+
|
3
|
+
This gem enhances ActiveRecord models with the ability to quickly and easily
|
4
|
+
define which attributes of the model are to be encrypted. Once the attributes
|
5
|
+
have been identified, the gem takes care of all the details.
|
6
|
+
|
7
|
+
For example, if you want to encrypt the card number of a CreditCard model, you
|
8
|
+
would simply code the following:
|
9
|
+
|
10
|
+
class CreditCard < ActiveRecord::Base
|
11
|
+
attr_encrypted :card_number
|
12
|
+
end
|
13
|
+
|
14
|
+
All that is required at this point is a column on the 'credit_cards' table named
|
15
|
+
'card_number_enc'.
|
16
|
+
|
17
|
+
There are a variety of options that can be applied to the attr_encrypted call to customize
|
18
|
+
how the encryption proceeds, but more on that later.
|
19
|
+
|
20
|
+
== Contributing to attr_encryption
|
21
|
+
|
22
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
23
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
24
|
+
* Fork the project.
|
25
|
+
* Start a feature/bugfix branch.
|
26
|
+
* Commit and push until you are happy with your contribution.
|
27
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
28
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
29
|
+
|
30
|
+
== Copyright
|
31
|
+
|
32
|
+
Copyright (c) 2013 Providigm. See LICENSE.txt for
|
33
|
+
further details.
|
34
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "attr_encryption"
|
18
|
+
gem.homepage = "http://github.com/GitHubAdmin/attr_encryption"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Extends Object and ActiveRecord::Base objects to support encrypted attributes}
|
21
|
+
gem.description = %Q{Provides an extension for Ruby and rails object to support a flexible means of encrypting attributes.}
|
22
|
+
gem.email = "dave.sieh@providigm.com"
|
23
|
+
gem.authors = ["Dave Sieh"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "attr_encryption #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,345 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'attr_encryption/date_extensions'
|
3
|
+
require 'attr_encryption/mysql_encryption'
|
4
|
+
require 'attr_encryption/mysql_encryptor'
|
5
|
+
|
6
|
+
# Adds attr_accessors that encrypt and decrypt an object's attributes
|
7
|
+
module AttrEncryption
|
8
|
+
|
9
|
+
def self.extended(base) # :nodoc:
|
10
|
+
base.class_eval do
|
11
|
+
include InstanceMethods
|
12
|
+
attr_writer :attr_encrypted_options
|
13
|
+
@attr_encrypted_options, @encrypted_attributes = {}, {}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Generates attr_accessors that encrypt and decrypt attributes transparently
|
18
|
+
#
|
19
|
+
# Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
|
20
|
+
#
|
21
|
+
# :attribute => The name of the referenced encrypted attribute. For example
|
22
|
+
# <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
|
23
|
+
# attribute named 'ee' to store the encrypted email. This is useful when defining
|
24
|
+
# one attribute to encrypt at a time or when the :prefix and :suffix options
|
25
|
+
# aren't enough. Defaults to nil.
|
26
|
+
#
|
27
|
+
# :type => The data type of the value to be encrypted/decrypted. Can be 'date', 'datetime', 'binary' or 'text'.
|
28
|
+
# When encrypting, all values will use their string value (value.to_s). When decrypting,
|
29
|
+
# the type of the value will determine what is returned. For example:
|
30
|
+
#
|
31
|
+
# type = 'date': Date.parse(decrypted_value)
|
32
|
+
# type = 'time': DateTime.parse(decrypted_value)
|
33
|
+
# type = 'binary': decrypted_value
|
34
|
+
# type = 'text': decrypted_value.force_encoding('utf-8')
|
35
|
+
#
|
36
|
+
# :prefix => A prefix used to generate the name of the referenced encrypted attributes.
|
37
|
+
# For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
|
38
|
+
# generate attributes named 'crypted_email' and 'crypted_password' to store the
|
39
|
+
# encrypted email and password. Defaults to ''.
|
40
|
+
#
|
41
|
+
# :suffix => A suffix used to generate the name of the referenced encrypted attributes.
|
42
|
+
# For example <tt>attr_accessor :email, :password, :suffix => '_encrypted'</tt>
|
43
|
+
# would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
|
44
|
+
# encrypted email. Defaults to '_enc'.
|
45
|
+
#
|
46
|
+
# :preencrypt => The symbol identifying a method that should be run on an attribute immediately prior
|
47
|
+
# to marshalling and encrypting. This could be used for things like stripping white-space
|
48
|
+
# from values or other sorts of pre-processing. Defaults to nil.
|
49
|
+
#
|
50
|
+
# :key => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
|
51
|
+
# a symbol representing an instance method then the :key option will be replaced with the result of the
|
52
|
+
# method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
|
53
|
+
# Any other key types will be passed directly to the encryptor. TODO (DJS): We'll see if we need this.
|
54
|
+
#
|
55
|
+
# :encode => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
|
56
|
+
# planning on storing the encrypted attributes in a database. The default encoding is 'm' (base64),
|
57
|
+
# however this can be overwritten by setting the :encode option to some other encoding string instead of
|
58
|
+
# just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
|
59
|
+
# Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel. TODO(DJS): We'll see if we need this.
|
60
|
+
#
|
61
|
+
# :default_encoding => Defaults to 'm' (base64). TODO(DJS): Hmmm. See above
|
62
|
+
#
|
63
|
+
# :marshal => If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning
|
64
|
+
# on encrypting something other than a string. Defaults to false unless you're using it with ActiveRecord
|
65
|
+
# or DataMapper. TODO(DJS): Don't want to use this by default in our encryption since we want to be able to query...
|
66
|
+
#
|
67
|
+
# :marshaler => The object to use for marshaling. Defaults to Marshal.
|
68
|
+
#
|
69
|
+
# :dump_method => The dump method name to call on the <tt>:marshaler</tt> object to. Defaults to 'dump'.
|
70
|
+
#
|
71
|
+
# :load_method => The load method name to call on the <tt>:marshaler</tt> object. Defaults to 'load'.
|
72
|
+
#
|
73
|
+
# :encryptor => The object to use for encrypting. Defaults to Encryptor. TODO(DJS): Need to changed this to indicate our encryptor
|
74
|
+
#
|
75
|
+
# :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'encrypt'. TODO(DJS): Verify this.
|
76
|
+
#
|
77
|
+
# :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'decrypt'. TODO(DJS): Verify this.
|
78
|
+
#
|
79
|
+
# :if => Attributes are only encrypted if this option evaluates to true. If you pass a symbol representing an instance
|
80
|
+
# method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
|
81
|
+
# Defaults to true.
|
82
|
+
#
|
83
|
+
# :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
|
84
|
+
# method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
|
85
|
+
# Defaults to false.
|
86
|
+
#
|
87
|
+
# You can specify your own default options
|
88
|
+
#
|
89
|
+
# TODO(DJS): Need to rework the examples.
|
90
|
+
# class User
|
91
|
+
# # now all attributes will be encoded and marshaled by default
|
92
|
+
# attr_encrypted_options.merge!(:encode => true, :marshal => true, :some_other_option => true)
|
93
|
+
# attr_encrypted :configuration, :key => 'my secret key'
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
#
|
97
|
+
# Example
|
98
|
+
#
|
99
|
+
# class User
|
100
|
+
# attr_encrypted :email, :credit_card, :key => 'some secret key'
|
101
|
+
# attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# @user = User.new
|
105
|
+
# @user.encrypted_email # nil
|
106
|
+
# @user.email? # false
|
107
|
+
# @user.email = 'test@example.com'
|
108
|
+
# @user.email? # true
|
109
|
+
# @user.encrypted_email # returns the encrypted version of 'test@example.com'
|
110
|
+
#
|
111
|
+
# @user.configuration = { :time_zone => 'UTC' }
|
112
|
+
# @user.encrypted_configuration # returns the encrypted version of configuration
|
113
|
+
#
|
114
|
+
# See README for more examples
|
115
|
+
def attr_encrypted(*attributes)
|
116
|
+
options = {
|
117
|
+
:prefix => '',
|
118
|
+
:suffix => '_enc',
|
119
|
+
:if => true,
|
120
|
+
:unless => false,
|
121
|
+
:encode => false,
|
122
|
+
:key => $encryption_key,
|
123
|
+
:type => 'text',
|
124
|
+
:default_encoding => 'm',
|
125
|
+
:preenrypt => nil,
|
126
|
+
:marshal => false,
|
127
|
+
:marshaler => Marshal,
|
128
|
+
:dump_method => 'dump',
|
129
|
+
:load_method => 'load',
|
130
|
+
:encryptor => MySQLEncryptor.instance,
|
131
|
+
:encrypt_method => 'encrypt',
|
132
|
+
:decrypt_method => 'decrypt'
|
133
|
+
}.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})
|
134
|
+
|
135
|
+
options[:encode] = options[:default_encoding] if options[:encode] == true
|
136
|
+
|
137
|
+
attributes.each do |attribute|
|
138
|
+
encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
|
139
|
+
|
140
|
+
instance_methods_as_symbols = instance_methods.collect { |method| method.to_sym }
|
141
|
+
attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
|
142
|
+
attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
|
143
|
+
|
144
|
+
define_method(attribute) do
|
145
|
+
cached_value = instance_variable_get("@#{attribute}")
|
146
|
+
value = cached_value && options[:type] == 'date' && cached_value.is_a?(Date) ? cached_value : nil
|
147
|
+
value || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
|
148
|
+
# instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
|
149
|
+
end
|
150
|
+
|
151
|
+
define_method("#{attribute}=") do |value|
|
152
|
+
send("#{encrypted_attribute_name}=", encrypt(attribute, value))
|
153
|
+
instance_variable_set("@#{attribute}", value)
|
154
|
+
end
|
155
|
+
|
156
|
+
define_method("#{attribute}?") do
|
157
|
+
value = send(attribute)
|
158
|
+
value.respond_to?(:empty?) ? !value.empty? : !!value
|
159
|
+
end
|
160
|
+
|
161
|
+
encrypted_attributes[attribute.to_sym] = options.merge(:attribute => encrypted_attribute_name)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
alias_method :attr_encryptor, :attr_encrypted
|
165
|
+
|
166
|
+
# Default options to use with calls to <tt>attr_encrypted</tt>
|
167
|
+
#
|
168
|
+
# It will inherit existing options from its superclass
|
169
|
+
def attr_encrypted_options
|
170
|
+
@attr_encrypted_options ||= superclass.attr_encrypted_options.dup
|
171
|
+
end
|
172
|
+
|
173
|
+
# Checks if an attribute is configured with <tt>attr_encrypted</tt>
|
174
|
+
#
|
175
|
+
# Example
|
176
|
+
#
|
177
|
+
# class User
|
178
|
+
# attr_accessor :name
|
179
|
+
# attr_encrypted :email
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# User.attr_encrypted?(:name) # false
|
183
|
+
# User.attr_encrypted?(:email) # true
|
184
|
+
def attr_encrypted?(attribute)
|
185
|
+
encrypted_attributes.has_key?(attribute.to_sym)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Decrypts a value for the attribute specified
|
189
|
+
#
|
190
|
+
# Example
|
191
|
+
#
|
192
|
+
# class User
|
193
|
+
# attr_encrypted :email
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
|
197
|
+
def decrypt(attribute, encrypted_value, options = {})
|
198
|
+
options = encrypted_attributes[attribute.to_sym].merge(options)
|
199
|
+
if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
|
200
|
+
encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
|
201
|
+
value = options[:encryptor].send(options[:decrypt_method], options.merge!(:value => encrypted_value))
|
202
|
+
value = options[:marshaler].send(options[:load_method], value) if options[:marshal]
|
203
|
+
value
|
204
|
+
else
|
205
|
+
encrypted_value
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Encrypts a value for the attribute specified
|
210
|
+
#
|
211
|
+
# Example
|
212
|
+
#
|
213
|
+
# class User
|
214
|
+
# attr_encrypted :email
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# encrypted_email = User.encrypt(:email, 'test@example.com')
|
218
|
+
def encrypt(attribute, value, options = {})
|
219
|
+
options = encrypted_attributes[attribute.to_sym].merge(options)
|
220
|
+
if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
|
221
|
+
value = options[:preencrypt] ? (value.is_a?(String) ? value.send(options[:preencrypt]) : value) : value
|
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
|
+
cleanse_value value, options
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Cleans up the value to ensure we don't get empty strings the db when we should be getting nils.
|
232
|
+
def cleanse_value(value, options)
|
233
|
+
return nil if value.is_a?(String) && value.empty? && options[:type] == 'date'
|
234
|
+
value
|
235
|
+
end
|
236
|
+
|
237
|
+
# Contains a hash of encrypted attributes with virtual attribute names as keys
|
238
|
+
# and their corresponding options as values
|
239
|
+
#
|
240
|
+
# Example
|
241
|
+
#
|
242
|
+
# class User
|
243
|
+
# attr_encrypted :email, :key => 'my secret key'
|
244
|
+
# end
|
245
|
+
#
|
246
|
+
# User.encrypted_attributes # { :email => { :attribute => 'encrypted_email', :key => 'my secret key' } }
|
247
|
+
def encrypted_attributes
|
248
|
+
@encrypted_attributes ||= superclass.encrypted_attributes.dup
|
249
|
+
end
|
250
|
+
|
251
|
+
# Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
|
252
|
+
# if attribute was configured with attr_encrypted
|
253
|
+
#
|
254
|
+
# Example
|
255
|
+
#
|
256
|
+
# class User
|
257
|
+
# attr_encrypted :email, :key => 'my secret key'
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
|
261
|
+
def method_missing(method, *arguments, &block)
|
262
|
+
if method.to_s =~ /\A((en|de)crypt)_(.+)\z/ && attr_encrypted?($3)
|
263
|
+
send($1, $3, *arguments)
|
264
|
+
else
|
265
|
+
super
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
module InstanceMethods
|
270
|
+
# Decrypts a value for the attribute specified using options evaluated in the current object's scope
|
271
|
+
#
|
272
|
+
# Example
|
273
|
+
#
|
274
|
+
# class User
|
275
|
+
# attr_accessor :secret_key
|
276
|
+
# attr_encrypted :email, :key => :secret_key
|
277
|
+
#
|
278
|
+
# def initialize(secret_key)
|
279
|
+
# self.secret_key = secret_key
|
280
|
+
# end
|
281
|
+
# end
|
282
|
+
#
|
283
|
+
# @user = User.new('some-secret-key')
|
284
|
+
# @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
|
285
|
+
def decrypt(attribute, encrypted_value)
|
286
|
+
self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
|
287
|
+
end
|
288
|
+
|
289
|
+
# Encrypts a value for the attribute specified using options evaluated in the current object's scope
|
290
|
+
#
|
291
|
+
# Example
|
292
|
+
#
|
293
|
+
# class User
|
294
|
+
# attr_accessor :secret_key
|
295
|
+
# attr_encrypted :email, :key => :secret_key
|
296
|
+
#
|
297
|
+
# def initialize(secret_key)
|
298
|
+
# self.secret_key = secret_key
|
299
|
+
# end
|
300
|
+
# end
|
301
|
+
#
|
302
|
+
# @user = User.new('some-secret-key')
|
303
|
+
# @user.encrypt(:email, 'test@example.com')
|
304
|
+
def encrypt(attribute, value)
|
305
|
+
self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
|
306
|
+
end
|
307
|
+
|
308
|
+
def unencrypted_attributes
|
309
|
+
attributes.each_with_object({}) do |a, new_hash|
|
310
|
+
key = a.first
|
311
|
+
value = a.last
|
312
|
+
if key =~ /\A(.+)_enc\z/
|
313
|
+
key = $1
|
314
|
+
value = decrypt(key.to_sym, value)
|
315
|
+
end
|
316
|
+
new_hash[key] = value
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
protected
|
321
|
+
|
322
|
+
# Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
|
323
|
+
def evaluated_attr_encrypted_options_for(attribute)
|
324
|
+
self.class.encrypted_attributes[attribute.to_sym].inject({}) { |hash, (option, value)| hash.merge!(option => (option == :preencrypt) ? value : evaluate_attr_encrypted_option(value)) }
|
325
|
+
end
|
326
|
+
|
327
|
+
# Evaluates symbol (method reference) or proc (responds to call) options
|
328
|
+
#
|
329
|
+
# If the option is not a symbol or proc then the original option is returned
|
330
|
+
def evaluate_attr_encrypted_option(option)
|
331
|
+
if option.is_a?(Symbol) && respond_to?(option)
|
332
|
+
send(option)
|
333
|
+
elsif option.respond_to?(:call)
|
334
|
+
option.call(self)
|
335
|
+
else
|
336
|
+
option
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
Object.extend AttrEncryption
|
343
|
+
|
344
|
+
# require File.expand_path('attr_encryption/adapters/active_record.rb', File.dirname(__FILE__))
|
345
|
+
require 'attr_encryption/adapters/active_record.rb'
|
@@ -0,0 +1,58 @@
|
|
1
|
+
if defined?(ActiveRecord::Base)
|
2
|
+
module AttrEncryption
|
3
|
+
module Adapters
|
4
|
+
module ActiveRecord
|
5
|
+
def self.extended(base) # :nodoc:
|
6
|
+
base.class_eval do
|
7
|
+
# TODO(DJS): We don't want encoding by define in OUR ActiveRecord models
|
8
|
+
# attr_encrypted_options[:encode] = true
|
9
|
+
class << self; alias_method_chain :method_missing, :attr_encrypted; end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# Ensures the attribute methods for db fields have been defined before calling the original
|
16
|
+
# <tt>attr_encrypted</tt> method
|
17
|
+
def attr_encrypted(*attrs)
|
18
|
+
define_attribute_methods rescue nil
|
19
|
+
super
|
20
|
+
attrs.reject { |attr| attr.is_a?(Hash) }.each { |attr| alias_method "#{attr}_before_type_cast", attr }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Allows you to use dynamic methods like <tt>find_by_email</tt> or <tt>scoped_by_email</tt> for
|
24
|
+
# encrypted attributes
|
25
|
+
#
|
26
|
+
# NOTE: This only works when the <tt>:key</tt> option is specified as a string (see the README)
|
27
|
+
#
|
28
|
+
# This is useful for encrypting fields like email addresses. Your user's email addresses
|
29
|
+
# are encrypted in the database, but you can still look up a user by email for logging in
|
30
|
+
#
|
31
|
+
# Example
|
32
|
+
#
|
33
|
+
# class User < ActiveRecord::Base
|
34
|
+
# attr_encrypted :email, :key => 'secret key'
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# User.find_by_email_and_password('test@example.com', 'testing')
|
38
|
+
# # results in a call to
|
39
|
+
# User.find_by_encrypted_email_and_password('the_encrypted_version_of_test@example.com', 'testing')
|
40
|
+
def method_missing_with_attr_encrypted(method, *args, &block)
|
41
|
+
if match = /\A(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)\z/.match(method.to_s)
|
42
|
+
attribute_names = match.captures.last.split('_and_')
|
43
|
+
attribute_names.each_with_index do |attribute, index|
|
44
|
+
if attr_encrypted?(attribute)
|
45
|
+
args[index] = send("encrypt_#{attribute}", args[index])
|
46
|
+
attribute_names[index] = encrypted_attributes[attribute.to_sym][:attribute]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
|
50
|
+
end
|
51
|
+
method_missing_without_attr_encrypted(method, *args, &block)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ActiveRecord::Base.extend AttrEncryption::Adapters::ActiveRecord
|
58
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module MySQLEncryption
|
2
|
+
# Mimics MySQL's AES_ENCRYPT() and AES_DECRYPT() encryption functions
|
3
|
+
$encryption_key ||= "hzjd88hqxejak31"
|
4
|
+
|
5
|
+
def mysql_encrypt(s, key=$encryption_key)
|
6
|
+
return nil if s.blank?
|
7
|
+
do_encrypt(s, mysql_key(key))
|
8
|
+
end
|
9
|
+
|
10
|
+
def mysql_decrypt(s, key=$encryption_key)
|
11
|
+
return nil if s.blank?
|
12
|
+
do_decrypt(s, mysql_key(key))
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def aes(m,k,t)
|
18
|
+
(aes = OpenSSL::Cipher::AES128.new("ECB").send(m)).key = k
|
19
|
+
aes.update(t) << aes.final
|
20
|
+
end
|
21
|
+
|
22
|
+
def do_encrypt(text, key)
|
23
|
+
aes(:encrypt, key, text)
|
24
|
+
end
|
25
|
+
|
26
|
+
def do_decrypt(text, key)
|
27
|
+
aes(:decrypt, key, text)
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# This method returns a key based on the specified key that is
|
32
|
+
# 16 bytes in length. If the specified key is shorter than 16 bytes
|
33
|
+
# it is zero-padded to 16 bytes. If the specified key is longer
|
34
|
+
# 16 bytes, the bytes of the original key are folded back on itself
|
35
|
+
# using the XOR operator. This ensures that all the bytes in the
|
36
|
+
# original key are used, but the resulting key remains 16 bytes long.
|
37
|
+
#
|
38
|
+
# Sheesh.
|
39
|
+
#
|
40
|
+
def mysql_key(key)
|
41
|
+
return nil if key.nil?
|
42
|
+
final_key = "\0" * 16
|
43
|
+
key.bytes.each_with_index do |b, i|
|
44
|
+
buf = (final_key[i%16].bytes.first ^ b)
|
45
|
+
final_key[i%16] = buf.chr
|
46
|
+
end
|
47
|
+
final_key
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
=begin
|
53
|
+
Copyright (c) 2009 Felipe Coury
|
54
|
+
|
55
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
56
|
+
a copy of this software and associated documentation files (the
|
57
|
+
"Software"), to deal in the Software without restriction, including
|
58
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
59
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
60
|
+
permit persons to whom the Software is furnished to do so, subject to
|
61
|
+
the following conditions:
|
62
|
+
|
63
|
+
The above copyright notice and this permission notice shall be
|
64
|
+
included in all copies or substantial portions of the Software.
|
65
|
+
|
66
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
67
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
68
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
69
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
70
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
71
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
72
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
73
|
+
=end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class MySQLEncryptor
|
2
|
+
include MySQLEncryption
|
3
|
+
include Singleton
|
4
|
+
|
5
|
+
def encrypt(encrypt_options)
|
6
|
+
value_to_encrypt = encrypt_options[:value].nil? ? nil : encrypt_options[:type] == 'date' ? encrypt_date(encrypt_options[:value]) : encrypt_options[:value].to_s
|
7
|
+
mysql_encrypt(value_to_encrypt, encrypt_options[:key])
|
8
|
+
end
|
9
|
+
|
10
|
+
def decrypt(encrypt_options)
|
11
|
+
decrypted_value = mysql_decrypt(encrypt_options[:value], encrypt_options[:key])
|
12
|
+
return decrypted_value if decrypted_value.nil?
|
13
|
+
|
14
|
+
case encrypt_options[:type]
|
15
|
+
when 'text'
|
16
|
+
decrypted_value.force_encoding('utf-8')
|
17
|
+
when 'date'
|
18
|
+
Date.parse(decrypted_value)
|
19
|
+
when 'datetime'
|
20
|
+
DateTime.parse(decrypted_value)
|
21
|
+
when 'binary'
|
22
|
+
decrypted_value # no processing
|
23
|
+
else
|
24
|
+
raise "Invalid type specified for post-processing decrypted value"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def encrypt_date(value)
|
29
|
+
dt = value.is_a?(String) ? Date.safe_parse(value) : value
|
30
|
+
dt.strftime("%Y%m%d") if dt.present?
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class DummyClass
|
4
|
+
include MySQLEncryption
|
5
|
+
end
|
6
|
+
|
7
|
+
describe 'MySQLEncryption' do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@cut = DummyClass.new
|
11
|
+
@key = "eaduccitranatereheenalaistater"
|
12
|
+
end
|
13
|
+
|
14
|
+
context "Public Methods" do
|
15
|
+
|
16
|
+
context '#mysql_encrypt' do
|
17
|
+
|
18
|
+
it "should return nil if a nil string is specified" do
|
19
|
+
expect(@cut.mysql_encrypt(nil, @key)).to be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should call do_encrypt with a mysql'ized key and the string" do
|
23
|
+
plain_text = "Plain Text"
|
24
|
+
@cut.should_receive(:do_encrypt).
|
25
|
+
with(plain_text, "abcdefghijklmnop").
|
26
|
+
and_return("encrypted_value")
|
27
|
+
@cut.should_receive(:mysql_key).
|
28
|
+
with("abcdefghijklmnop").
|
29
|
+
and_return("abcdefghijklmnop")
|
30
|
+
expect(@cut.mysql_encrypt(plain_text, "abcdefghijklmnop")).to eq("encrypted_value")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
context '#mysql_decrypt' do
|
36
|
+
|
37
|
+
it "should return nil if a nil string is specified" do
|
38
|
+
expect(@cut.mysql_decrypt(nil, @key)).to be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should call do_decrypt with a mysql'ized key and the string" do
|
42
|
+
plain_text = "Plain Text"
|
43
|
+
@cut.should_receive(:do_decrypt).
|
44
|
+
with(plain_text, "abcdefghijklmnop").
|
45
|
+
and_return("encrypted_value")
|
46
|
+
@cut.should_receive(:mysql_key).
|
47
|
+
with("abcdefghijklmnop").
|
48
|
+
and_return("abcdefghijklmnop")
|
49
|
+
expect(@cut.mysql_decrypt(plain_text, "abcdefghijklmnop")).to eq("encrypted_value")
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'Protected Methods' do
|
57
|
+
|
58
|
+
context '#aes' do
|
59
|
+
|
60
|
+
it "should be able to encrypt, then decrypt a string using a specified key" do
|
61
|
+
plain_text = 'plain text'
|
62
|
+
encrypted_string = @cut.send(:aes, :encrypt, @key, plain_text)
|
63
|
+
unencrypted_string = @cut.send(:aes, :decrypt, @key, encrypted_string)
|
64
|
+
expect(unencrypted_string).to eq(plain_text)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
context '#do_en/decrypt' do
|
70
|
+
|
71
|
+
it "should be able to encrypt, then decrypt a string using a specified key" do
|
72
|
+
plain_text = 'plain text'
|
73
|
+
encrypted_string = @cut.send(:do_encrypt, plain_text, @key)
|
74
|
+
unencrypted_string = @cut.send(:do_decrypt, encrypted_string, @key)
|
75
|
+
expect(unencrypted_string).to eq(plain_text)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context '#mysql_key' do
|
80
|
+
|
81
|
+
it "should return nil if a nil key was specified" do
|
82
|
+
expect(@cut.send(:mysql_key, nil)).to be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should return a final key that is 16 bytes long, each byte being the null-value if a blank key is specified" do
|
86
|
+
expect(@cut.send(:mysql_key, "")).to eq("\0" * 16)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should right-pad a specified key with null bytes if the specified key is less than 16 bytes" do
|
90
|
+
key = "abcdefghijklmop" # 15-byte key
|
91
|
+
expect(@cut.send(:mysql_key, key)).to eq("abcdefghijklmop\0")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should simply echo back the same key if the specified key is 16 bytes" do
|
95
|
+
key = "abcdefghijklmopq" # 16-byte key
|
96
|
+
expect(@cut.send(:mysql_key, key)).to eq("abcdefghijklmopq")
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should XOR the leading bytes of the resulting key with the trailing bytes of the specified key over 16 bytes" do
|
100
|
+
key = "abcdefghijklmopqrs" # 18-byte key
|
101
|
+
expect(@cut.send(:mysql_key, key)).to eq(("a".bytes.first ^ "r".bytes.first).chr +
|
102
|
+
("b".bytes.first ^ "s".bytes.first).chr +
|
103
|
+
"cdefghijklmopq")
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MySQLEncryptor do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@cut = MySQLEncryptor.instance
|
7
|
+
end
|
8
|
+
|
9
|
+
context '#encrypt' do
|
10
|
+
|
11
|
+
it "should return nil if the value passed in the options is nil" do
|
12
|
+
@cut.should_receive(:mysql_encrypt).
|
13
|
+
with(nil, 'the_key_value').
|
14
|
+
and_return(nil)
|
15
|
+
|
16
|
+
expect(@cut.encrypt(value: nil, key: 'the_key_value')).
|
17
|
+
to be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should convert the value to a string before having the mysql_encrypt method encrypt it" do
|
21
|
+
@cut.should_receive(:mysql_encrypt).
|
22
|
+
with("12", 'the_key_value').
|
23
|
+
and_return("encrypted_12")
|
24
|
+
|
25
|
+
expect(@cut.encrypt(value: 12, key: 'the_key_value')).
|
26
|
+
to eq("encrypted_12")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should convert the value to a date string before encryption when the type is specified as a 'date'" do
|
30
|
+
date = Date.today
|
31
|
+
date_str = date.strftime("%Y%m%d")
|
32
|
+
@cut.should_receive(:encrypt_date).
|
33
|
+
with(date).and_return(date_str)
|
34
|
+
@cut.should_receive(:mysql_encrypt).
|
35
|
+
with(date_str, 'the_key_value').
|
36
|
+
and_return("encrypted_date")
|
37
|
+
|
38
|
+
expect(@cut.encrypt(value: date, key: 'the_key_value', type: 'date')).
|
39
|
+
to eq("encrypted_date")
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
context '#decrypt' do
|
45
|
+
|
46
|
+
it "should return nil if mysql_decrypt returns a nil" do
|
47
|
+
@cut.should_receive(:mysql_decrypt).
|
48
|
+
with(nil, 'the_key').
|
49
|
+
and_return(nil)
|
50
|
+
|
51
|
+
expect(@cut.decrypt(value: nil, key: 'the_key')).
|
52
|
+
to be_nil
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should force the encoding of the decrypted string if the type is 'text'" do
|
56
|
+
decrypted_value = 'decrypted_value'
|
57
|
+
decrypted_value.should_receive(:force_encoding).
|
58
|
+
with('utf-8').
|
59
|
+
and_return(decrypted_value)
|
60
|
+
|
61
|
+
@cut.should_receive(:mysql_decrypt).
|
62
|
+
with('encrypted_value', 'the_key').
|
63
|
+
and_return(decrypted_value)
|
64
|
+
|
65
|
+
expect(@cut.decrypt(value: 'encrypted_value', key: 'the_key', type: 'text')).
|
66
|
+
to eq(decrypted_value)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return a Date object if the type of the encrypted value is 'date'" do
|
70
|
+
date = Date.today
|
71
|
+
date_str = date.strftime("%Y%m%d")
|
72
|
+
|
73
|
+
@cut.should_receive(:mysql_decrypt).
|
74
|
+
with('encrypted_date', 'the_key').
|
75
|
+
and_return(date_str)
|
76
|
+
|
77
|
+
expect(@cut.decrypt(value: 'encrypted_date', key: 'the_key', type: 'date')).
|
78
|
+
to eq(date)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should return a DateTime object if the type of the encrypted value is 'datetime'" do
|
82
|
+
date = DateTime.now.utc
|
83
|
+
date_str = date.strftime("%Y-%m-%d %H:%M:%S")
|
84
|
+
|
85
|
+
@cut.should_receive(:mysql_decrypt).
|
86
|
+
with('encrypted_datetime', 'the_key').
|
87
|
+
and_return(date_str)
|
88
|
+
|
89
|
+
expect(@cut.decrypt(value: 'encrypted_datetime', key: 'the_key', type: 'datetime').strftime("%Y-%m-%d %H:%M:%S")).
|
90
|
+
to eq(date_str)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should return the raw-unprocessed encrypted data if the value is binary" do
|
94
|
+
@cut.should_receive(:mysql_decrypt).
|
95
|
+
with('encrypted_binary', 'the_key').
|
96
|
+
and_return("unencrypted_binary")
|
97
|
+
|
98
|
+
expect(@cut.decrypt(value: 'encrypted_binary', key: 'the_key', type: 'binary')).
|
99
|
+
to eq("unencrypted_binary")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should raise a runtime exception if the specified data type is not recognized" do
|
103
|
+
@cut.should_receive(:mysql_decrypt).
|
104
|
+
with('value', 'the_key').
|
105
|
+
and_return("unencrypted_binary")
|
106
|
+
expect { @cut.decrypt(value: 'value', key: 'the_key', type: 'bad_type') }.
|
107
|
+
to raise_error(RuntimeError)
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
context '#encrypt_date' do
|
113
|
+
|
114
|
+
it "should return nil if a nil value is specified" do
|
115
|
+
expect(@cut.encrypt_date(nil)).
|
116
|
+
to be_nil
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should parse specified value as a date if a string is specified, then format it appropriately" do
|
120
|
+
expect(@cut.encrypt_date("2013/09/08")).
|
121
|
+
to eq("20130908")
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should use the date specified and format it appropriately" do
|
125
|
+
date = Date.today
|
126
|
+
date_str = date.strftime("%Y%m%d")
|
127
|
+
expect(@cut.encrypt_date(date)).
|
128
|
+
to eq(date_str)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'active_support/core_ext/object'
|
5
|
+
require 'attr_encryption'
|
6
|
+
require 'openssl'
|
7
|
+
|
8
|
+
# Requires supporting files with custom matchers and macros, etc,
|
9
|
+
# in ./support/ and its subdirectories.
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attr_encryption
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dave Sieh
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.2.14
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.2.14
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rdoc
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.12'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: jeweler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.8.7
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.8.7
|
83
|
+
description: Provides an extension for Ruby and rails object to support a flexible
|
84
|
+
means of encrypting attributes.
|
85
|
+
email: dave.sieh@providigm.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files:
|
89
|
+
- LICENSE.txt
|
90
|
+
- README.rdoc
|
91
|
+
files:
|
92
|
+
- .document
|
93
|
+
- .rspec
|
94
|
+
- Gemfile
|
95
|
+
- Gemfile.lock
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.rdoc
|
98
|
+
- Rakefile
|
99
|
+
- VERSION
|
100
|
+
- lib/attr_encryption.rb
|
101
|
+
- lib/attr_encryption/adapters/active_record.rb
|
102
|
+
- lib/attr_encryption/date_extensions.rb
|
103
|
+
- lib/attr_encryption/mysql_encryption.rb
|
104
|
+
- lib/attr_encryption/mysql_encryptor.rb
|
105
|
+
- spec/mysql_encryption_spec.rb
|
106
|
+
- spec/mysql_encryptor_spec.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
homepage: http://github.com/GitHubAdmin/attr_encryption
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.3.0
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Extends Object and ActiveRecord::Base objects to support encrypted attributes
|
132
|
+
test_files: []
|