attr_masker 0.1.0 → 0.3.1

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.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/tests.yml +91 -0
  3. data/.gitignore +5 -1
  4. data/.rubocop.yml +13 -1069
  5. data/CHANGELOG.adoc +31 -0
  6. data/Gemfile +5 -0
  7. data/README.adoc +81 -30
  8. data/Rakefile +0 -27
  9. data/attr_masker.gemspec +15 -10
  10. data/bin/console +14 -0
  11. data/bin/rake +29 -0
  12. data/bin/rspec +29 -0
  13. data/bin/rubocop +29 -0
  14. data/bin/setup +9 -0
  15. data/gemfiles/Rails-4.2.gemfile +2 -3
  16. data/gemfiles/Rails-5.0.gemfile +2 -3
  17. data/gemfiles/Rails-5.1.gemfile +2 -3
  18. data/gemfiles/Rails-5.2.gemfile +4 -0
  19. data/gemfiles/Rails-6.0.gemfile +3 -0
  20. data/gemfiles/Rails-6.1.gemfile +3 -0
  21. data/gemfiles/Rails-head.gemfile +1 -3
  22. data/gemfiles/common.gemfile +4 -0
  23. data/lib/attr_masker.rb +6 -210
  24. data/lib/attr_masker/attribute.rb +80 -0
  25. data/lib/attr_masker/error.rb +1 -0
  26. data/lib/attr_masker/maskers/replacing.rb +20 -3
  27. data/lib/attr_masker/maskers/simple.rb +20 -5
  28. data/lib/attr_masker/model.rb +143 -0
  29. data/lib/attr_masker/performer.rb +56 -17
  30. data/lib/attr_masker/version.rb +1 -16
  31. data/lib/tasks/db.rake +13 -4
  32. data/spec/dummy/app/models/non_persisted_model.rb +2 -0
  33. data/spec/dummy/config/attr_masker.rb +1 -0
  34. data/spec/dummy/config/mongoid.yml +33 -0
  35. data/spec/dummy/config/routes.rb +0 -1
  36. data/spec/dummy/db/schema.rb +1 -0
  37. data/spec/features/active_record_spec.rb +97 -0
  38. data/spec/features/mongoid_spec.rb +36 -0
  39. data/spec/features/shared_examples.rb +382 -0
  40. data/spec/spec_helper.rb +26 -3
  41. data/spec/support/00_control_constants.rb +2 -0
  42. data/spec/support/10_mongoid_env.rb +9 -0
  43. data/spec/support/20_combustion.rb +10 -0
  44. data/spec/support/db_cleaner.rb +13 -2
  45. data/spec/support/force_config_file_reload.rb +9 -0
  46. data/spec/support/rake.rb +1 -1
  47. data/spec/unit/attribute_spec.rb +210 -0
  48. data/spec/{maskers → unit/maskers}/replacing_spec.rb +0 -0
  49. data/spec/{maskers → unit/maskers}/simple_spec.rb +2 -2
  50. data/spec/unit/model_spec.rb +12 -0
  51. data/spec/unit/rake_task_spec.rb +30 -0
  52. metadata +139 -32
  53. data/.travis.yml +0 -32
  54. data/gemfiles/Rails-4.0.gemfile +0 -5
  55. data/gemfiles/Rails-4.1.gemfile +0 -5
  56. data/spec/features_spec.rb +0 -203
  57. data/spec/support/0_combustion.rb +0 -5
