db_obfuscation 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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