masking 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +14 -0
- data/.gitignore +16 -0
- data/.mdlrc +1 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +119 -0
- data/LICENSE.txt +21 -0
- data/README.md +166 -0
- data/Rakefile +11 -0
- data/bin/console +11 -0
- data/bin/masking_profile +58 -0
- data/bin/setup +10 -0
- data/config/.keep +0 -0
- data/exe/masking +7 -0
- data/lib/masking.rb +31 -0
- data/lib/masking/cli.rb +42 -0
- data/lib/masking/cli/error_message.rb +36 -0
- data/lib/masking/cli/error_messages.yml +6 -0
- data/lib/masking/config.rb +33 -0
- data/lib/masking/config/target_columns.rb +52 -0
- data/lib/masking/config/target_columns/column.rb +32 -0
- data/lib/masking/config/target_columns/method.rb +41 -0
- data/lib/masking/config/target_columns/method/binary.rb +23 -0
- data/lib/masking/config/target_columns/method/boolean.rb +29 -0
- data/lib/masking/config/target_columns/method/date.rb +30 -0
- data/lib/masking/config/target_columns/method/float.rb +23 -0
- data/lib/masking/config/target_columns/method/integer.rb +23 -0
- data/lib/masking/config/target_columns/method/null.rb +17 -0
- data/lib/masking/config/target_columns/method/string.rb +33 -0
- data/lib/masking/config/target_columns/method/string_binary_distinctor.rb +31 -0
- data/lib/masking/config/target_columns/method/time.rb +28 -0
- data/lib/masking/config/target_columns/table.rb +24 -0
- data/lib/masking/data_mask_processor.rb +44 -0
- data/lib/masking/errors.rb +9 -0
- data/lib/masking/insert_statement.rb +74 -0
- data/lib/masking/insert_statement/sql_builder.rb +34 -0
- data/lib/masking/insert_statement/value.rb +30 -0
- data/lib/masking/sql_dump_line.rb +24 -0
- data/lib/masking/version.rb +5 -0
- data/masking.gemspec +46 -0
- data/masking.yml.sample +17 -0
- metadata +259 -0
data/Rakefile
ADDED
@@ -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]
|
data/bin/console
ADDED
@@ -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
|
data/bin/masking_profile
ADDED
@@ -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
|
data/bin/setup
ADDED
data/config/.keep
ADDED
File without changes
|
data/exe/masking
ADDED
data/lib/masking.rb
ADDED
@@ -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
|
data/lib/masking/cli.rb
ADDED
@@ -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
|