data/CHANGELOG.adoc ADDED
@@ -0,0 +1,31 @@
1
+ == Not released yet
2
+
3
+ * Drop support for Rails < 4.2
4
+ * Allow masking attributes which span on more than one column (or field),
5
+ supersede `column_name` option with `column_names`
6
+
7
+ == 0.2.1
8
+
9
+ * Bugfix: preload application to find all models
10
+ * Bugfix: require all dependencies correctly
11
+
12
+ == 0.2.0
13
+
14
+ * `AttrMasker::Maskers::Simple` is now a class
15
+ * Added support for Mongoid
16
+
17
+ == 0.1.1
18
+
19
+ * Mask records disregarding default scope
20
+ (https://github.com/riboseinc/attr_masker/pull/41[#41])
21
+ * Major refactoring (extracting `Attribute` and `Model` classes)
22
+ * Code style improvements (nearly all violations fixed)
23
+
24
+ == 0.1
25
+
26
+ * First useful version
27
+ * Rails 4 & 5 compatibility
28
+ * Callable objects as maskers
29
+ * Nice progress bar
30
+ * Built-in maskers: `AttrMasker::Maskers::Replacing`
31
+ and `AttrMasker::Maskers::SIMPLE`
data/Gemfile CHANGED
@@ -1,3 +1,8 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
+
5
+ gem "mongoid", require: false
6
+
7
+ gem "database_cleaner-active_record", "~> 1.8", require: false
8
+ gem "database_cleaner-mongoid", "~> 1.8", require: false
data/README.adoc CHANGED
@@ -3,17 +3,33 @@
3
3
  :pygments-style: native
4
4
  :pygments-linenums-mode: inline
5
5
 
6
- image:https://img.shields.io/travis/riboseinc/attr_masker/master.svg["Build Status", link="https://travis-ci.org/riboseinc/attr_masker"]
7
-
8
- Mask ActiveRecord data with ease!
6
+ ifdef::env-github[]
7
+ image:https://img.shields.io/gem/v/attr_masker[
8
+ "Gem Version",
9
+ link="https://rubygems.org/gems/attr_masker"]
10
+ image:https://img.shields.io/github/workflow/status/riboseinc/attr_masker/Tests[
11
+ "Build Status",
12
+ link="https://github.com/riboseinc/attr_masker/actions"]
13
+ image:https://img.shields.io/codeclimate/maintainability/riboseinc/attr_masker[
14
+ "Code Climate",
15
+ link="https://codeclimate.com/github/riboseinc/attr_masker"]
16
+ image:https://img.shields.io/codecov/c/github/riboseinc/attr_masker[
17
+ "Test Coverage",
18
+ link="https://codecov.io/gh/riboseinc/attr_masker"]
19
+ image:https://img.shields.io/badge/documentation-rdoc-informational[
20
+ "Documentation on RubyDoc.info",
21
+ link="https://rubydoc.info/gems/attr_masker"]
22
+ endif::[]
23
+
24
+ Mask ActiveRecord/Mongoid data with ease!
9
25
 
10
26
  == Introduction
11
27
 
12
28
  This gem is intended to mask sensitive data so that production database dumps
13
- can be used in staging or test environments. It works with Active Record 4+
14
- and modern Rubies.
29
+ can be used in staging or test environments. It works with Rails 4.2+ and
30
+ modern Rubies. It supports Active Record and Mongoid models.
15
31
 
16
- == Getting started
32
+ == Usage instructions
17
33
 
18
34
  === Installation
19
35
 
@@ -68,33 +84,16 @@ attr_masker :email :unless => ->(record) { ! record.tester_user? }
68
84
  attr_masker :first_name, :if => :tester_user?
69
85
  ----
70
86
 
71
- === Using custom maskers
72
-
73
- By default, data is maksed with `AttrMasker::Maskers::SIMPLE` masker which
74
- always returns `"(redacted)"` string. But anything what responds to `#call`
75
- can be used instead: a lambda, `Method` instance, and more. You can specify it
76
- by setting the `:masker` option.
77
-
78
- For instance, you may want to use https://github.com/ffaker/ffaker[ffaker] or
79
- https://github.com/skalee/well_read_faker[Well Read Faker] to generate random
80
- replacement values:
81
-
82
- [source,ruby]
83
- ----
84
- require "ffaker"
85
-
86
- attr_masker :first_name, :masker => ->(_hash) { FFaker::Name.first_name }
87
- ----
88
-
89
- A hash is passed as an argument, which includes some information about the
90
- record being masked and the attribute value. It can be used to further
91
- customize masker's behaviour.
87
+ The ActiveRecord's `::default_scope` method has no effect on masking. All
88
+ table records are updated, provided that :if and :unless filters allow that.
89
+ For example, if you're using a https://github.com/rubysherpas/paranoia[Paranoia]
90
+ gem to soft-delete your data, records marked as deleted will be masked as well.
92
91
 
93
92
  === Built-in maskers
94
93
 
95
94
  Attr Masker comes with several built-in maskers.
96
95
 
97
- `AttrMasker::Maskers::SIMPLE`::
96
+ `AttrMasker::Maskers::Simple`::
98
97
  +
99
98
  Simply replaces any value with the `"(redacted)"`. Only useful for columns
100
99
  containing textual data.
@@ -106,7 +105,7 @@ Example:
106
105
  [source,ruby]
107
106
  ----
108
107
  attr_masker :first_name
109
- attr_masker :last_name, :masker => AttrMasker::Maskers::SIMPLE
108
+ attr_masker :last_name, :masker => AttrMasker::Maskers::Simple.new
110
109
  ----
111
110
  +
112
111
  Would set both `first_name` and `last_name` attributes to `"(redacted)"`.
