db_obfuscation 0.0.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 (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/Gemfile +13 -0
  4. data/Gemfile.lock +83 -0
  5. data/LICENSE +21 -0
  6. data/README.md +121 -0
  7. data/TODO +15 -0
  8. data/bin/console +25 -0
  9. data/bin/db_obfuscation +121 -0
  10. data/bin/obfuscation_test +54 -0
  11. data/cli/db_dump.rb +29 -0
  12. data/cli/migrator.rb +26 -0
  13. data/cli/seeder.rb +52 -0
  14. data/db_obfuscation.gemspec +25 -0
  15. data/features/bin/dump.feature +21 -0
  16. data/features/bin/obfuscation.feature +12 -0
  17. data/features/bin/test_database_tasks.feature +16 -0
  18. data/features/support.rb +1 -0
  19. data/lib/db_obfuscation.rb +50 -0
  20. data/lib/db_obfuscation/batch_formulator.rb +26 -0
  21. data/lib/db_obfuscation/config.rb +43 -0
  22. data/lib/db_obfuscation/database.rb +8 -0
  23. data/lib/db_obfuscation/environment.rb +14 -0
  24. data/lib/db_obfuscation/filtering.rb +56 -0
  25. data/lib/db_obfuscation/filtering/column.rb +40 -0
  26. data/lib/db_obfuscation/filtering/truncation.rb +18 -0
  27. data/lib/db_obfuscation/obfuscation_strategy.rb +22 -0
  28. data/lib/db_obfuscation/obfuscator.rb +65 -0
  29. data/lib/db_obfuscation/query_builder.rb +62 -0
  30. data/lib/db_obfuscation/truncation.rb +39 -0
  31. data/lib/db_obfuscation/util/trigger.rb +83 -0
  32. data/lib/db_obfuscation/version.rb +4 -0
  33. data/spec/cli/db_dump_spec.rb +33 -0
  34. data/spec/cli/migrator_spec.rb +59 -0
  35. data/spec/cli/seeder_spec.rb +33 -0
  36. data/spec/config/database.yml +5 -0
  37. data/spec/config/table_strategies/table_1.yml +3 -0
  38. data/spec/config/table_strategies/table_2.yml +4 -0
  39. data/spec/config/table_strategies/truncation_table_1.yml +3 -0
  40. data/spec/config/table_strategies/whitelisted_table_1.yml +3 -0
  41. data/spec/config/truncation_patterns.yml +2 -0
  42. data/spec/config/whitelisted_tables.yml +1 -0
  43. data/spec/db_obfuscation/batch_formulator_spec.rb +36 -0
  44. data/spec/db_obfuscation/config_spec.rb +60 -0
  45. data/spec/db_obfuscation/database_spec.rb +10 -0
  46. data/spec/db_obfuscation/filtering/column_spec.rb +82 -0
  47. data/spec/db_obfuscation/filtering/truncation_spec.rb +41 -0
  48. data/spec/db_obfuscation/filtering_spec.rb +39 -0
  49. data/spec/db_obfuscation/obfuscation_strategy_spec.rb +43 -0
  50. data/spec/db_obfuscation/obfuscator_spec.rb +150 -0
  51. data/spec/db_obfuscation/query_builder_spec.rb +259 -0
  52. data/spec/db_obfuscation/truncation_spec.rb +31 -0
  53. data/spec/db_obfuscation/util/trigger_spec.rb +126 -0
  54. data/spec/integration/obfuscation_spec.rb +69 -0
  55. data/spec/spec_helper.rb +3 -0
  56. data/spec/test_db_setup/migrations/1_add_table_1.rb +18 -0
  57. data/spec/test_db_setup/migrations/2_add_table_2.rb +19 -0
  58. data/spec/test_db_setup/migrations/3_add_truncation_table_1.rb +14 -0
  59. data/spec/test_db_setup/migrations/4_add_whitelisted_table_1.rb +14 -0
  60. data/spec/test_db_setup/migrations/5_add_table_without_any_user_defined_obfuscation_strategies.rb +18 -0
  61. data/spec/test_db_setup/migrations/6_add_table_without_any_obfuscatable_columns.rb +15 -0
  62. data/spec/test_db_setup/migrations/7_add_audit_truncation_table.rb +13 -0
  63. data/spec/test_db_setup/seeds/audit_truncation_table.yml +7 -0
  64. data/spec/test_db_setup/seeds/table_1.yml +13 -0
  65. data/spec/test_db_setup/seeds/table_2.yml +15 -0
  66. data/spec/test_db_setup/seeds/table_without_any_obfuscatable_columns.yml +7 -0
  67. data/spec/test_db_setup/seeds/table_without_any_user_defined_obfuscation_strategies.yml +13 -0
  68. data/spec/test_db_setup/seeds/truncation_table_1.yml +9 -0
  69. data/spec/test_db_setup/seeds/whitelisted_table_1.yml +9 -0
  70. metadata +159 -0
@@ -0,0 +1,22 @@
1
+ require 'db_obfuscation/database'
2
+
3
+ module DbObfuscation
4
+ module ObfuscationStrategy
5
+ def self.strategy(column_name)
6
+ case column_name.to_sym
7
+ when /ssn/
8
+ :ssn_strategy
9
+ when /email/
10
+ :email_strategy
11
+ when :first_name
12
+ :first_name_strategy
13
+ when :last_name
14
+ :last_name_strategy
15
+ when :name
16
+ :name_strategy
17
+ else
18
+ :default_strategy
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,65 @@
1
+ require 'ffaker'
2
+ module DbObfuscation
3
+ module Obfuscator
4
+ extend self
5
+
6
+ def obfuscate(strategy_name)
7
+ case strategy_name
8
+ when :address_strategy
9
+ address
10
+ when :date_strategy
11
+ rand(31..240)
12
+ when :default_strategy
13
+ FFaker::Lorem.word
14
+ when :driving_license_strategy
15
+ FFaker::Identification.drivers_license
16
+ when :email_strategy
17
+ FFaker::Internet.safe_email
18
+ when :first_name_strategy
19
+ FFaker::Name.first_name
20
+ when :gender_strategy
21
+ 'Unknown'
22
+ when :last_name_strategy
23
+ FFaker::Name.last_name
24
+ when :medicaid_id_strategy
25
+ FFaker.numerify('############')
26
+ when :name_strategy
27
+ FFaker::Name.name
28
+ when :nil_strategy
29
+ nil
30
+ when :paragraph_strategy
31
+ FFaker::Lorem.paragraph
32
+ when :phone_number_strategy
33
+ FFaker::PhoneNumber.phone_number
34
+ when :school_strategy
35
+ FFaker::Education.school
36
+ when :sentence_strategy
37
+ FFaker::Lorem.sentence
38
+ when :ssn_strategy
39
+ FFaker::SSN.ssn
40
+ when :suffix_strategy
41
+ FFaker::Name.suffix
42
+ when :unique_name_strategy
43
+ full_name
44
+ end
45
+ end
46
+
47
+ private
48
+ def address
49
+ <<-ADDRESS.gsub(/\s{2,}/,' ').strip
50
+ #{FFaker::Address.street_address}
51
+ #{FFaker::Address.city}
52
+ #{FFaker::AddressUS.state_abbr}
53
+ #{FFaker::AddressUS.zip_code}
54
+ ADDRESS
55
+ end
56
+
57
+ def full_name
58
+ <<-name.gsub(/\s{2,}/,' ').strip
59
+ #{FFaker::Name.first_name}
60
+ #{FFaker.letterify('??????????')}
61
+ #{FFaker::Name.last_name}
62
+ name
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,62 @@
1
+ module DbObfuscation
2
+ module QueryBuilder
3
+ class << self
4
+ def multi_update_sql(table, columns, date_columns=[])
5
+ <<-SQL.gsub(/\s{2,}/,' ').strip
6
+ UPDATE "#{table}"
7
+ #{case_sql(columns, date_columns)}
8
+ #{where_sql(columns)}
9
+ SQL
10
+ end
11
+
12
+ def where_sql(columns)
13
+ column_name = columns.first.keys.last
14
+ values = columns.map { |column| "'#{column.values.last}'" }.join(',')
15
+ %Q{WHERE "#{column_name}" IN (#{values})}
16
+ end
17
+
18
+ def case_sql(columns, date_columns)
19
+ column_names = columns.first.keys
20
+ constraint_name = column_names.last
21
+ column_names -= [constraint_name]
22
+ column_names.each_with_object('SET ') do |name, sql_query|
23
+ sql_query << %Q("#{name}" = CASE)
24
+ columns.each_with_object(sql_query) do |column, query|
25
+ type = (date_columns.include?(name) ? :date : :string)
26
+ query << when_sql(name, column, constraint_name, type)
27
+ end
28
+ sql_query << %Q( ELSE "#{name}" END, )
29
+ end.chomp(", ")
30
+ end
31
+
32
+ def update_sql(table, id, columns, date_columns=[])
33
+ "UPDATE #{table} #{column_sql(columns, date_columns)} WHERE id=#{id};"
34
+ end
35
+
36
+ def column_sql(columns, date_columns)
37
+ columns.each_with_object("SET ") do |column, query|
38
+ column_name, column_value = column.first, column.last
39
+ if date_columns.include?(column.first)
40
+ query << "#{column_name}=#{column_name} + INTERVAL '#{column_value} DAYS', "
41
+ else
42
+ query << "#{column_name}=#{column_value}, "
43
+ end
44
+ end.chomp(", ")
45
+ end
46
+
47
+ private
48
+ def when_sql(column, table_row, constraint, type)
49
+ when_clause = <<-SQL.gsub(/\s{2,}/,' ').chomp
50
+ WHEN "#{constraint}" = '#{table_row[constraint]}'
51
+ AND "#{column}" IS NOT NULL
52
+ SQL
53
+ if type == :date
54
+ when_clause << %Q( THEN "#{column}" + INTERVAL '#{table_row[column]} DAYS')
55
+ else
56
+ when_clause << %Q( AND "#{column}" != '' THEN #{table_row[column]})
57
+ end
58
+ when_clause
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,39 @@
1
+ require 'db_obfuscation/database'
2
+ require 'db_obfuscation/filtering/truncation'
3
+
4
+ module DbObfuscation
5
+ class Truncation
6
+
7
+
8
+ def self.truncate
9
+ new.send(:truncate)
10
+ end
11
+
12
+ def self.tables
13
+ new.send(:tables)
14
+ end
15
+
16
+ private
17
+
18
+ def truncate
19
+ tables.each do |table|
20
+ begin
21
+ DbObfuscation.logging.info "Truncating #{table}"
22
+ DB[table].truncate
23
+ rescue => e
24
+ DbObfuscation.logging.error "#{table} encountered #{e.message}"
25
+ DbObfuscation.logging.error "#{e.backtrace}"
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ def tables
32
+ Filtering::Truncation.matches_patterns(DB.tables, patterns)
33
+ end
34
+
35
+ def patterns
36
+ Config.truncation_patterns
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,83 @@
1
+ require 'db_obfuscation/database'
2
+
3
+ module DbObfuscation
4
+ module Util
5
+ module Trigger
6
+ extend self
7
+ def tables
8
+ query = <<-sql
9
+ SELECT tables.relname as table_name
10
+ FROM pg_trigger triggers, pg_class tables
11
+ WHERE triggers.tgrelid = tables.oid
12
+ AND tables.relname !~ '^pg_'
13
+ AND triggers.tgname LIKE '#{prefix}%'
14
+ sql
15
+ DbObfuscation::DB[query].map(:table_name)
16
+ end
17
+
18
+ def drop(table)
19
+ table == :all ? drop_triggers : drop_trigger(table)
20
+ end
21
+
22
+ def disable(table)
23
+ table == :all ? disable_triggers : disable_trigger(table)
24
+ end
25
+
26
+ def enable(table)
27
+ table == :all ? enable_triggers : enable_trigger(table)
28
+ end
29
+
30
+ def exists?(table)
31
+ tables.include?(table.to_s)
32
+ end
33
+
34
+ private
35
+ def disable_triggers
36
+ tables.each do |table|
37
+ disable_trigger(table)
38
+ end
39
+ end
40
+
41
+ def disable_trigger(table)
42
+ DbObfuscation::DB.run <<-sql
43
+ ALTER TABLE #{table}
44
+ DISABLE TRIGGER #{name(table)}
45
+ sql
46
+ end
47
+
48
+ def enable_triggers
49
+ tables.each do |table|
50
+ enable_trigger(table)
51
+ end
52
+ end
53
+
54
+ def enable_trigger(table)
55
+ DbObfuscation::DB.run <<-sql
56
+ ALTER TABLE #{table}
57
+ ENABLE TRIGGER #{name(table)}
58
+ sql
59
+ end
60
+
61
+ def drop_triggers
62
+ tables.each do |table|
63
+ drop_trigger(table)
64
+ end
65
+ end
66
+
67
+ def drop_trigger(table)
68
+ DbObfuscation::DB.run <<-sql
69
+ DROP TRIGGER #{name(table)}
70
+ ON #{table}
71
+ sql
72
+ end
73
+
74
+ def prefix
75
+ 'audit_'
76
+ end
77
+
78
+ def name(table)
79
+ "#{prefix}#{table}"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,4 @@
1
+ module DbObfuscation
2
+ VERSION = "0.0.1"
3
+ end
4
+
@@ -0,0 +1,33 @@
1
+ require 'db_obfuscation/environment'
2
+ require 'db_dump'
3
+
4
+ module DbObfuscation
5
+ module Cli
6
+ describe DbDump do
7
+
8
+ it 'dump a db into a dump file' do
9
+ filename = 'db.yml'
10
+ db_config = {
11
+ 'username' => 'fake_username',
12
+ 'password' => 'fake_password',
13
+ 'database' => 'fake_database',
14
+ 'host' => 'fake_host'
15
+ }
16
+
17
+ allow(YAML).to receive(:load_file).with(filename).
18
+ and_return(db_config)
19
+
20
+ dump_name = 'dumped_database'
21
+
22
+ dump_cmd = <<-dump_cmd.gsub(/\s{2,}/,' ').strip
23
+ PGPASSWORD=fake_password pg_dump -h fake_host -U fake_username -w
24
+ -Fc -f dumped_database fake_database
25
+ dump_cmd
26
+
27
+ expect(Kernel).to receive(:system).with(dump_cmd)
28
+ described_class.dump(filename, dump_name)
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+ require 'migrator'
3
+ require 'db_obfuscation/database'
4
+
5
+ module DbObfuscation
6
+ module Cli
7
+ describe Migrator do
8
+ describe '.migrate' do
9
+ before do
10
+ system("dropdb -e obfuscation_test")
11
+ system("createdb -e obfuscation_test")
12
+ end
13
+
14
+ it 'creates table in the test database' do
15
+ expect(DbObfuscation::DB.tables).to eq []
16
+
17
+ db_yml = File.expand_path('../../config/database.yml',
18
+ __FILE__)
19
+ migration_path = File.expand_path('../../test_db_setup/migrations',
20
+ __FILE__)
21
+
22
+ described_class.migrate(db_yml, migration_path)
23
+
24
+ expect(DbObfuscation::DB.tables).to include :table_1
25
+ expect(DbObfuscation::DB.tables).to include :table_2
26
+ expect(DbObfuscation::DB.tables).to include :truncation_table_1
27
+ expect(DbObfuscation::DB.tables).to include :whitelisted_table_1
28
+ end
29
+ end
30
+
31
+ describe '.down_migrate' do
32
+ before do
33
+ system("dropdb -e obfuscation_test")
34
+ system("createdb -e obfuscation_test")
35
+ end
36
+
37
+ it 'creates table in the test database' do
38
+ db_yml = File.expand_path('../../config/database.yml',
39
+ __FILE__)
40
+ migration_path = File.expand_path('../../test_db_setup/migrations',
41
+ __FILE__)
42
+
43
+ described_class.migrate(db_yml, migration_path)
44
+ expect(DbObfuscation::DB.tables).to include :table_1
45
+ expect(DbObfuscation::DB.tables).to include :table_2
46
+ expect(DbObfuscation::DB.tables).to include :truncation_table_1
47
+ expect(DbObfuscation::DB.tables).to include :whitelisted_table_1
48
+
49
+ described_class.down_migrate(db_yml, migration_path)
50
+
51
+ expect(DbObfuscation::DB.tables).to eq([:schema_info])
52
+
53
+ #rescue operation to keep the test db in a clean state
54
+ described_class.migrate(db_yml, migration_path)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'seeder'
3
+ require 'db_obfuscation/database'
4
+ require 'migrator'
5
+
6
+ module DbObfuscation
7
+ module Cli
8
+ describe Seeder do
9
+ let(:db_yml) {
10
+ File.expand_path('../../config/database.yml', __FILE__)
11
+ }
12
+ let(:migrations_path) {
13
+ File.expand_path('../../test_db_setup/migrations', __FILE__)
14
+ }
15
+ let(:seeds_path) {
16
+ File.expand_path('../../test_db_setup/seeds', __FILE__)
17
+ }
18
+
19
+ describe '.seed' do
20
+ before do
21
+ Migrator.down_migrate(db_yml, migrations_path)
22
+ Migrator.migrate(db_yml, migrations_path)
23
+ end
24
+
25
+ it 'seeds the database with seed data' do
26
+ expect(DbObfuscation::DB[:table_1].all).to eq []
27
+ Seeder.seed(db_yml, seeds_path)
28
+ expect(DbObfuscation::DB[:table_1].all).to_not eq([])
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ adapter: postgres
2
+ host: localhost
3
+ encoding: unicode
4
+ username: ccUser
5
+ database: obfuscation_test
@@ -0,0 +1,3 @@
1
+ table_1:
2
+ field_1: :first_name_strategy
3
+ date_field: :date_strategy
@@ -0,0 +1,4 @@
1
+ table_2:
2
+ field_1: :default_strategy
3
+ field_2: :whitelisted
4
+ date_field: :date_strategy
@@ -0,0 +1,3 @@
1
+ truncation_table_1:
2
+ field_1: :default_strategy
3
+ field_2: :default_strategy
@@ -0,0 +1,3 @@
1
+ whitelisted_table_1:
2
+ field_1: :default_strategy
3
+ field_2: :default_strategy
@@ -0,0 +1,2 @@
1
+ - truncation_table_1
2
+ - audit
@@ -0,0 +1 @@
1
+ - whitelisted_table_1
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'db_obfuscation/batch_formulator'
3
+
4
+ module DbObfuscation
5
+ describe BatchFormulator do
6
+
7
+ describe '.batch_for' do
8
+ it 'returns a batch of obfuscated row values for a set of ids' do
9
+ config = {
10
+ field_1: :default_strategy,
11
+ field_2: :default_strategy,
12
+ date_field: :default_strategy
13
+ }
14
+
15
+ ids = [1,2]
16
+
17
+ expected_batch = [
18
+ { field_1: "'obfuscated_value'",
19
+ field_2: "'obfuscated_value'",
20
+ date_field: "'obfuscated_value'",
21
+ id: 1 },
22
+ { field_1: "'obfuscated_value'",
23
+ field_2: "'obfuscated_value'",
24
+ date_field: "'obfuscated_value'",
25
+ id: 2 }
26
+ ]
27
+
28
+ allow(DbObfuscation::Obfuscator).to receive(:obfuscate)
29
+ .and_return('obfuscated_value')
30
+ obfuscated_batch = described_class.batch_for(config, ids)
31
+
32
+ expect(obfuscated_batch).to eq expected_batch
33
+ end
34
+ end
35
+ end
36
+ end