pluginaweek-encrypted_attributes 0.4.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/CHANGELOG.rdoc +48 -0
- data/LICENSE +20 -0
- data/README.rdoc +110 -0
- data/Rakefile +97 -0
- data/init.rb +1 -0
- data/lib/encrypted_attributes/sha_cipher.rb +30 -0
- data/lib/encrypted_attributes.rb +207 -0
- data/test/app_root/app/models/user.rb +3 -0
- data/test/app_root/config/environment.rb +9 -0
- data/test/app_root/db/migrate/001_create_users.rb +11 -0
- data/test/factory.rb +41 -0
- data/test/keys/private +9 -0
- data/test/keys/public +4 -0
- data/test/test_helper.rb +13 -0
- data/test/unit/encrypted_attributes_test.rb +446 -0
- data/test/unit/sha_cipher_test.rb +43 -0
- metadata +87 -0
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
== master
|
2
|
+
|
3
|
+
== 0.4.0 / 2009-05-02
|
4
|
+
|
5
|
+
* Replace dynamic :salt option with :embed_salt option and utilizing the #encrypts block
|
6
|
+
* Allow a block to be used for dynamically defining encryption options
|
7
|
+
* Add :before / :after callbacks for hooking into the encryption process
|
8
|
+
* Allow the callback when encryption occurs to be customized [Sergio Salvatore]
|
9
|
+
* Define source attribute accessor if different than target and not defined [Sergio Salvatore]
|
10
|
+
* Update tests to work with Rails 2.3
|
11
|
+
|
12
|
+
== 0.3.0 / 2008-12-14
|
13
|
+
|
14
|
+
* Remove the PluginAWeek namespace
|
15
|
+
|
16
|
+
== 0.2.0 / 2008-12-4
|
17
|
+
|
18
|
+
* Update to be compatible with encrypted_strings 0.2.1
|
19
|
+
|
20
|
+
== 0.1.3 / 2008-10-26
|
21
|
+
|
22
|
+
* Change how the base module is included to prevent namespacing conflicts
|
23
|
+
|
24
|
+
== 0.1.2 / 2008-07-05
|
25
|
+
|
26
|
+
* Leave salt stringification up to PluginAWeek::EncryptedString::ShaEncryptor
|
27
|
+
|
28
|
+
== 0.1.1 / 2008-06-22
|
29
|
+
|
30
|
+
* Remove log files from gems
|
31
|
+
|
32
|
+
== 0.1.0 / 2008-05-05
|
33
|
+
|
34
|
+
* Don't extend encrypted_string's encryptors, instead creating subclasses
|
35
|
+
* Don't assume anything about attribute confirmations
|
36
|
+
* Support storing the salt for SHA encryption in the same string as the original value
|
37
|
+
* Update documentation
|
38
|
+
|
39
|
+
== 0.0.2 / 2007-09-26
|
40
|
+
|
41
|
+
* Move test fixtures out of the test application root directory
|
42
|
+
* Convert dos newlines to unix newlines
|
43
|
+
|
44
|
+
== 0.0.1 / 2007-08-05
|
45
|
+
|
46
|
+
* Official public release
|
47
|
+
* Add documentation
|
48
|
+
* Refactor unit test names
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006-2009 Aaron Pfeifer
|
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,110 @@
|
|
1
|
+
= encrypted_attributes
|
2
|
+
|
3
|
+
+encrypted_attributes+ adds support for automatically encrypting ActiveRecord
|
4
|
+
attributes.
|
5
|
+
|
6
|
+
== Resources
|
7
|
+
|
8
|
+
API
|
9
|
+
|
10
|
+
* http://api.pluginaweek.org/encrypted_attributes
|
11
|
+
|
12
|
+
Bugs
|
13
|
+
|
14
|
+
* http://pluginaweek.lighthouseapp.com/projects/13269-encrypted_attributes
|
15
|
+
|
16
|
+
Development
|
17
|
+
|
18
|
+
* http://github.com/pluginaweek/encrypted_attributes
|
19
|
+
|
20
|
+
Source
|
21
|
+
|
22
|
+
* git://github.com/pluginaweek/encrypted_attributes.git
|
23
|
+
|
24
|
+
== Description
|
25
|
+
|
26
|
+
Encrypting attributes can be repetitive especially when doing so throughout
|
27
|
+
various models and various projects. encrypted_attributes, in association
|
28
|
+
with the encrypted_strings library, helps make encrypting ActiveRecord
|
29
|
+
attributes easier by automating the process.
|
30
|
+
|
31
|
+
The options that +encrypts+ takes includes all of the encryption options for
|
32
|
+
the specific type of cipher being used from the encrypted_strings library.
|
33
|
+
Therefore, if setting the key for asymmetric encryption, this would be passed
|
34
|
+
into the +encrypts+ method. Examples of this are show in the Usage section.
|
35
|
+
|
36
|
+
== Usage
|
37
|
+
|
38
|
+
=== Encryption Modes
|
39
|
+
|
40
|
+
SHA, symmetric, and asymmetric encryption modes are supported (default is SHA):
|
41
|
+
|
42
|
+
class User < ActiveRecord::Base
|
43
|
+
encrypts :password, :salt => 'secret'
|
44
|
+
# encrypts :password, :mode => :symmetric, :password => 'secret'
|
45
|
+
# encrypts :password, :mode => :asymmetric, :public_key_file => '/keys/public', :private_key_file => '/keys/private'
|
46
|
+
end
|
47
|
+
|
48
|
+
=== Dynamic Configuration
|
49
|
+
|
50
|
+
The encryption configuration can be dynamically set like so:
|
51
|
+
|
52
|
+
class User < ActiveRecord::Base
|
53
|
+
encrypts :password, :mode => :sha do |user|
|
54
|
+
{:salt => "#{user.login}-#{Time.now}", :embed_salt => true}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
In this case, the salt and password values are combined and stored in the
|
59
|
+
attribute being encrypted. Therefore, there's no need to add a second column
|
60
|
+
for storing the salt value.
|
61
|
+
|
62
|
+
To store the dynamic salt in a separate column:
|
63
|
+
|
64
|
+
class User < ActiveRecord::Base
|
65
|
+
encrypts :password, :mode => :sha, :before => :create_salt do |user|
|
66
|
+
{:salt => user.salt}
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_salt
|
70
|
+
self.salt = "#{login}-#{Time.now}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
=== Targeted Encryption
|
75
|
+
|
76
|
+
If you want to store the encrypted value in a different attribute than the
|
77
|
+
attribute being encrypted:
|
78
|
+
|
79
|
+
class User < ActiveRecord::Base
|
80
|
+
encrypts :password, :to => :crypted_password
|
81
|
+
end
|
82
|
+
|
83
|
+
=== Conditional Encryption
|
84
|
+
|
85
|
+
Like ActiveRecord validations, +encrypts+ can take <tt>:if</tt> and <tt>:unless</tt>
|
86
|
+
parameters that determine whether the encryption should occur. For example,
|
87
|
+
|
88
|
+
class User < ActiveRecord::Base
|
89
|
+
encrypts :password, :if => lambda {Rails.env != 'development'}
|
90
|
+
end
|
91
|
+
|
92
|
+
=== Additional information
|
93
|
+
|
94
|
+
For more examples of actual migrations and models that encrypt attributes,
|
95
|
+
see the actual API and unit tests. Also, see encrypted_strings for more
|
96
|
+
information about the various options that can be passed in.
|
97
|
+
|
98
|
+
== Testing
|
99
|
+
|
100
|
+
Before you can run any tests, the following gem must be installed:
|
101
|
+
* plugin_test_helper[http://github.com/pluginaweek/plugin_test_helper]
|
102
|
+
|
103
|
+
To run against a specific version of Rails:
|
104
|
+
|
105
|
+
rake test RAILS_FRAMEWORK_ROOT=/path/to/rails
|
106
|
+
|
107
|
+
== Dependencies
|
108
|
+
|
109
|
+
* Rails 2.1 or later
|
110
|
+
* encrypted_strings[http://github.com/pluginaweek/encrypted_strings]
|
data/Rakefile
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/contrib/sshpublisher'
|
5
|
+
|
6
|
+
spec = Gem::Specification.new do |s|
|
7
|
+
s.name = 'encrypted_attributes'
|
8
|
+
s.version = '0.4.0'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.summary = 'Adds support for automatically encrypting ActiveRecord attributes'
|
11
|
+
s.description = s.summary
|
12
|
+
|
13
|
+
s.files = FileList['{lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
|
14
|
+
s.require_path = 'lib'
|
15
|
+
s.has_rdoc = true
|
16
|
+
s.test_files = Dir['test/**/*_test.rb']
|
17
|
+
s.add_dependency 'encrypted_strings', '>= 0.3.0'
|
18
|
+
|
19
|
+
s.author = 'Aaron Pfeifer'
|
20
|
+
s.email = 'aaron@pluginaweek.org'
|
21
|
+
s.homepage = 'http://www.pluginaweek.org'
|
22
|
+
s.rubyforge_project = 'pluginaweek'
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Default: run all tests.'
|
26
|
+
task :default => :test
|
27
|
+
|
28
|
+
desc "Test the #{spec.name} plugin."
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.test_files = spec.test_files
|
32
|
+
t.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
require 'rcov/rcovtask'
|
37
|
+
namespace :test do
|
38
|
+
desc "Test the #{spec.name} plugin with Rcov."
|
39
|
+
Rcov::RcovTask.new(:rcov) do |t|
|
40
|
+
t.libs << 'lib'
|
41
|
+
t.test_files = spec.test_files
|
42
|
+
t.rcov_opts << '--exclude="^(?!lib/)"'
|
43
|
+
t.verbose = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
rescue LoadError
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Generate documentation for the #{spec.name} plugin."
|
50
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = spec.name
|
53
|
+
rdoc.template = '../rdoc_template.rb'
|
54
|
+
rdoc.options << '--line-numbers'
|
55
|
+
rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb')
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'Generate a gemspec file.'
|
59
|
+
task :gemspec do
|
60
|
+
File.open("#{spec.name}.gemspec", 'w') do |f|
|
61
|
+
f.write spec.to_ruby
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Rake::GemPackageTask.new(spec) do |p|
|
66
|
+
p.gem_spec = spec
|
67
|
+
p.need_tar = true
|
68
|
+
p.need_zip = true
|
69
|
+
end
|
70
|
+
|
71
|
+
desc 'Publish the beta gem.'
|
72
|
+
task :pgem => [:package] do
|
73
|
+
Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
|
74
|
+
end
|
75
|
+
|
76
|
+
desc 'Publish the API documentation.'
|
77
|
+
task :pdoc => [:rdoc] do
|
78
|
+
Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
|
79
|
+
end
|
80
|
+
|
81
|
+
desc 'Publish the API docs and gem'
|
82
|
+
task :publish => [:pgem, :pdoc, :release]
|
83
|
+
|
84
|
+
desc 'Publish the release files to RubyForge.'
|
85
|
+
task :release => [:gem, :package] do
|
86
|
+
require 'rubyforge'
|
87
|
+
|
88
|
+
ruby_forge = RubyForge.new.configure
|
89
|
+
ruby_forge.login
|
90
|
+
|
91
|
+
%w(gem tgz zip).each do |ext|
|
92
|
+
file = "pkg/#{spec.name}-#{spec.version}.#{ext}"
|
93
|
+
puts "Releasing #{File.basename(file)}..."
|
94
|
+
|
95
|
+
ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
|
96
|
+
end
|
97
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'encrypted_attributes'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module EncryptedAttributes
|
2
|
+
# Adds support for embedding salts in the encrypted value
|
3
|
+
class ShaCipher < EncryptedStrings::ShaCipher
|
4
|
+
# Encrypts a string using a Secure Hash Algorithm (SHA), specifically SHA-1.
|
5
|
+
#
|
6
|
+
# Configuration options:
|
7
|
+
# * <tt>:salt</tt> - Random bytes used as one of the inputs for generating
|
8
|
+
# the encrypted string
|
9
|
+
# * <tt>:embed_salt</tt> - Whether to embed the salt directly within the
|
10
|
+
# encrypted value. Default is false. This is useful for storing both
|
11
|
+
# the salt and the encrypted value in the same attribute.
|
12
|
+
def initialize(value, options = {}) #:nodoc:
|
13
|
+
if @embed_salt = options.delete(:embed_salt)
|
14
|
+
# The salt is at the end of the value
|
15
|
+
salt = value[40..-1]
|
16
|
+
options[:salt] = salt unless salt.blank?
|
17
|
+
end
|
18
|
+
|
19
|
+
super(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Encrypts the data, embedding the salt at the end of the string if
|
23
|
+
# configured to do so
|
24
|
+
def encrypt(data)
|
25
|
+
encrypted_data = super
|
26
|
+
encrypted_data << salt if @embed_salt
|
27
|
+
encrypted_data
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'encrypted_strings'
|
2
|
+
require 'encrypted_attributes/sha_cipher'
|
3
|
+
|
4
|
+
module EncryptedAttributes
|
5
|
+
module MacroMethods
|
6
|
+
# Encrypts the given attribute.
|
7
|
+
#
|
8
|
+
# Configuration options:
|
9
|
+
# * <tt>:mode</tt> - The mode of encryption to use. Default is <tt>:sha</tt>.
|
10
|
+
# See EncryptedStrings for other possible modes.
|
11
|
+
# * <tt>:to</tt> - The attribute to write the encrypted value to. Default
|
12
|
+
# is the same attribute being encrypted.
|
13
|
+
# * <tt>:before</tt> - The callback to invoke every time *before* the
|
14
|
+
# attribute is encrypted
|
15
|
+
# * <tt>:after</tt> - The callback to invoke every time *after* the
|
16
|
+
# attribute is encrypted
|
17
|
+
# * <tt>:on</tt> - The ActiveRecord callback to use when triggering the
|
18
|
+
# encryption. By default, this will encrypt on <tt>before_validation</tt>.
|
19
|
+
# See ActiveRecord::Callbacks for a list of possible callbacks.
|
20
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
21
|
+
# if the encryption should occur. The method, proc or string should return
|
22
|
+
# or evaluate to a true or false value.
|
23
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
24
|
+
# determine if the encryption should not occur. The method, proc or string
|
25
|
+
# should return or evaluate to a true or false value.
|
26
|
+
#
|
27
|
+
# For additional configuration options used during the actual encryption,
|
28
|
+
# see the individual cipher class for the specified mode.
|
29
|
+
#
|
30
|
+
# == Encryption timeline
|
31
|
+
#
|
32
|
+
# By default, attributes are encrypted immediately before a record is
|
33
|
+
# validated. This means that you can still validate the presence of the
|
34
|
+
# encrypted attribute, but other things like password length cannot be
|
35
|
+
# validated without either (a) decrypting the value first or (b) using a
|
36
|
+
# different encryption target. For example,
|
37
|
+
#
|
38
|
+
# class User < ActiveRecord::Base
|
39
|
+
# encrypts :password, :to => :crypted_password
|
40
|
+
#
|
41
|
+
# validates_presence_of :password, :crypted_password
|
42
|
+
# validates_length_of :password, :maximum => 16
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# In the above example, the actual encrypted password will be stored in
|
46
|
+
# the +crypted_password+ attribute. This means that validations can
|
47
|
+
# still run against the model for the original password value.
|
48
|
+
#
|
49
|
+
# user = User.new(:password => 'secret')
|
50
|
+
# user.password # => "secret"
|
51
|
+
# user.crypted_password # => nil
|
52
|
+
# user.valid? # => true
|
53
|
+
# user.crypted_password # => "8152bc582f58c854f580cb101d3182813dec4afe"
|
54
|
+
#
|
55
|
+
# user = User.new(:password => 'longer_than_the_maximum_allowed')
|
56
|
+
# user.valid? # => false
|
57
|
+
# user.crypted_password # => "e80a709f25798f87d9ca8005a7f64a645964d7c2"
|
58
|
+
# user.errors[:password] # => "is too long (maximum is 16 characters)"
|
59
|
+
#
|
60
|
+
# == Encryption mode examples
|
61
|
+
#
|
62
|
+
# SHA encryption:
|
63
|
+
#
|
64
|
+
# class User < ActiveRecord::Base
|
65
|
+
# encrypts :password
|
66
|
+
# # encrypts :password, :salt => 'secret'
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# Symmetric encryption:
|
70
|
+
#
|
71
|
+
# class User < ActiveRecord::Base
|
72
|
+
# encrypts :password, :mode => :symmetric
|
73
|
+
# # encrypts :password, :mode => :symmetric, :key => 'custom'
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# Asymmetric encryption:
|
77
|
+
#
|
78
|
+
# class User < ActiveRecord::Base
|
79
|
+
# encrypts :password, :mode => :asymmetric
|
80
|
+
# # encrypts :password, :mode => :asymmetric, :public_key_file => '/keys/public', :private_key_file => '/keys/private'
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# == Dynamic configuration
|
84
|
+
#
|
85
|
+
# For better security, the encryption options (such as the salt value)
|
86
|
+
# can be based on values in each individual record. In order to
|
87
|
+
# dynamically configure the encryption options so that individual records
|
88
|
+
# can be referenced, an optional block can be specified.
|
89
|
+
#
|
90
|
+
# For example,
|
91
|
+
#
|
92
|
+
# class User < ActiveRecord::Base
|
93
|
+
# encrypts :password, :mode => :sha, :before => :create_salt do |user|
|
94
|
+
# {:salt => user.salt}
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# private
|
98
|
+
# def create_salt
|
99
|
+
# self.salt = "#{login}-#{Time.now}"
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# In the above example, the SHA encryption's <tt>salt</tt> is configured
|
104
|
+
# dynamically based on the user's login and the time at which it was
|
105
|
+
# encrypted. This helps improve the security of the user's password.
|
106
|
+
def encrypts(attr_name, options = {}, &config)
|
107
|
+
config ||= options
|
108
|
+
attr_name = attr_name.to_s
|
109
|
+
to_attr_name = (options.delete(:to) || attr_name).to_s
|
110
|
+
|
111
|
+
# Figure out what cipher is being configured for the attribute
|
112
|
+
mode = options.delete(:mode) || :sha
|
113
|
+
class_name = "#{mode.to_s.classify}Cipher"
|
114
|
+
if EncryptedAttributes.const_defined?(class_name)
|
115
|
+
cipher_class = EncryptedAttributes.const_get(class_name)
|
116
|
+
else
|
117
|
+
cipher_class = EncryptedStrings.const_get(class_name)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Define encryption hooks
|
121
|
+
define_callbacks("before_encrypt_#{attr_name}", "after_encrypt_#{attr_name}")
|
122
|
+
send("before_encrypt_#{attr_name}", options.delete(:before)) if options.include?(:before)
|
123
|
+
send("after_encrypt_#{attr_name}", options.delete(:after)) if options.include?(:after)
|
124
|
+
|
125
|
+
# Set the encrypted value on the configured callback
|
126
|
+
callback = options.delete(:on) || :before_validation
|
127
|
+
send(callback, :if => options.delete(:if), :unless => options.delete(:unless)) do |record|
|
128
|
+
record.send(:write_encrypted_attribute, attr_name, to_attr_name, cipher_class, config)
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
# Define virtual source attribute
|
133
|
+
if attr_name != to_attr_name && !column_names.include?(attr_name)
|
134
|
+
attr_reader attr_name unless method_defined?(attr_name)
|
135
|
+
attr_writer attr_name unless method_defined?("#{attr_name}=")
|
136
|
+
end
|
137
|
+
|
138
|
+
# Define the reader when reading the encrypted attribute from the database
|
139
|
+
define_method(to_attr_name) do
|
140
|
+
read_encrypted_attribute(to_attr_name, cipher_class, config)
|
141
|
+
end
|
142
|
+
|
143
|
+
unless included_modules.include?(EncryptedAttributes::InstanceMethods)
|
144
|
+
include EncryptedAttributes::InstanceMethods
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
module InstanceMethods #:nodoc:
|
150
|
+
private
|
151
|
+
# Encrypts the given attribute to a target location using the encryption
|
152
|
+
# options configured for that attribute
|
153
|
+
def write_encrypted_attribute(attr_name, to_attr_name, cipher_class, options)
|
154
|
+
value = send(attr_name)
|
155
|
+
|
156
|
+
# Only encrypt values that actually have content and have not already
|
157
|
+
# been encrypted
|
158
|
+
unless value.blank? || value.encrypted?
|
159
|
+
callback("before_encrypt_#{attr_name}")
|
160
|
+
|
161
|
+
# Create the cipher configured for this attribute
|
162
|
+
cipher = create_cipher(cipher_class, options, value)
|
163
|
+
|
164
|
+
# Encrypt the value
|
165
|
+
value = cipher.encrypt(value)
|
166
|
+
value.cipher = cipher
|
167
|
+
|
168
|
+
# Update the value based on the target attribute
|
169
|
+
send("#{to_attr_name}=", value)
|
170
|
+
|
171
|
+
callback("after_encrypt_#{attr_name}")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Reads the given attribute from the database, adding contextual
|
176
|
+
# information about how it was encrypted so that equality comparisons
|
177
|
+
# can be used
|
178
|
+
def read_encrypted_attribute(to_attr_name, cipher_class, options)
|
179
|
+
value = read_attribute(to_attr_name)
|
180
|
+
|
181
|
+
# Make sure we set the cipher for equality comparison when reading
|
182
|
+
# from the database. This should only be done if the value is *not*
|
183
|
+
# blank, is *not* encrypted, and hasn't changed since it was read from
|
184
|
+
# the database. The dirty checking is important when the encypted value
|
185
|
+
# is written to the same attribute as the unencrypted value (i.e. you
|
186
|
+
# don't want to encrypt when a new value has been set)
|
187
|
+
unless value.blank? || value.encrypted? || attribute_changed?(to_attr_name)
|
188
|
+
# Create the cipher configured for this attribute
|
189
|
+
value.cipher = create_cipher(cipher_class, options, value)
|
190
|
+
end
|
191
|
+
|
192
|
+
value
|
193
|
+
end
|
194
|
+
|
195
|
+
# Creates a new cipher with the given configuration options
|
196
|
+
def create_cipher(klass, options, value)
|
197
|
+
options = options.is_a?(Proc) ? options.call(self) : options.dup
|
198
|
+
|
199
|
+
# Only use the contextual information for this plugin's ciphers
|
200
|
+
klass.parent == EncryptedAttributes ? klass.new(value, options) : klass.new(options)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
ActiveRecord::Base.class_eval do
|
206
|
+
extend EncryptedAttributes::MacroMethods
|
207
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'config/boot'
|
2
|
+
|
3
|
+
Rails::Initializer.run do |config|
|
4
|
+
config.plugin_paths << '..'
|
5
|
+
config.plugins = %w(encrypted_strings encrypted_attributes)
|
6
|
+
config.cache_classes = false
|
7
|
+
config.whiny_nils = true
|
8
|
+
config.action_controller.session = {:key => 'rails_session', :secret => 'd229e4d22437432705ab3985d4d246'}
|
9
|
+
end
|
data/test/factory.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Factory
|
2
|
+
# Build actions for the model
|
3
|
+
def self.build(model, &block)
|
4
|
+
name = model.to_s.underscore
|
5
|
+
|
6
|
+
define_method("#{name}_attributes", block)
|
7
|
+
define_method("valid_#{name}_attributes") {|*args| valid_attributes_for(model, *args)}
|
8
|
+
define_method("new_#{name}") {|*args| new_record(model, *args)}
|
9
|
+
define_method("create_#{name}") {|*args| create_record(model, *args)}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get valid attributes for the model
|
13
|
+
def valid_attributes_for(model, attributes = {})
|
14
|
+
name = model.to_s.underscore
|
15
|
+
send("#{name}_attributes", attributes)
|
16
|
+
attributes.stringify_keys!
|
17
|
+
attributes
|
18
|
+
end
|
19
|
+
|
20
|
+
# Build an unsaved record
|
21
|
+
def new_record(model, *args)
|
22
|
+
attributes = valid_attributes_for(model, *args)
|
23
|
+
record = model.new(attributes)
|
24
|
+
attributes.each {|attr, value| record.send("#{attr}=", value) if model.accessible_attributes && !model.accessible_attributes.include?(attr) || model.protected_attributes && model.protected_attributes.include?(attr)}
|
25
|
+
record
|
26
|
+
end
|
27
|
+
|
28
|
+
# Build and save/reload a record
|
29
|
+
def create_record(model, *args)
|
30
|
+
record = new_record(model, *args)
|
31
|
+
record.save!
|
32
|
+
record.reload
|
33
|
+
record
|
34
|
+
end
|
35
|
+
|
36
|
+
build User do |attributes|
|
37
|
+
attributes.reverse_merge!(
|
38
|
+
:login => 'admin'
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
data/test/keys/private
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIBOwIBAAJBAL/xeY6aqFx6z1ThNOwgPgxv3tsonTlCj8VkN3Ikumg6SzBuLxlV
|
3
|
+
i9gFQZ7K9Pv9o/7+xUTYODqBpVhwgLBeu2cCAwEAAQJAHyjFMfg7Yp/xLndMzxRA
|
4
|
+
3mX+yJckRtpeWo31TktWE3syks1r9OrfmxKiStM9kFRubeBHTihZrW92TYkROLxh
|
5
|
+
uQIhAPuftVTJZFDNxeYDKIMIMqwR8KZgtuf25cv4pTxYwPqLAiEAw0gNwDJHBkvo
|
6
|
+
da4402pZNQmBA6qCSf0svDXqoEoaShUCIGBma340Oe6LJ0pb42Vv+pnZtazIWMq9
|
7
|
+
2IQwmn1oM2bJAiEAhgP869mVRIzzi091UCG79tn+4DU0FPLasI+P5VD1mcECIQDb
|
8
|
+
3ndvbPcElVvdJgabxyWJJsNtBBNZYPsuc6NrQyShOw==
|
9
|
+
-----END RSA PRIVATE KEY-----
|
data/test/keys/public
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Load the plugin testing framework
|
2
|
+
$:.unshift("#{File.dirname(__FILE__)}/../../plugin_test_helper/lib")
|
3
|
+
require 'rubygems'
|
4
|
+
require 'plugin_test_helper'
|
5
|
+
|
6
|
+
# Run the migrations
|
7
|
+
ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
|
8
|
+
|
9
|
+
# Mixin the factory helper
|
10
|
+
require File.expand_path("#{File.dirname(__FILE__)}/factory")
|
11
|
+
Test::Unit::TestCase.class_eval do
|
12
|
+
include Factory
|
13
|
+
end
|
@@ -0,0 +1,446 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class EncryptedAttributesTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
User.encrypts :password
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_should_use_sha_by_default
|
9
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
10
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_should_encrypt_on_invalid_model
|
14
|
+
user = new_user(:login => nil, :password => 'secret')
|
15
|
+
assert !user.valid?
|
16
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_should_not_encrypt_if_attribute_is_nil
|
20
|
+
user = create_user(:login => 'admin', :password => nil)
|
21
|
+
assert_nil user.password
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_not_encrypt_if_attribute_is_blank
|
25
|
+
user = create_user(:login => 'admin', :password => '')
|
26
|
+
assert_equal '', user.password
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_should_not_encrypt_if_already_encrypted
|
30
|
+
user = create_user(:login => 'admin', :password => 'secret'.encrypt)
|
31
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_should_return_encrypted_attribute_for_saved_record
|
35
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
36
|
+
user = User.find(user.id)
|
37
|
+
assert user.password.encrypted?
|
38
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_should_not_encrypt_attribute_if_updating_without_any_changes
|
42
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
43
|
+
user.login = 'Administrator'
|
44
|
+
user.save!
|
45
|
+
assert user.password.encrypted?
|
46
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_should_encrypt_attribute_if_updating_with_changes
|
50
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
51
|
+
user.password = 'shhh'
|
52
|
+
user.save!
|
53
|
+
assert user.password.encrypted?
|
54
|
+
assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.password}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def teardown
|
58
|
+
User.class_eval do
|
59
|
+
@before_validation_callbacks = nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class EncryptedAttributesWithDifferentTargetTest < ActiveSupport::TestCase
|
65
|
+
def setup
|
66
|
+
User.encrypts :password, :to => :crypted_password
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_should_not_encrypt_if_attribute_is_nil
|
70
|
+
user = create_user(:login => 'admin', :password => nil)
|
71
|
+
assert_nil user.password
|
72
|
+
assert_nil user.crypted_password
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_should_not_encrypt_if_attribute_is_blank
|
76
|
+
user = create_user(:login => 'admin', :password => '')
|
77
|
+
assert_equal '', user.password
|
78
|
+
assert_nil user.crypted_password
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_should_not_encrypt_if_already_encrypted
|
82
|
+
user = create_user(:login => 'admin', :crypted_password => 'secret'.encrypt)
|
83
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.crypted_password}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_should_return_encrypted_attribute_for_saved_record
|
87
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
88
|
+
user = User.find(user.id)
|
89
|
+
assert user.crypted_password.encrypted?
|
90
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.crypted_password}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_should_not_encrypt_attribute_if_updating_without_any_changes
|
94
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
95
|
+
user.login = 'Administrator'
|
96
|
+
user.save!
|
97
|
+
assert user.crypted_password.encrypted?
|
98
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.crypted_password}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_should_encrypt_attribute_if_updating_with_changes
|
102
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
103
|
+
user.password = 'shhh'
|
104
|
+
user.save!
|
105
|
+
assert user.crypted_password.encrypted?
|
106
|
+
assert_equal '162cf5debf84cbc2af13da848544c3e2c515b4d3', "#{user.crypted_password}"
|
107
|
+
end
|
108
|
+
|
109
|
+
def teardown
|
110
|
+
User.class_eval do
|
111
|
+
@before_validation_callbacks = nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class EncryptedAttributesWithVirtualAttributeSourceTest < ActiveSupport::TestCase
|
117
|
+
def setup
|
118
|
+
User.encrypts :raw_password, :to => :crypted_password
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_should_define_source_reader
|
122
|
+
assert User.method_defined?(:raw_password)
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_should_define_source_writer
|
126
|
+
assert User.method_defined?(:raw_password=)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_should_encrypt_from_virtual_attribute
|
130
|
+
user = create_user(:login => 'admin', :raw_password => 'secret')
|
131
|
+
assert user.crypted_password.encrypted?
|
132
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.crypted_password}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def teardown
|
136
|
+
User.class_eval do
|
137
|
+
@before_validation_callbacks = nil
|
138
|
+
|
139
|
+
remove_method(:raw_password)
|
140
|
+
remove_method(:raw_password=)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class EncryptedAttributesWithConflictingVirtualAttributeSourceTest < ActiveSupport::TestCase
|
146
|
+
def setup
|
147
|
+
User.class_eval do
|
148
|
+
def raw_password
|
149
|
+
'raw_password'
|
150
|
+
end
|
151
|
+
|
152
|
+
def raw_password=(value)
|
153
|
+
self.password = value
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
User.encrypts :raw_password, :to => :crypted_password
|
158
|
+
@user = User.new
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_should_not_define_source_reader
|
162
|
+
assert_equal 'raw_password', @user.raw_password
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_should_not_define_source_writer
|
166
|
+
@user.raw_password = 'raw_password'
|
167
|
+
assert_equal 'raw_password', @user.password
|
168
|
+
end
|
169
|
+
|
170
|
+
def teardown
|
171
|
+
User.class_eval do
|
172
|
+
@before_validation_callbacks = nil
|
173
|
+
|
174
|
+
remove_method(:raw_password)
|
175
|
+
remove_method(:raw_password=)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class EncryptedAttributesWithCustomCallbackTest < ActiveSupport::TestCase
|
181
|
+
def setup
|
182
|
+
User.encrypts :password, :on => :before_save
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_should_not_encrypt_on_validation
|
186
|
+
user = new_user(:login => 'admin', :password => 'secret')
|
187
|
+
user.valid?
|
188
|
+
assert_equal 'secret', user.password
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_should_encrypt_on_create
|
192
|
+
user = new_user(:login => 'admin', :password => 'secret')
|
193
|
+
user.save
|
194
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
195
|
+
end
|
196
|
+
|
197
|
+
def teardown
|
198
|
+
User.class_eval do
|
199
|
+
@before_save_callbacks = nil
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class EncryptedAttributesWithConditionalsTest < ActiveSupport::TestCase
|
205
|
+
def test_should_not_encrypt_if_if_conditional_is_false
|
206
|
+
User.encrypts :password, :if => lambda {|user| false}
|
207
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
208
|
+
assert_equal 'secret', user.password
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_should_encrypt_if_if_conditional_is_true
|
212
|
+
User.encrypts :password, :if => lambda {|user| true}
|
213
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
214
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_should_not_encrypt_if_unless_conditional_is_true
|
218
|
+
User.encrypts :password, :unless => lambda {|user| true}
|
219
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
220
|
+
assert_equal 'secret', user.password
|
221
|
+
end
|
222
|
+
|
223
|
+
def test_should_encrypt_if_unless_conditional_is_false
|
224
|
+
User.encrypts :password, :unless => lambda {|user| false}
|
225
|
+
user = create_user(:login => 'admin', :password => 'secret')
|
226
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{user.password}"
|
227
|
+
end
|
228
|
+
|
229
|
+
def teardown
|
230
|
+
User.class_eval do
|
231
|
+
@before_validation_callbacks = nil
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
class EncryptedAttributesWithBeforeCallbacksTest < ActiveSupport::TestCase
|
237
|
+
def setup
|
238
|
+
@password = nil
|
239
|
+
@ran_callback = false
|
240
|
+
User.encrypts :password, :before => lambda {|user| @ran_callback = true; @password = user.password.to_s}
|
241
|
+
|
242
|
+
create_user(:login => 'admin', :password => 'secret')
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_should_run_callback
|
246
|
+
assert @ran_callback
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_should_not_have_encrypted_yet
|
250
|
+
assert_equal 'secret', @password
|
251
|
+
end
|
252
|
+
|
253
|
+
def teardown
|
254
|
+
User.class_eval do
|
255
|
+
@before_validation_callbacks = nil
|
256
|
+
@before_encrypt_password_callbacks = nil
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class EncryptedAttributesWithAfterCallbacksTest < ActiveSupport::TestCase
|
262
|
+
def setup
|
263
|
+
@password = nil
|
264
|
+
@ran_callback = false
|
265
|
+
User.encrypts :password, :after => lambda {|user| @ran_callback = true; @password = user.password.to_s}
|
266
|
+
|
267
|
+
create_user(:login => 'admin', :password => 'secret')
|
268
|
+
end
|
269
|
+
|
270
|
+
def test_should_run_callback
|
271
|
+
assert @ran_callback
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_should_have_encrypted_already
|
275
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', @password
|
276
|
+
end
|
277
|
+
|
278
|
+
def teardown
|
279
|
+
User.class_eval do
|
280
|
+
@before_validation_callbacks = nil
|
281
|
+
@after_encrypt_password_callbacks = nil
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
class EncryptedAttributesWithDynamicConfigurationTest < ActiveSupport::TestCase
|
287
|
+
def setup
|
288
|
+
@salt = nil
|
289
|
+
User.encrypts :password, :before => lambda {|user| user.salt = user.login} do |user|
|
290
|
+
{:salt => @salt = user.salt}
|
291
|
+
end
|
292
|
+
|
293
|
+
@user = create_user(:login => 'admin', :password => 'secret')
|
294
|
+
end
|
295
|
+
|
296
|
+
def test_should_use_dynamic_configuration_during_write
|
297
|
+
assert_equal 'a55d037f385cad22efe7862e07b805938d150154', "#{@user[:password]}"
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_should_use_dynamic_configuration_during_read
|
301
|
+
user = User.find(@user.id)
|
302
|
+
assert_equal 'a55d037f385cad22efe7862e07b805938d150154', "#{user.password}"
|
303
|
+
end
|
304
|
+
|
305
|
+
def test_should_build_configuration_after_before_callbacks_invoked
|
306
|
+
assert_equal 'admin', @salt
|
307
|
+
end
|
308
|
+
|
309
|
+
def teardown
|
310
|
+
User.class_eval do
|
311
|
+
@before_validation_callbacks = nil
|
312
|
+
@before_encrypt_password_callbacks = nil
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class ShaEncryptionTest < ActiveSupport::TestCase
|
318
|
+
def setup
|
319
|
+
User.encrypts :password, :mode => :sha
|
320
|
+
@user = create_user(:login => 'admin', :password => 'secret')
|
321
|
+
end
|
322
|
+
|
323
|
+
def test_should_encrypt_password
|
324
|
+
assert_equal '8152bc582f58c854f580cb101d3182813dec4afe', "#{@user.password}"
|
325
|
+
end
|
326
|
+
|
327
|
+
def test_should_be_encrypted
|
328
|
+
assert @user.password.encrypted?
|
329
|
+
end
|
330
|
+
|
331
|
+
def test_should_use_sha_cipher
|
332
|
+
assert_instance_of EncryptedAttributes::ShaCipher, @user.password.cipher
|
333
|
+
end
|
334
|
+
|
335
|
+
def test_should_use_default_salt
|
336
|
+
assert_equal 'salt', @user.password.cipher.salt
|
337
|
+
end
|
338
|
+
|
339
|
+
def test_should_be_able_to_check_password
|
340
|
+
assert_equal 'secret', @user. password
|
341
|
+
end
|
342
|
+
|
343
|
+
def teardown
|
344
|
+
User.class_eval do
|
345
|
+
@before_validation_callbacks = nil
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
class ShaWithEmbeddedSaltEncryptionTest < ActiveSupport::TestCase
|
351
|
+
def setup
|
352
|
+
User.encrypts :password, :mode => :sha, :salt => 'admin', :embed_salt => true
|
353
|
+
@user = create_user(:login => 'admin', :password => 'secret')
|
354
|
+
end
|
355
|
+
|
356
|
+
def test_should_encrypt_password
|
357
|
+
assert_equal 'a55d037f385cad22efe7862e07b805938d150154admin', "#{@user.password}"
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_should_be_encrypted
|
361
|
+
assert @user.password.encrypted?
|
362
|
+
end
|
363
|
+
|
364
|
+
def test_should_use_sha_cipher
|
365
|
+
assert_instance_of EncryptedAttributes::ShaCipher, @user.password.cipher
|
366
|
+
end
|
367
|
+
|
368
|
+
def test_should_use_custom_salt
|
369
|
+
assert_equal 'admin', @user.password.cipher.salt
|
370
|
+
end
|
371
|
+
|
372
|
+
def test_should_be_able_to_check_password
|
373
|
+
assert_equal 'secret', @user.password
|
374
|
+
end
|
375
|
+
|
376
|
+
def teardown
|
377
|
+
User.class_eval do
|
378
|
+
@before_validation_callbacks = nil
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
class SymmetricEncryptionTest < ActiveSupport::TestCase
|
384
|
+
def setup
|
385
|
+
User.encrypts :password, :mode => :symmetric, :password => 'key'
|
386
|
+
@user = create_user(:login => 'admin', :password => 'secret')
|
387
|
+
end
|
388
|
+
|
389
|
+
def test_should_encrypt_password
|
390
|
+
assert_equal "zfKtnSa33tc=\n", @user.password
|
391
|
+
end
|
392
|
+
|
393
|
+
def test_should_be_encrypted
|
394
|
+
assert @user.password.encrypted?
|
395
|
+
end
|
396
|
+
|
397
|
+
def test_should_use_sha_cipher
|
398
|
+
assert_instance_of EncryptedStrings::SymmetricCipher, @user.password.cipher
|
399
|
+
end
|
400
|
+
|
401
|
+
def test_should_use_custom_password
|
402
|
+
assert_equal 'key', @user.password.cipher.password
|
403
|
+
end
|
404
|
+
|
405
|
+
def test_should_be_able_to_check_password
|
406
|
+
assert_equal 'secret', @user.password
|
407
|
+
end
|
408
|
+
|
409
|
+
def teardown
|
410
|
+
User.class_eval do
|
411
|
+
@before_validation_callbacks = nil
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
class AsymmetricEncryptionTest < ActiveSupport::TestCase
|
417
|
+
def setup
|
418
|
+
User.encrypts :password, :mode => :asymmetric,
|
419
|
+
:private_key_file => File.dirname(__FILE__) + '/../keys/private',
|
420
|
+
:public_key_file => File.dirname(__FILE__) + '/../keys/public'
|
421
|
+
@user = create_user(:login => 'admin', :password => 'secret')
|
422
|
+
end
|
423
|
+
|
424
|
+
def test_should_encrypt_password
|
425
|
+
assert_not_equal 'secret', "#{@user.password}"
|
426
|
+
assert_equal 90, @user.password.length
|
427
|
+
end
|
428
|
+
|
429
|
+
def test_should_be_encrypted
|
430
|
+
assert @user.password.encrypted?
|
431
|
+
end
|
432
|
+
|
433
|
+
def test_should_use_sha_cipher
|
434
|
+
assert_instance_of EncryptedStrings::AsymmetricCipher, @user.password.cipher
|
435
|
+
end
|
436
|
+
|
437
|
+
def test_should_be_able_to_check_password
|
438
|
+
assert_equal 'secret', @user.password
|
439
|
+
end
|
440
|
+
|
441
|
+
def teardown
|
442
|
+
User.class_eval do
|
443
|
+
@before_validation_callbacks = nil
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class ShaCipherWithoutEmbeddingTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@cipher = EncryptedAttributes::ShaCipher.new('dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', :salt => 'custom_salt')
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_should_use_configured_salt
|
9
|
+
assert_equal 'custom_salt', @cipher.salt
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_should_not_embed_salt_in_encrypted_string
|
13
|
+
assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', @cipher.encrypt('secret')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ShaCipherWithNoSaltEmbeddedTest < Test::Unit::TestCase
|
18
|
+
def setup
|
19
|
+
@cipher = EncryptedAttributes::ShaCipher.new('dc0fc7c07bba982a8d8f18fe138dbea912df5e0e', :embed_salt => true, :salt => 'custom_salt')
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_should_use_configured_salt
|
23
|
+
assert_equal 'custom_salt', @cipher.salt
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_should_embed_salt_in_encrypted_string
|
27
|
+
assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', @cipher.encrypt('secret')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ShaCipherWithSaltEmbeddedTest < Test::Unit::TestCase
|
32
|
+
def setup
|
33
|
+
@cipher = EncryptedAttributes::ShaCipher.new('dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', :embed_salt => true, :salt => 'ignored_salt')
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_should_use_remaining_characters_after_password_for_salt
|
37
|
+
assert_equal 'custom_salt', @cipher.salt
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_should_embed_salt_in_encrypted_string
|
41
|
+
assert_equal 'dc0fc7c07bba982a8d8f18fe138dbea912df5e0ecustom_salt', @cipher.encrypt('secret')
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pluginaweek-encrypted_attributes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Pfeifer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-08 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: encrypted_strings
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.3.0
|
24
|
+
version:
|
25
|
+
description: Adds support for automatically encrypting ActiveRecord attributes
|
26
|
+
email: aaron@pluginaweek.org
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/encrypted_attributes
|
35
|
+
- lib/encrypted_attributes/sha_cipher.rb
|
36
|
+
- lib/encrypted_attributes.rb
|
37
|
+
- test/unit
|
38
|
+
- test/unit/encrypted_attributes_test.rb
|
39
|
+
- test/unit/sha_cipher_test.rb
|
40
|
+
- test/keys
|
41
|
+
- test/keys/private
|
42
|
+
- test/keys/public
|
43
|
+
- test/factory.rb
|
44
|
+
- test/app_root
|
45
|
+
- test/app_root/app
|
46
|
+
- test/app_root/app/models
|
47
|
+
- test/app_root/app/models/user.rb
|
48
|
+
- test/app_root/db
|
49
|
+
- test/app_root/db/migrate
|
50
|
+
- test/app_root/db/migrate/001_create_users.rb
|
51
|
+
- test/app_root/config
|
52
|
+
- test/app_root/config/environment.rb
|
53
|
+
- test/test_helper.rb
|
54
|
+
- CHANGELOG.rdoc
|
55
|
+
- init.rb
|
56
|
+
- LICENSE
|
57
|
+
- Rakefile
|
58
|
+
- README.rdoc
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: http://www.pluginaweek.org
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project: pluginaweek
|
81
|
+
rubygems_version: 1.2.0
|
82
|
+
signing_key:
|
83
|
+
specification_version: 2
|
84
|
+
summary: Adds support for automatically encrypting ActiveRecord attributes
|
85
|
+
test_files:
|
86
|
+
- test/unit/encrypted_attributes_test.rb
|
87
|
+
- test/unit/sha_cipher_test.rb
|