@@ -120,7 +119,7 @@ Can be initialized with options.
120
119
  |===============================================================================
121
120
  |Name|Default|Description
122
121
  |`replacement`|`"*"`|Replacement string, can be empty.
123
- |`alphanum_only`|`false`|When true, only alphanumeric charaters are replaced.
122
+ |`alphanum_only`|`false`|When true, only alphanumeric characters are replaced.
124
123
  |===============================================================================
125
124
  +
126
125
  Example:
@@ -133,7 +132,59 @@ attr_masker :phone, :masker => rm
133
132
  +
134
133
  Would mask "123-456-7890" as "XXX-XXX-XXXX".
135
134
 
135
+ === Using custom maskers
136
+
137
+ Apart from built-in maskers, any object which responds to `#call` can be used,
138
+ e.g. some lambda or `Method` instance. For instance, you may want to produce
139
+ unique values basing on other attributes, to mask selectively, or to use
140
+ tool like https://github.com/skalee/well_read_faker[Well Read Faker] to
141
+ generate random replacement values:
142
+
143
+ [source,ruby]
144
+ ----
145
+ require "well_read_faker"
146
+
147
+ attr_masker :email, masker: ->(model:, **) { "user#{model.id}@example.com" }
148
+ attr_masker :phone, masker: ->(value:, **) { "******" + value[-3..-1] }
149
+ attr_masker :bio, masker: ->(**) { WellReadFaker.paragraph }
150
+ ----
151
+
152
+ Masker is called with following keyword arguments:
153
+
154
+ `value`:: Original value of the field which is about to be masked
155
+
156
+ `model`:: Model instance
157
+
158
+ `attribute_name`:: Name of the attribute which is about to be masked
159
+
160
+ `masking_options`:: Hash of options which were passed in `#attr_masker` call
161
+
162
+ This list is likely to be extended in future versions, and that will not be
163
+ considered a breaking change, therefore it is strongly recommended to always
164
+ use a splat (`**`) at end of argument list of masker's `#call` method.
165
+
166
+ === Configuration file
167
+
168
+ It is also possible to contain all the maskers configuration in one file.
169
+ Just place it in `config/attr_masker.rb`, and it will be loaded from a Rake
170
+ task after the application is fully loaded. That means you can re-open classes
171
+ and add masker definitions there, for example:
172
+
173
+ [source,ruby]
174
+ ----
175
+ # config/attr_masker.rb
176
+
177
+ class User
178
+ attr_masker :first_name, :last_name
179
+ end
180
+
181
+ class Email
182
+ attr_masker :address, ->(model:, **) { "mail#{model.id}@example.com" }
183
+ end
184
+ ----
185
+
136
186
  == Roadmap & TODOs
187
+
137
188
  - documentation
138
189
  - spec tests
139
190
  - Make the `Rails.env` (in which `db:mask` could be run) configurable
data/Rakefile CHANGED
@@ -1,32 +1,5 @@
1
1
  # (c) 2017 Ribose Inc.
2
2
  #
3
3
 
4
- # require 'rake'
5
- # require 'rake/testtask'
6
- # require 'rake/rdoctask'
7
-
8
4
  require "bundler/gem_tasks"
9
5
  load "tasks/db.rake"
10
-
11
- # desc 'Generate documentation for the attr_masker gem.'
12
- # Rake::RDocTask.new(:rdoc) do |rdoc|
13
- # rdoc.rdoc_dir = 'rdoc'
14
- # rdoc.title = 'attr_masker'
15
- # rdoc.options << '--line-numbers' << '--inline-source'
16
- # rdoc.rdoc_files.include('README*')
17
- # rdoc.rdoc_files.include('lib/**/*.rb')
18
- # end
19
- #
20
- # if RUBY_VERSION < '1.9.3'
21
- # require 'rcov/rcovtask'
22
- #
23
- # task :rcov do
24
- # system "rcov -o coverage/rcov --exclude '^(?!lib)' " + FileList[ 'test/**/*_test.rb' ].join(' ')
25
- # end
26
- #
27
- # desc 'Default: run unit tests under rcov.'
28
- # task :default => :rcov
29
- # else
30
- # desc 'Default: run unit tests.'
31
- # task :default => :test
32
- # end
data/attr_masker.gemspec CHANGED
@@ -1,13 +1,13 @@
1
1
  # (c) 2017 Ribose Inc.
2
2
  #
3
3
 
4
- lib = File.expand_path("../lib", __FILE__)
4
+ lib = File.expand_path("lib", __dir__)
5
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
6
  require "attr_masker/version"
7
7
 
8
8
  Gem::Specification.new do |gem|
