attr_redactor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7ef566d6a681d20457b44307df0704879c64814b
4
+ data.tar.gz: 529a1c447257ad7b692d36847f1625bbd70354b0
5
+ SHA512:
6
+ metadata.gz: fd65f904aac0a3827d8d108c2ba1b2481d3696882c046e8b1782db61899b1b3e45e092dfe3d745758cb121fb750c5caa6eee21f1dfd550d8abf8682d4f4cc2de
7
+ data.tar.gz: 4c96ba87e0a8a90eb78c3776d53487839843829694133167630bb33c29a5b2e5ebbcd5d1395496e0b80bf77ab160eddd1fae2f20ac50e68459b9ae7c74c280dd
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .bundle
2
+ .DS_Store
3
+ .ruby-version
4
+ pkg
5
+ Gemfile.lock
6
+ coverage
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.0
6
+ - 2.1
7
+ - 2.3.0
8
+ env:
9
+ - ACTIVERECORD=3.0.0
10
+ - ACTIVERECORD=3.2.0
11
+ - ACTIVERECORD=4.0.0
12
+ - ACTIVERECORD=4.2.0
13
+ matrix:
14
+ exclude:
15
+ allow_failures:
16
+ - rvm: rbx
17
+ fast_finish: true
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # attr_redactor #
2
+
3
+ ##0.1.0 ##
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Sean Huber - shuber@huberry.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,259 @@
1
+ [![Gem Version](https://badge.fury.io/rb/attr_redactor.svg)](https://badge.fury.io/rb/attr_redactor)
2
+ [![Build Status](https://travis-ci.org/chrisjensen/attr_redactor.svg?branch=master)](https://travis-ci.org/chrisjensen/attr_redactor)
3
+ [![Test Coverage](https://codeclimate.com/github/chrisjensen/attr_redactor/badges/coverage.svg)](https://codeclimate.com/github/chrisjensen/attr_redactor/coverage)
4
+ [![Code Climate](https://codeclimate.com/github/chrisjensen/attr_redactor/badges/gpa.svg)](https://codeclimate.com/github/chrisjensen/attr_redactor)
5
+ [![security](https://hakiri.io/github/chrisjensen/attr_redactor/master.svg)](https://hakiri.io/github/chrisjensen/attr_redactor/master)
6
+
7
+ # attr_redactor
8
+
9
+ Generates attr_accessors that transparently redact a hash attribute by removing, digesting or encrypting certain keys.
10
+
11
+ This code is based off of the [attr_encrypted/attr_encrypted](https://github.com/attr-encrypted/attr_encrypted) code base.
12
+
13
+ Helper classes for `ActiveRecord`,
14
+
15
+ `DataMapper`, and `Sequel` helpers have been retained, but have not been successfully tested.
16
+
17
+ ## Installation
18
+
19
+ Add attr_redactor to your gemfile:
20
+
21
+ ```ruby
22
+ gem "attr_redactor"
23
+ ```
24
+
25
+ Then install the gem:
26
+
27
+ ```bash
28
+ bundle install
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ If you're using an ORM like `ActiveRecord` using attr_redactor is easy:
34
+
35
+ ```ruby
36
+ class User
37
+ attr_redactor :user_data, redact: { :ssn => :remove, :email => :encrypt }
38
+ end
39
+ ```
40
+
41
+ If you're using a PORO, you have to do a little bit more work by extending the class:
42
+
43
+ ```ruby
44
+ class User
45
+ extend AttrRedactor
46
+ attr_accessor :name
47
+ attr_redactor :user_data, redact: { :ssn => :remove, :email => :encrypt }, encryption_key: 'YOUR ENCRYPTION KEY'
48
+
49
+ def load
50
+ # loads the stored data
51
+ end
52
+
53
+ def save
54
+ # saves the :name and :redacted_user_data attributes somewhere (e.g. filesystem, database, etc)
55
+ end
56
+ end
57
+
58
+ user = User.new
59
+ user.user_data = { ssn: '123-45-6789', email: 'personal@email.com' }
60
+ user.redacted_data[:encrypted_email] # returns the encrypted version of :ssn
61
+ user.redacted_data.has_key?(:email) # false
62
+ user.save
63
+
64
+ user = User.load
65
+ user.data { email: 'personal@email.com' }
66
+ ```
67
+
68
+ When you set a redacted attribute the data is *immediately* redacted and the attribute replaced.
69
+ This is to avoid confusion or inconsistency when passing a record around that might be freshly created or loaded from the DB.
70
+
71
+ ```
72
+ user = User.new user_data: { ssn: '123-45-6789', email: 'personal@email.com' }
73
+
74
+ user.user_data[:ssn] # nil
75
+ ```
76
+
77
+ ### Note on Updating Data
78
+
79
+ Changes within the hash may not be saved
80
+
81
+ To ensure ActiveRecord saves changed data, you should always update the hash entirely, not keys within the hash.
82
+
83
+ ```
84
+ user = User.new user_data: { ssn: '123-45-6789', email: 'personal@email.com' }
85
+
86
+ user.user_data[:email] = 'new_address@gmail.com'
87
+ user.save!
88
+
89
+ user.reload
90
+ user.user_data[:email] # 'personal@email.com'
91
+ ```
92
+
93
+ ### attr_redacted with database persistence
94
+
95
+ By default, `attr_redacted` stores the redacted data in `:redacted_<attribute>`.
96
+
97
+ Create or modify the table that your model uses to add a column with the `redacted_` prefix (which can be modified, see below), e.g. `redacted_ssn` via a migration like the following:
98
+
99
+ ```ruby
100
+ create_table :users do |t|
101
+ t.string :name
102
+ t.jsonb :redacted_user_data
103
+ t.timestamps
104
+ end
105
+ ```
106
+
107
+ ### Specifying the redacted attribute name
108
+
109
+ By default, the redacted attribute name is `redacted_#{attribute}` (e.g. `attr_redacted :data` would create an attribute named `redacted_data`). So, if you're storing the redacted attribute in the database, you need to make sure the `redacted_#{attribute}` field exists in your table. You have a couple of options if you want to name your attribute or db column something else, see below for more details.
110
+
111
+
112
+ ## attr_redacted options
113
+
114
+ #### Options are evaluated
115
+ All options will be evaluated at the instance level.
116
+
117
+ ### Default options
118
+
119
+ The following are the default options used by `attr_redacted`:
120
+
121
+ ```ruby
122
+ prefix: 'redacted_',
123
+ suffix: '',
124
+ marshal: false,
125
+ marshaler: Marshal,
126
+ dump_method: 'dump',
127
+ load_method: 'load',
128
+ ```
129
+
130
+ Additionally, you can specify default options for all redacted attributes in your class. Instead of having to define your class like this:
131
+
132
+ ```ruby
133
+ class User
134
+ attr_redacted :email, prefix: '', suffix: '_redacted'
135
+ attr_redacted :ssn, prefix: '', suffix: '_redacted'
136
+ attr_redacted :credit_card, prefix: '', suffix: '_redacted'
137
+ end
138
+ ```
139
+
140
+ You can simply define some default options like so:
141
+
142
+ ```ruby
143
+ class User
144
+ attr_redacted_options.merge!(prefix: '', :suffix => '_crypted')
145
+ attr_redacted :email
146
+ attr_redacted :ssn
147
+ attr_redacted :credit_card
148
+ end
149
+ ```
150
+
151
+ This should help keep your classes clean and DRY.
152
+
153
+ ### The `:attribute` option
154
+
155
+ You can simply pass the name of the redacted attribute as the `:attribute` option:
156
+
157
+ ```ruby
158
+ class User
159
+ attr_redacted :data, attribute: 'obfuscated_data'
160
+ end
161
+ ```
162
+
163
+ This would generate an attribute named `obfuscated_data`
164
+
165
+
166
+ ### The `:prefix` and `:suffix` options
167
+
168
+ If you don't like the `redacted_#{attribute}` naming convention then you can specify your own:
169
+
170
+ ```ruby
171
+ class User
172
+ attr_redacted :data, prefix: 'secret_', suffix: '_hidden'
173
+ end
174
+ ```
175
+
176
+ This would generate the attribute: `secret_data_hidden`.
177
+
178
+ ### The `:encryption_key` option
179
+
180
+ Specifies the encryption key to use for encrypted data in the hash.
181
+ This *must* be present if you use encryption.
182
+
183
+ ### The `:digest_salt` option
184
+
185
+ Specifies a salt to use when digesting.
186
+ If not present then your data will be hashed *without* a salt which makes it less secure.
187
+
188
+ ### The `:encode`, `:encode_iv`, and `:default_encoding` options
189
+
190
+ You're probably going to be storing your redacted attributes somehow (e.g. filesystem, database, etc). You can pass the `:encode` option to automatically encode/decode when encrypting/hashing/decrypting. The default behavior assumes that you're using a string column type and will base64 encode your cipher text. If you choose to use the binary column type then encoding is not required, but be sure to pass in `false` with the `:encode` option.
191
+
192
+ ```ruby
193
+ class User
194
+ attr_redacted :data, encode: true, encode_iv: true
195
+ end
196
+ ```
197
+
198
+ The default encoding is `m` (base64). You can change this by setting `encode: 'some encoding'`. See [`Arrary#pack`](http://ruby-doc.org/core-2.3.0/Array.html#method-i-pack) for more encoding options.
199
+
200
+ ## ORMs
201
+
202
+ ### ActiveRecord
203
+
204
+ If you're using this gem with `ActiveRecord`, you get a few extra features:
205
+
206
+ ## Things to consider before using attr_redacted
207
+
208
+ #### Data gone immediately
209
+ Obviously, anything you decide to digest or remove, that will be done immediately and you will have no way to recover that data (except keeping a copy of the original hash)
210
+
211
+ #### Searching, joining, etc
212
+ You cannot search encrypted or hashed data (or, obviously, removed data), and because you can't search it, you can't index it either. You also can't use joins on the redacted data. Data that is securely encrypted is effectively noise.
213
+ So any operations that rely on the data not being noise will not work. If you need to do any of the aforementioned operations, please consider using database and file system encryption along with transport encryption as it moves through your stack.
214
+ Since redacting uses a hash that is comparable, you could still index digested columns
215
+
216
+ #### Data leaks
217
+ Please also consider where your data leaks. If you're using attr_redacted with Rails, it's highly likely that this data will enter your app as a request parameter. You'll want to be sure that you're filtering your request params from you logs or else your data is sitting in the clear in your logs. [Parameter Filtering in Rails](http://apidock.com/rails/ActionDispatch/Http/FilterParameters) Please also consider other possible leak points.
218
+
219
+ #### Metadata regarding your crypto implementation
220
+ It is advisable to also store metadata regarding the circumstances of your encrypted data. Namely, you should store information about the key used to encrypt your data, as well as the algorithm. Having this metadata with every record will make key rotation and migrating to a new algorithm signficantly easier. It will allow you to continue to decrypt old data using the information provided in the metadata and new data can be encrypted using your new key and algorithm of choice.
221
+
222
+ ## Testing
223
+ To verify you've configured redaction properly in your tests, use `attr_redacted?` and `attr_redact_hash`
224
+
225
+ Given class:
226
+
227
+ ```ruby
228
+ class User
229
+ attr_redacted :data, redact: { :ssn => :remove, :email => :encrypt }
230
+ end
231
+ ```
232
+
233
+ ### Minitest
234
+ ```ruby
235
+ def test_should_redact_dataa
236
+ expected_hash = { :ssn => :remove, :email => :encrypt }
237
+ assert User.new.attr_redacted?(:data)
238
+ assert_equal expected_hash, User.new.data_redact_hash
239
+ end
240
+ ```
241
+
242
+ ### RSpec
243
+ ```ruby
244
+ it "should redact data"
245
+ expect(User.new.attr_redacted?(:data)).to be_truthy
246
+ expect(User.new.data_redact_hash).to eq({ :ssn => :remove, :email => :encrypt })
247
+ end
248
+ ```
249
+
250
+
251
+
252
+ ## Note on Patches/Pull Requests
253
+
254
+ * Fork the project.
255
+ * Make your feature addition or bug fix.
256
+ * Add tests for it. This is important so I don't break it in a
257
+ future version unintentionally.
258
+ * Commit, do not mess with rakefile, version, changelog, or history.
259
+ * Send me a pull request. Bonus points for topic branches.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake/testtask'
2
+ require 'rdoc/task'
3
+ require "bundler/gem_tasks"
4
+
5
+ desc 'Test the attr_redactor gem.'
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'lib'
8
+ t.pattern = 'test/**/*_test.rb'
9
+ t.verbose = true
10
+ end
11
+
12
+ desc 'Generate documentation for the attr_redactor gem.'
13
+ Rake::RDocTask.new(:rdoc) do |rdoc|
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = 'attr_redactor'
16
+ rdoc.options << '--line-numbers' << '--inline-source'
17
+ rdoc.rdoc_files.include('README*')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
20
+
21
+ desc 'Default: run unit tests.'
22
+ task :default => :test
@@ -0,0 +1,52 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib/', __FILE__)
4
+ $:.unshift lib unless $:.include?(lib)
5
+
6
+ require 'attr_redactor/version'
7
+ require 'date'
8
+
9
+ Gem::Specification.new do |s|
10
+ s.name = 'attr_redactor'
11
+ s.version = AttrRedactor::Version.string
12
+ s.date = Date.today
13
+
14
+ s.summary = 'Redact JSON attributes before saving'
15
+ s.description = 'Generates attr_accessors that redact certain values in the JSON structure before saving.'
16
+
17
+ s.authors = ['Chris Jensen']
18
+ s.email = ['chris@broadthought.co']
19
+ s.homepage = 'http://github.com/chrisjensen/attr_redactor'
20
+
21
+ s.has_rdoc = false
22
+ s.rdoc_options = ['--line-numbers', '--inline-source', '--main', 'README.rdoc']
23
+
24
+ s.require_paths = ['lib']
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- test/*`.split("\n")
28
+
29
+ s.required_ruby_version = '>= 2.0.0'
30
+
31
+ s.add_dependency('hash_redactor', ['~> 0.2.1'])
32
+ # support for testing with specific active record version
33
+ activerecord_version = if ENV.key?('ACTIVERECORD')
34
+ "~> #{ENV['ACTIVERECORD']}"
35
+ else
36
+ '~> 3.0'
37
+ end
38
+ s.add_development_dependency('activerecord', activerecord_version)
39
+ s.add_development_dependency('actionpack', activerecord_version)
40
+ s.add_development_dependency('datamapper')
41
+ s.add_development_dependency('rake')
42
+ s.add_development_dependency('minitest')
43
+ s.add_development_dependency('sequel')
44
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE.to_sym == :jruby
45
+ s.add_development_dependency('activerecord-jdbcsqlite3-adapter')
46
+ s.add_development_dependency('jdbc-sqlite3', '< 3.8.7') # 3.8.7 is nice and broke
47
+ else
48
+ s.add_development_dependency('sqlite3')
49
+ end
50
+ s.add_development_dependency('dm-sqlite-adapter')
51
+ s.add_development_dependency("codeclimate-test-reporter")
52
+ end
@@ -0,0 +1,86 @@
1
+ if defined?(ActiveRecord::Base)
2
+ module AttrRedactor
3
+ module Adapters
4
+ module ActiveRecord
5
+ def self.extended(base) # :nodoc:
6
+ base.class_eval do
7
+
8
+ # https://github.com/attr-encrypted/attr_encrypted/issues/68
9
+ alias_method :reload_without_attr_redactor, :reload
10
+ def reload(*args, &block)
11
+ result = reload_without_attr_redactor(*args, &block)
12
+ self.class.redacted_attributes.keys.each do |attribute_name|
13
+ instance_variable_set("@#{attribute_name}", nil)
14
+ end
15
+ result
16
+ end
17
+
18
+ def perform_attribute_assignment(method, new_attributes, *args)
19
+ return if new_attributes.blank?
20
+
21
+ send method, new_attributes.reject { |k, _| self.class.redacted_attributes.key?(k.to_sym) }, *args
22
+ send method, new_attributes.reject { |k, _| !self.class.redacted_attributes.key?(k.to_sym) }, *args
23
+ end
24
+ private :perform_attribute_assignment
25
+
26
+ if ::ActiveRecord::VERSION::STRING > "3.1"
27
+ alias_method :assign_attributes_without_attr_redactor, :assign_attributes
28
+ def assign_attributes(*args)
29
+ perform_attribute_assignment :assign_attributes_without_attr_redactor, *args
30
+ end
31
+ end
32
+
33
+ alias_method :attributes_without_attr_redactor=, :attributes=
34
+ def attributes=(*args)
35
+ perform_attribute_assignment :attributes_without_attr_redactor=, *args
36
+ end
37
+ end
38
+ end
39
+
40
+ protected
41
+
42
+ # <tt>attr_redactor</tt> method
43
+ def attr_redactor(*attrs)
44
+ super
45
+ options = attrs.extract_options!
46
+ attr = attrs.pop
47
+ options.merge! redacted_attributes[attr]
48
+
49
+ define_method("#{attr}_changed?") do
50
+ if send("#{options[:attribute]}_changed?")
51
+ send(attr) != send("#{attr}_was")
52
+ end
53
+ end
54
+
55
+ define_method("#{attr}_was") do
56
+ attr_was_options = { operation: :unredacting }
57
+ redacted_attributes[attr].merge!(attr_was_options)
58
+ evaluated_options = evaluated_attr_redacted_options_for(attr)
59
+ [:iv, :salt, :operation].each { |key| redacted_attributes[attr].delete(key) }
60
+ self.class.unredact(attr, send("#{options[:attribute]}_was"), evaluated_options)
61
+ end
62
+
63
+ alias_method "#{attr}_before_type_cast", attr
64
+ end
65
+
66
+ def attribute_instance_methods_as_symbols
67
+ # We add accessor methods of the db columns to the list of instance
68
+ # methods returned to let ActiveRecord define the accessor methods
69
+ # for the db columns
70
+
71
+ # Use with_connection so the connection doesn't stay pinned to the thread.
72
+ connected = ::ActiveRecord::Base.connection_pool.with_connection(&:active?) rescue false
73
+
74
+ if connected && table_exists?
75
+ columns_hash.keys.inject(super) {|instance_methods, column_name| instance_methods.concat [column_name.to_sym, :"#{column_name}="]}
76
+ else
77
+ super
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ ActiveRecord::Base.extend AttrRedactor
85
+ ActiveRecord::Base.extend AttrRedactor::Adapters::ActiveRecord
86
+ end
@@ -0,0 +1,21 @@
1
+ if defined?(DataMapper)
2
+ module AttrRedactor
3
+ module Adapters
4
+ module DataMapper
5
+ def self.extended(base) # :nodoc:
6
+ class << base
7
+ alias_method :included_without_attr_redactor, :included
8
+ alias_method :included, :included_with_attr_redactor
9
+ end
10
+ end
11
+
12
+ def included_with_attr_redactor(base)
13
+ included_without_attr_redactor(base)
14
+ base.extend AttrRedactor
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ DataMapper::Resource.extend AttrRedactor::Adapters::DataMapper
21
+ end
@@ -0,0 +1,17 @@
1
+ module AttrRedactor
2
+ # Contains information about this gem's version
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 0
7
+
8
+ # Returns a version string by joining <tt>MAJOR</tt>, <tt>MINOR</tt>, and <tt>PATCH</tt> with <tt>'.'</tt>
9
+ #
10
+ # Example
11
+ #
12
+ # Version.string # '1.0.2'
13
+ def self.string
14
+ [MAJOR, MINOR, PATCH].join('.')
15
+ end
16
+ end
17
+ end