jcnnghm-acts_as_secure 1.0.6

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.
@@ -0,0 +1,39 @@
1
+ rel_1_0_5:
2
+ date: 2011-2-20
3
+ notes:
4
+ - Fixed Rails 3 deprecations
5
+
6
+ rel_1_0_1:
7
+ date: 2009-12-20
8
+ notes:
9
+ - Fixed AR serialization - now adds "serialize" to the model when adding callbacks
10
+ -- Added secure_column_symbols to return an array of columns to be secured, as symbols. Not very DRY, but works for now
11
+ - Defaulting to :text fields instead of binary for :storage type. To allow storing base64 encoded encrypted data
12
+ - Need to verify :only and :except returns a valid set of columns to secure - haven't tested this much at the moment.
13
+
14
+ rel_1_0_0:
15
+ date: 2009-12-20
16
+ notes:
17
+ - Updates to use AR serialization to manage storage of data to the DB
18
+ - Added acts_as_secure :only => [] option to allow specifying specific columns to be secured
19
+ - Added update_pk_password class method to allow setting the private key password
20
+ - Added gemspec
21
+ rel_0_0_4:
22
+ date: 2008-09-06
23
+ notes:
24
+ - No decryption for NULL fields
25
+
26
+ rel_0_0_3:
27
+ date: 2007-06-26
28
+ notes:
29
+ - Added support for nested crypto blocks
30
+
31
+ rel_0_0_2:
32
+ date: 2007-06-18
33
+ notes:
34
+ - Added support for model inheritance
35
+
36
+ rel_0_0_1:
37
+ date: 2005-04-26
38
+ notes:
39
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in acts_as_secure.gemspec
4
+ gemspec
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ jcnnghm-acts_as_secure (1.0.5)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ activemodel (3.0.4)
10
+ activesupport (= 3.0.4)
11
+ builder (~> 2.1.2)
12
+ i18n (~> 0.4)
13
+ activerecord (3.0.4)
14
+ activemodel (= 3.0.4)
15
+ activesupport (= 3.0.4)
16
+ arel (~> 2.0.2)
17
+ tzinfo (~> 0.3.23)
18
+ activesupport (3.0.4)
19
+ arel (2.0.8)
20
+ builder (2.1.2)
21
+ diff-lcs (1.1.2)
22
+ i18n (0.5.0)
23
+ rspec (2.5.0)
24
+ rspec-core (~> 2.5.0)
25
+ rspec-expectations (~> 2.5.0)
26
+ rspec-mocks (~> 2.5.0)
27
+ rspec-core (2.5.1)
28
+ rspec-expectations (2.5.0)
29
+ diff-lcs (~> 1.1.2)
30
+ rspec-mocks (2.5.0)
31
+ sqlite3 (1.3.3)
32
+ tzinfo (0.3.24)
33
+
34
+ PLATFORMS
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ activerecord (~> 3.0.4)
39
+ bundler (>= 1.0.0)
40
+ jcnnghm-acts_as_secure!
41
+ rspec (~> 2.4)
42
+ sqlite3
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Revolution Health Group LLC. All rights reserved.
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.
@@ -0,0 +1,152 @@
1
+ == Introduction
2
+
3
+ ActsAsSecure adds an ability to store ActiveRecord model's fields encrypted in a DB. When a model is marked with acts_as_secure, the :binary type fields are recognized as needed to be stored encrypted. The plugin does before_save/after_save/after_find encryption/decryption thus making it transparent for a code using the secure models.
4
+
5
+ The plugin supports a master key approach as well as individual records encryption keys. It does not contain any crypto provider but allows to plug in any external one as long as it supports encrypt/decrypt methods.
6
+
7
+ The fields are converted to a YAML form before encryption. After description they are restored via YAML.load. Since fields are stored encrypted, the find usage is very limited.
8
+
9
+ == Compatibility
10
+
11
+ This is only compatible with Rails 3.
12
+
13
+ == Installation
14
+
15
+ As gem, add the following to your gemfile.
16
+
17
+ gem 'jcnnghm-acts_as_secure'
18
+
19
+ == Usage
20
+
21
+ === Master Key Provider Usage
22
+
23
+ class SecureModel < ActiveRecord::Base
24
+ acts_as_secure :crypto_provider => MasterKeyProviderClassOrInstance
25
+ end
26
+
27
+ SecureModel.create()
28
+ SecureModel.find(:first)
29
+
30
+ === Individual Keys Provider Usage
31
+
32
+ class SecureModel < ActiveRecord::Base
33
+ acts_as_secure
34
+ end
35
+
36
+ SecureModel.with_crypto_provider(SomeProvider.new(some_param)) { SecureModel.find(:first) }
37
+ SecureModel.with_crypto_provider(SomeProvider.new(some_param)) { SecureModel.create() }
38
+
39
+ === Other Options
40
+
41
+ acts_as_secure :storage_type => :text -- changes the secure storage type from :binary to the supplied one
42
+ acts_as_secure :except => [:field1, :field2] -- disables the secure behavior for the :binary type fields in a supplied list
43
+
44
+ == Example
45
+
46
+ Let's define three models: Fruit (not secure), SecretFruit (uses the master key approach), and UberSecretFruit (uses the individual key approach).
47
+
48
+ Rot13CryptoProvider represents a master key provider. SaltedRot13CryptoProvider depends on a salt individual for each record.
49
+
50
+
51
+ === Models
52
+
53
+ class Fruit < ActiveRecord::Base
54
+ has_one :secret_fruit
55
+ has_one :uber_secret_fruit
56
+ end
57
+
58
+ class CreateFruits < ActiveRecord::Migration
59
+ def self.up
60
+ create_table :fruits do |t|
61
+ t.column :name, :string
62
+ end
63
+ end
64
+ end
65
+
66
+ class Rot13CryptoProvider
67
+ class << self
68
+ def encrypt(arg)
69
+ arg.tr("A-Za-z", "N-ZA-Mn-za-m")
70
+ end
71
+ alias_method :decrypt, :encrypt
72
+ end
73
+ end
74
+
75
+ class SecretFruit < ActiveRecord::Base
76
+ acts_as_secure :crypto_provider => Rot13CryptoProvider
77
+ belongs_to :fruit
78
+ end
79
+
80
+ class CreateSecretFruits < ActiveRecord::Migration
81
+ def self.up
82
+ create_table :secret_fruits do |t|
83
+ t.column :name, :binary
84
+ t.column :fruit_id, :integer
85
+ end
86
+ end
87
+ end
88
+
89
+ class SaltedRot13CryptoProvider
90
+ def initialize(salt)
91
+ @salt = salt
92
+ end
93
+ def encrypt(arg)
94
+ @salt + arg.tr("A-Za-z", "N-ZA-Mn-za-m")
95
+ end
96
+ def decrypt(arg)
97
+ arg[@salt.size .. -1].tr("A-Za-z", "N-ZA-Mn-za-m")
98
+ end
99
+ end
100
+
101
+ class UberSecretFruit < ActiveRecord::Base
102
+ acts_as_secure
103
+ belongs_to :fruit
104
+ end
105
+
106
+ class CreateUberSecretFruits < ActiveRecord::Migration
107
+ def self.up
108
+ create_table :uber_secret_fruits do |t|
109
+ t.column :name, :binary
110
+ t.column :fruit_id, :integer
111
+ end
112
+ end
113
+ end
114
+
115
+ === Usage
116
+
117
+ >> f = Fruit.create(:name => 'passion fruit')
118
+ >> SecretFruit.create(:name => 'maracuya', :fruit => f)
119
+ >> puts f.secret_fruit.name
120
+ maracuya
121
+ >> secret = readline.chomp
122
+ uber_secret
123
+ >> crypto_provider = SaltedRot13CryptoProvider.new(secret)
124
+ >> UberSecretFruit.with_crypto_provider(crypto_provider) { UberSecretFruit.create(:name => 'Passiflora edulis', :fruit => f) }
125
+ >> UberSecretFruit.with_crypto_provider(crypto_provider) { puts f.uber_secret_fruit.name }
126
+ Passiflora edulis
127
+
128
+ === DB
129
+
130
+ > select * from secret_fruits;
131
+ +----+---------------+----------+
132
+ | id | name | fruit_id |
133
+ +----+---------------+----------+
134
+ | 1 | --- znenphln | 1 |
135
+ +----+---------------+----------+
136
+
137
+ > select * from uber_secret_fruits;
138
+ +----+-----------------------------------+----------+
139
+ | id | name | fruit_id |
140
+ +----+-----------------------------------+----------+
141
+ | 1 | uber_secret--- Cnffvsyben rqhyvf | 1 |
142
+ +----+-----------------------------------+----------+
143
+
144
+
145
+ == Running Tests
146
+
147
+ bundle exec rspec spec
148
+
149
+ == License
150
+
151
+ ActsAsSecure released under the MIT license.
152
+
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/acts_as_secure/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "jcnnghm-acts_as_secure"
6
+ s.version = ActsAsSecure::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ['Justin Cunningham' , '2007 Revolution Health Group LLC.']
9
+ s.email = ['justin@compucatedsolutions.com']
10
+ s.homepage = "http://rubygems.org/gems/jcnnghm-acts_as_secure"
11
+ s.summary = "ActsAsSecure adds an ability to store ActiveRecord model's fields encrypted in a DB."
12
+ s.description = "When a model is marked with acts_as_secure, the :binary type fields are recognized as needed to be stored encrypted.
13
+ The plugin does before_save/after_save/after_find encryption/decryption thus making it transparent for a
14
+ code using the secure models."
15
+
16
+ s.required_rubygems_version = ">= 1.3.6"
17
+ s.rubyforge_project = "jcnnghm-acts_as_secure"
18
+
19
+ s.add_development_dependency "bundler", ">= 1.0.0"
20
+ s.add_development_dependency "rspec", "~> 2.4"
21
+ s.add_development_dependency "activerecord", '~> 3.0.4'
22
+ s.add_development_dependency "sqlite3"
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
26
+ s.require_path = 'lib'
27
+ end
@@ -0,0 +1 @@
1
+ require 'acts_as_secure/acts_as_secure'
@@ -0,0 +1,120 @@
1
+ module ActiveRecord; module Acts; end; end
2
+
3
+ module ActiveRecord::Acts::ActsAsSecure
4
+
5
+ require 'yaml'
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def acts_as_secure(options = {})
14
+ parse_options!(options)
15
+ add_callbacks
16
+ extend(ActsAsSecureClassMethods)
17
+ send(:include, InstanceMethods)
18
+ end
19
+
20
+ private
21
+
22
+ def parse_options!(options)
23
+ @secure_except = unsecure_columns(options.delete(:except))
24
+ @secure_storage_type = options.delete(:storage_type) || :binary
25
+ @secure_crypto_provider = options.delete(:crypto_provider)
26
+ fail("Unknown option(s): #{ options.keys.join(', ') }") unless options.empty?
27
+ end
28
+
29
+ def add_callbacks
30
+ before_save :encrypt_secure_columns
31
+ after_save :decrypt_secure_columns
32
+ after_find :decrypt_secure_columns
33
+ end
34
+
35
+ def unsecure_columns(*names)
36
+ names.flatten.collect(&:to_s)
37
+ end
38
+
39
+ module ActsAsSecureClassMethods
40
+
41
+ def inherited(sub)
42
+
43
+ [:secure_except, :secure_storage_type, :secure_crypto_provider].each do |p|
44
+ sub.instance_variable_set("@#{ p }", instance_variable_get("@#{ p }"))
45
+ end
46
+
47
+ super
48
+
49
+ end
50
+
51
+ def with_crypto_provider(provider)
52
+ begin
53
+ original_provider = @secure_crypto_provider
54
+ @secure_crypto_provider = provider
55
+ yield
56
+ ensure
57
+ @secure_crypto_provider = original_provider
58
+ end
59
+ end
60
+
61
+ def secure_columns
62
+ columns.reject { |col| (col.type != @secure_storage_type) || @secure_except.include?(col.name) }
63
+ end
64
+
65
+ def secure_crypto_provider
66
+ @secure_crypto_provider
67
+ end
68
+
69
+ end
70
+
71
+
72
+ module InstanceMethods
73
+
74
+ def encrypt_secure_columns
75
+ self.class.secure_columns.each do |col|
76
+ self[col.name] = secure_encrypt(self[col.name])
77
+ end
78
+ end
79
+
80
+ def decrypt_secure_columns
81
+ @encrypted_attributes = {}
82
+ self.class.secure_columns.each do |col|
83
+ @encrypted_attributes[col.name] = send("#{ col.name }_before_type_cast")
84
+ self[col.name] = secure_decrypt(send("#{ col.name }_before_type_cast")) unless self[col.name].nil?
85
+ end
86
+ end
87
+
88
+ def read_attribute_before_decryption(attr_name)
89
+ if @encrypted_attributes[attr_name.to_s].nil?
90
+ self[attr_name]
91
+ else
92
+ @encrypted_attributes[attr_name.to_s]
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def secure_encrypt(arg)
99
+ secure_crypto_provider.encrypt(arg.to_yaml)
100
+ end
101
+
102
+ def secure_decrypt(arg)
103
+ begin
104
+ YAML.load(secure_crypto_provider.decrypt(arg))
105
+ rescue Exception => ex
106
+ raise "Failed to decode the field. Incorrect key?"
107
+ end
108
+ end
109
+
110
+ def secure_crypto_provider
111
+ self.class.secure_crypto_provider || fail('No crypto provider defined')
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ end
119
+
120
+ ActiveRecord::Base.send(:include, ActiveRecord::Acts::ActsAsSecure)
@@ -0,0 +1,3 @@
1
+ module ActsAsSecure
2
+ VERSION = "1.0.6"
3
+ end
@@ -0,0 +1 @@
1
+ require 'acts_as_secure'
@@ -0,0 +1 @@
1
+ active_record.log
@@ -0,0 +1,55 @@
1
+ require 'test_in_memory'
2
+
3
+ describe "acts_as_secure" do
4
+ it "should make sure README works" do
5
+ f = Fruit.create(:name => 'passion fruit')
6
+ SecretFruit.create(:name => 'maracuya', :fruit => f)
7
+ f.secret_fruit.name.should eql('maracuya')
8
+
9
+ secret = 'uber_secret'
10
+ crypto_provider = SaltedRot13CryptoProvider.new(secret)
11
+ UberSecretFruit.with_crypto_provider(crypto_provider) { UberSecretFruit.create(:name => 'Passiflora edulis', :fruit => f) }
12
+ UberSecretFruit.with_crypto_provider(crypto_provider) { f.uber_secret_fruit.name.should eql('Passiflora edulis') }
13
+ end
14
+
15
+ it "should allow you to read attributes before encryption" do
16
+ f = Fruit.create(:name => 'passion fruit')
17
+ SecretFruit.create(:name => 'maracuya', :fruit => f)
18
+
19
+ f.secret_fruit.read_attribute_before_decryption(:name).should eql('znenphln'.to_yaml)
20
+ f.secret_fruit.name.should eql('maracuya')
21
+ end
22
+
23
+ it "should encrypt properly" do
24
+ f = Fruit.create(:name => 'passion fruit')
25
+ SecretFruit.create(:name => 'maracuya', :fruit => f)
26
+
27
+ f.secret_fruit.read_attribute_before_decryption(:name).should eql(f.secret_fruit.send(:secure_encrypt, 'maracuya'))
28
+ end
29
+
30
+ it "should test secure columns" do
31
+ SecretFruit.secure_columns.map(&:name).should_not include('id')
32
+ SecretFruit.secure_columns.map(&:name).should_not include('fruit_id')
33
+ SecretFruit.secure_columns.map(&:name).should include('name')
34
+ end
35
+
36
+ it "should test except" do
37
+ class SecretFruit < ActiveRecord::Base
38
+ acts_as_secure :crypto_provider => Rot13CryptoProvider, :except => [:name]
39
+ belongs_to :fruit
40
+ end
41
+
42
+ SecretFruit.secure_columns.should be_blank
43
+ end
44
+
45
+ it "should allow the storage type to be selected" do
46
+ class SecretFruit < ActiveRecord::Base
47
+ acts_as_secure :crypto_provider => Rot13CryptoProvider, :storage_type => :integer
48
+ belongs_to :fruit
49
+ end
50
+
51
+ SecretFruit.secure_columns.map(&:name).should include('id')
52
+ SecretFruit.secure_columns.map(&:name).should include('fruit_id')
53
+ SecretFruit.secure_columns.map(&:name).should_not include('name')
54
+ end
55
+ end