9
9
  gem.name = "attr_masker"
10
- gem.version = AttrMasker::Version.string
10
+ gem.version = AttrMasker::VERSION
11
11
  gem.authors = ["Ribose Inc."]
12
12
  gem.email = ["open.source@ribose.com"]
13
13
  gem.homepage = "https://github.com/riboseinc/attr_masker"
@@ -17,18 +17,23 @@ Gem::Specification.new do |gem|
17
17
  "of certain models by modifying the database."
18
18
 
19
19
  gem.files = `git ls-files`.split($/)
20
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
21
20
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
22
21
  gem.require_paths = ["lib"]
23
22
 
24
- gem.add_runtime_dependency("rails", ">= 4.0.0", "< 6.0")
23
+ gem.add_runtime_dependency("rails", ">= 4.0.0", "< 7")
25
24
  gem.add_runtime_dependency("ruby-progressbar", "~> 1.8")
26
25
 
27
- gem.add_development_dependency("bundler", "~> 1.15")
28
- gem.add_development_dependency("combustion", "~> 0.7.0")
29
- gem.add_development_dependency("database_cleaner", "~> 1.6")
26
+ gem.add_development_dependency("bundler", ">= 1.15")
27
+ gem.add_development_dependency("combustion", "~> 1.0")
28
+ gem.add_development_dependency("database_cleaner", "~> 1.8")
29
+ gem.add_development_dependency("database_cleaner-active_record", "~> 1.8")
30
+ gem.add_development_dependency("database_cleaner-mongoid", "~> 1.8")
31
+ # Older versions aren't needed as we don't support Rails < 4
32
+ gem.add_development_dependency("mongoid", ">= 5")
30
33
  gem.add_development_dependency("pry")
31
- gem.add_development_dependency("rspec", ">= 3.0")
32
- gem.add_development_dependency("rubocop", "~> 0.49.1")
33
- gem.add_development_dependency("sqlite3", "~> 1.3.13")
34
+ gem.add_development_dependency("rspec", "~> 3.0")
35
+ gem.add_development_dependency("rubocop", "~> 0.54.0")
36
+ gem.add_development_dependency("simplecov")
37
+ gem.add_development_dependency("sqlite3", ">= 1.3.13", "< 2")
38
+ gem.add_development_dependency("warning", "~> 1.1")
34
39
  end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rack/cleanser"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/rake ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ # rubocop:disable all
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake") if File.exists?(Gem.bin_path("rake", "rake"))
data/bin/rspec ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ # rubocop:disable all
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/rubocop ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ # rubocop:disable all
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rubocop", "rubocop")
data/bin/setup ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ # rubocop:disable all
3
+ set -euo pipefail
4
+ IFS=$'\n\t'
5
+ set -vx
6
+
7
+ bundle install
8
+
9
+ # Do any other automated setup that you need to do here
@@ -1,5 +1,4 @@
1
- source "https://rubygems.org"
1
+ eval_gemfile "common.gemfile"
2
2
 
3
3
  gem "activerecord", "~> 4.2.0"
4
-
5
- gemspec path: ".."
4
+ gem "sqlite3", "~> 1.3.13"
@@ -1,5 +1,4 @@
1
- source "https://rubygems.org"
1
+ eval_gemfile "common.gemfile"
2
2
 
3
3
  gem "activerecord", "~> 5.0.0"
4
-
5
- gemspec path: ".."
4
+ gem "sqlite3", "~> 1.3.13"
@@ -1,5 +1,4 @@
1
- source "https://rubygems.org"
1
+ eval_gemfile "common.gemfile"
2
2
 
3
3
  gem "activerecord", "~> 5.1.0"
4
-
5
- gemspec path: ".."
4
+ gem "sqlite3", "~> 1.3.13"
@@ -0,0 +1,4 @@
1
+ eval_gemfile "common.gemfile"
2
+
3
+ gem "activerecord", "~> 5.2.0"
4
+ gem "sqlite3", "~> 1.3.13"
@@ -0,0 +1,3 @@
1
+ eval_gemfile "common.gemfile"
2
+
3
+ gem "activerecord", "~> 6.0.0"
@@ -0,0 +1,3 @@
1
+ eval_gemfile "common.gemfile"
2
+
3
+ gem "activerecord", "~> 6.1.0"
@@ -1,6 +1,4 @@
1
- source "https://rubygems.org"
1
+ eval_gemfile "common.gemfile"
2
2
 
3
3
  gem "activerecord", github: "rails/rails"
4
4
  gem "arel", github: "rails/arel"
5
-
6
- gemspec path: ".."