masking 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +14 -0
  3. data/.gitignore +16 -0
  4. data/.mdlrc +1 -0
  5. data/.rubocop.yml +18 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +9 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +8 -0
  10. data/Gemfile.lock +119 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +166 -0
  13. data/Rakefile +11 -0
  14. data/bin/console +11 -0
  15. data/bin/masking_profile +58 -0
  16. data/bin/setup +10 -0
  17. data/config/.keep +0 -0
  18. data/exe/masking +7 -0
  19. data/lib/masking.rb +31 -0
  20. data/lib/masking/cli.rb +42 -0
  21. data/lib/masking/cli/error_message.rb +36 -0
  22. data/lib/masking/cli/error_messages.yml +6 -0
  23. data/lib/masking/config.rb +33 -0
  24. data/lib/masking/config/target_columns.rb +52 -0
  25. data/lib/masking/config/target_columns/column.rb +32 -0
  26. data/lib/masking/config/target_columns/method.rb +41 -0
  27. data/lib/masking/config/target_columns/method/binary.rb +23 -0
  28. data/lib/masking/config/target_columns/method/boolean.rb +29 -0
  29. data/lib/masking/config/target_columns/method/date.rb +30 -0
  30. data/lib/masking/config/target_columns/method/float.rb +23 -0
  31. data/lib/masking/config/target_columns/method/integer.rb +23 -0
  32. data/lib/masking/config/target_columns/method/null.rb +17 -0
  33. data/lib/masking/config/target_columns/method/string.rb +33 -0
  34. data/lib/masking/config/target_columns/method/string_binary_distinctor.rb +31 -0
  35. data/lib/masking/config/target_columns/method/time.rb +28 -0
  36. data/lib/masking/config/target_columns/table.rb +24 -0
  37. data/lib/masking/data_mask_processor.rb +44 -0
  38. data/lib/masking/errors.rb +9 -0
  39. data/lib/masking/insert_statement.rb +74 -0
  40. data/lib/masking/insert_statement/sql_builder.rb +34 -0
  41. data/lib/masking/insert_statement/value.rb +30 -0
  42. data/lib/masking/sql_dump_line.rb +24 -0
  43. data/lib/masking/version.rb +5 -0
  44. data/masking.gemspec +46 -0
  45. data/masking.yml.sample +17 -0
  46. metadata +259 -0
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+ require 'rake/notes/rake_task'
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+ RuboCop::RakeTask.new
10
+
11
+ task default: %i[spec rubocop notes]
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'masking'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require 'pry'
11
+ Pry.start
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'masking'
6
+ require 'masking/cli'
7
+
8
+ require 'ruby-prof'
9
+
10
+ original_stdout = $stdout.clone
11
+ $stdout.reopen(File.new(File::NULL, 'w'))
12
+
13
+ result = RubyProf.profile do
14
+ Masking::Cli.new(ARGV).run
15
+ end
16
+
17
+ $stdout.reopen(original_stdout)
18
+
19
+ flat_result_file = Pathname(__dir__).join('../profile/flat.txt')
20
+ RubyProf::FlatPrinterWithLineNumbers.new(result).tap do |flat_printer|
21
+ flat_printer.print(File.new(flat_result_file, 'w'))
22
+ puts "flat result is saved at #{flat_result_file}"
23
+ end
24
+
25
+ # it can produce big file. just comment out if you don't want to use it
26
+ graph_file = Pathname(__dir__).join('../profile/graph.txt')
27
+ RubyProf::GraphPrinter.new(result).tap do |graph_html_printer|
28
+ graph_html_printer.print(File.new(graph_file, 'w'))
29
+ puts "graph result is saved at #{graph_file}"
30
+ end
31
+
32
+ # it can produce huge html file. just comment out if you don't want to use it
33
+ graph_html_file = Pathname(__dir__).join('../profile/graph.html')
34
+ RubyProf::GraphHtmlPrinter.new(result).tap do |graph_html_printer|
35
+ graph_html_printer.print(File.new(graph_html_file, 'w'))
36
+ puts "graph html is saved at #{graph_html_file}"
37
+ end
38
+
39
+ # # this is quite slow so commented out. but it's useful
40
+ # call_stack_html_file = Pathname(__dir__).join('../profile/call_stack.html')
41
+ # RubyProf::CallStackPrinter.new(result).tap do |call_stack_html_printer|
42
+ # call_stack_html_printer.print(File.new(call_stack_html_file, 'w'))
43
+ # puts "call stack html is saved at #{call_stack_html_file}"
44
+ # end
45
+
46
+ # # I'm not sure how to read this
47
+ # call_graph_dot_file = Pathname(__dir__).join('../profile/call_graph.dot')
48
+ # RubyProf::DotPrinter.new(result).tap do |dot_printer|
49
+ # dot_printer.print(File.new(call_graph_dot_file, 'w'))
50
+ # puts "call graph dot file is saved at #{call_graph_dot_file}"
51
+ # end
52
+
53
+ # # I'm not sure how to read this
54
+ # call_tree_dir = Pathname(__dir__).join('../profile/call_tree')
55
+ # RubyProf::CallTreePrinter.new(result).tap do |call_tree_printer|
56
+ # call_tree_printer.print(path: File.dirname(call_tree_dir) , profile: File.basename(call_tree_dir))
57
+ # puts "call tree file is saved at #{call_tree_dir}"
58
+ # end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ if [[ ! -a masking.yml ]]; then
9
+ cp masking.yml.sample masking.yml
10
+ fi
File without changes
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'masking'
5
+ require 'masking/cli'
6
+
7
+ Masking::Cli.new(ARGV).run
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'masking/version'
4
+ require 'masking/cli'
5
+ require 'masking/config'
6
+ require 'masking/sql_dump_line'
7
+
8
+ module Masking
9
+ class << self
10
+ def run
11
+ Main.new.run
12
+ end
13
+ end
14
+
15
+ class Main
16
+ def initialize(input: $stdin, output: $stdout)
17
+ @input = input.set_encoding(Encoding::ASCII_8BIT, Encoding::ASCII_8BIT)
18
+ @output = output.set_encoding(Encoding::ASCII_8BIT, Encoding::ASCII_8BIT)
19
+ end
20
+
21
+ def run
22
+ input.each_line do |line|
23
+ output.print SQLDumpLine.new(line).output
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :input, :output
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'masking/config'
4
+ require 'masking/errors'
5
+ require 'masking/cli/error_message'
6
+ require 'optparse'
7
+
8
+ module Masking
9
+ class Cli
10
+ def initialize(argv)
11
+ @argv = argv
12
+ end
13
+
14
+ def run
15
+ option_parser.parse(argv)
16
+ Masking.run
17
+ rescue Masking::Error => error
18
+ warn(Masking::Cli::ErrorMessage.new(error).message(config_file_path: Masking.config.target_columns_file_path))
19
+ exit(false)
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :argv
25
+
26
+ def option_parser
27
+ OptionParser.new do |parser|
28
+ parser.banner = 'Usage: masking [options]'
29
+
30
+ define_config_option(parser)
31
+ end
32
+ end
33
+
34
+ def define_config_option(parser)
35
+ parser.on('-cFILE_PATH', '--config=FILE_PATH', 'specify config file. default: masking.yml') do |file_path|
36
+ Masking.configure do |config|
37
+ config.target_columns_file_path = file_path
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+ require 'ostruct'
5
+
6
+ module Masking
7
+ class Cli
8
+ class ErrorMessage
9
+ def initialize(error_class)
10
+ @error_class = error_class
11
+ end
12
+
13
+ def message(**keyword_args)
14
+ error_message(keyword_args)
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :error_class
20
+
21
+ YAML_FILE_PATH = Pathname('lib/masking/cli/error_messages.yml')
22
+
23
+ def error_messages
24
+ @error_messages = YAML.safe_load(YAML_FILE_PATH.read)
25
+ end
26
+
27
+ def error_message(keyword_args)
28
+ ERB.new(
29
+ error_messages.fetch(error_class.to_s)
30
+ ).result(
31
+ OpenStruct.new(keyword_args).instance_eval { binding }
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ Masking::Error::ConfigFileDoesNotExist:
2
+ "ERROR: config file (<%= config_file_path %>) does not exist"
3
+ Masking::Error::ConfigFileIsNotFile:
4
+ "ERROR: config file (<%= config_file_path %>) is not file"
5
+ Masking::Error::ConfigFileIsNotValidYaml:
6
+ "ERROR: config file (<%= config_file_path %>) is not valid yaml format"
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'masking/config/target_columns'
4
+
5
+ module Masking
6
+ class << self
7
+ def config
8
+ @config ||= Config.new
9
+ end
10
+
11
+ def configure
12
+ yield config
13
+ end
14
+ end
15
+
16
+ class Config
17
+ DEFAULT_TARGET_COLUMNS_YAML_PATH = Pathname('masking.yml')
18
+ attr_reader :target_columns_file_path
19
+
20
+ def initialize
21
+ @target_columns_file_path = DEFAULT_TARGET_COLUMNS_YAML_PATH
22
+ end
23
+
24
+ def target_columns_file_path=(val)
25
+ @target_columns_file_path = Pathname(val)
26
+ @target_columns = TargetColumns.new(target_columns_file_path)
27
+ end
28
+
29
+ def target_columns
30
+ @target_columns ||= TargetColumns.new(target_columns_file_path)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'masking/config/target_columns/table'
5
+ require 'masking/config/target_columns/column'
6
+ require 'masking/errors'
7
+
8
+ module Masking
9
+ class Config
10
+ # TODO: find better naming of TargetColumns
11
+ class TargetColumns
12
+ attr_reader :file_path
13
+
14
+ def initialize(file_path)
15
+ @file_path = file_path
16
+
17
+ raise Masking::Error::ConfigFileDoesNotExist unless file_path.exist?
18
+ raise Masking::Error::ConfigFileIsNotFile unless file_path.file?
19
+ end
20
+
21
+ def contains?(table_name:)
22
+ data.key?(table_name)
23
+ end
24
+
25
+ # TODO: refactoring
26
+ def columns(table_name:)
27
+ tables.find { |table| table.name == table_name.to_sym }&.columns
28
+ end
29
+
30
+ def ==(other)
31
+ file_path == other.file_path
32
+ end
33
+
34
+ private
35
+
36
+ def data
37
+ @data ||= YAML.safe_load(file_path.read, [Date, Time])
38
+ rescue Psych::SyntaxError
39
+ raise Masking::Error::ConfigFileIsNotValidYaml
40
+ end
41
+
42
+ # TODO: extract to other class
43
+ def tables
44
+ @tables ||= [].tap do |arr|
45
+ data.each do |table_name, columns|
46
+ arr << Table.new(table_name, columns: columns)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'masking/config/target_columns/method'
4
+
5
+ module Masking
6
+ class Config
7
+ class TargetColumns
8
+ class Column
9
+ attr_reader :name, :table_name, :method_value
10
+
11
+ def initialize(name, table_name:, method_value:)
12
+ @name = name.to_sym
13
+ @table_name = table_name.to_sym
14
+ @method_value = method_value
15
+ @method = Method.new(method_value)
16
+ end
17
+
18
+ def masked_value
19
+ method.call
20
+ end
21
+
22
+ def ==(other)
23
+ name == other.name && table_name == other.table_name && method_value == other.method_value
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :method
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[Pathname(__FILE__).dirname.join('method/*.rb').to_s].each(&method(:require))
4
+ require 'forwardable'
5
+
6
+ module Masking
7
+ class Config
8
+ class TargetColumns
9
+ class Method
10
+ extend Forwardable
11
+
12
+ def initialize(method)
13
+ @method_type = mapping(method.class).new(method)
14
+ end
15
+
16
+ def_delegator :@method_type, :call
17
+
18
+ private
19
+
20
+ # rubocop:disable Layout/AlignHash
21
+ MAPPING = {
22
+ ::String => StringBinaryDistinctor,
23
+ ::Integer => Integer,
24
+ ::Float => Float,
25
+ ::Date => Date,
26
+ ::Time => Time,
27
+ ::TrueClass => Boolean,
28
+ ::FalseClass => Boolean,
29
+ ::NilClass => Null
30
+ }.freeze
31
+ # rubocop:enable Layout/AlignHash
32
+
33
+ def mapping(klass)
34
+ MAPPING[klass] || raise(UnknownType)
35
+ end
36
+
37
+ class UnknownType < RuntimeError; end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Masking
4
+ class Config
5
+ class TargetColumns
6
+ class Method
7
+ class Binary
8
+ def initialize(value)
9
+ @binary = value
10
+ end
11
+
12
+ def call
13
+ "_binary '#{binary}'".b
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :binary
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Masking
4
+ class Config
5
+ class TargetColumns
6
+ class Method
7
+ class Boolean
8
+ def initialize(value)
9
+ @boolean = value
10
+ end
11
+
12
+ def call
13
+ boolean_format.to_s
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :boolean
19
+
20
+ # NOTE: 11.1.1 Numeric Type Overview, chapter BOOL, BOOLEAN
21
+ # https://dev.mysql.com/doc/refman/8.0/en/numeric-type-overview.html
22
+ def boolean_format
23
+ boolean ? 1 : 0
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end