tidus 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 14edabf3b34700e5d9ab484afd54af8348604833
4
+ data.tar.gz: 7df5e0603faff6661bebeb09ead1dd3442c1c8f2
5
+ SHA512:
6
+ metadata.gz: 02cde5036ec4facd91e9ac0b4dc1e3dc97394a25ffd745ea31d070475b33223be0d9268fd5fb40ecfd6d5fe3a8dc059cb6a0d5042660fe6434b7de2b43c26480
7
+ data.tar.gz: c5c883a7aeefd19482c566cdaf367956362f17e013856320b9db3066cba8c03b8ba9fed314169b8689a973218912ff04088ff9e0a2ce3075a5e1730067a88e55
@@ -0,0 +1,43 @@
1
+ namespace :db do
2
+ desc "Clears all the views which are currently existing"
3
+ task :clear_views do
4
+ Rails.application.eager_load! if defined?(Rails)
5
+ ActiveRecord::Base.descendants.each do |c|
6
+ next if c.table_name == "schema_migrations"
7
+ puts "Clearing view '#{c.view_name}' for table '#{c.table_name}'"
8
+
9
+ ActiveRecord::Base.connection.execute(
10
+ "DROP VIEW IF EXISTS #{c.view_name}"
11
+ )
12
+ end
13
+ end
14
+
15
+ desc "Generates all the views for the models"
16
+ task :generate_views do
17
+ Rails.application.eager_load! if defined?(Rails)
18
+ ActiveRecord::Base.descendants.each do |c|
19
+ next if c.table_name == "schema_migrations"
20
+
21
+ if ActiveRecord::Base.connection.table_exists? c.table_name
22
+ puts "Generating view '#{c.view_name}' for table '#{c.table_name}'"
23
+
24
+ ActiveRecord::Base.connection.execute(
25
+ "CREATE VIEW #{c.view_name} AS " +
26
+ "(SELECT #{c.view_columns.join(', ')} " +
27
+ "FROM #{c.table_name})"
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ Rake::Task["db:migrate"].enhance ["db:clear_views"]
35
+ Rake::Task["db:rollback"].enhance ["db:clear_views"]
36
+
37
+ Rake::Task["db:migrate"].enhance do
38
+ Rake::Task["db:generate_views"].invoke
39
+ end
40
+
41
+ Rake::Task["db:rollback"].enhance do
42
+ Rake::Task["db:generate_views"].invoke
43
+ end
@@ -0,0 +1,63 @@
1
+ module Tidus
2
+ module Anonymization
3
+ def view_postfix
4
+ @view_postfix || "anonymized"
5
+ end
6
+
7
+ def view_postfix=(val)
8
+ @view_postfix = val
9
+ end
10
+
11
+ def view_name
12
+ @view_name || "#{table_name}_#{view_postfix}"
13
+ end
14
+
15
+ def view_name=(val)
16
+ @view_name = val
17
+ end
18
+
19
+ def view_columns
20
+ @view_columns ||= {}
21
+ default_view_columns.merge(@view_columns)
22
+ .map{ |k,v| ["#{v} AS #{k}"] }
23
+ .flatten
24
+ end
25
+
26
+ def default_view_columns
27
+ defaults = {}
28
+ column_names.each do |column|
29
+ defaults[column.to_sym] = "#{table_name}.#{column}"
30
+ end
31
+ defaults
32
+ end
33
+
34
+ def anonymizes(*attributes)
35
+ @view_columns ||= {}
36
+
37
+ options = attributes.extract_options!.dup
38
+ columns = attributes - [options]
39
+
40
+ raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
41
+ raise ArgumentError, "You need to supply a strategy" if options[:strategy].blank?
42
+
43
+ columns.each do |column|
44
+ key = options[:strategy].to_s.camelize
45
+
46
+ begin
47
+ if key.include?('::')
48
+ klass = key.constantize
49
+ else
50
+ klass = const_get("ActiveRecordAnonymize::#{key}Anonymizer")
51
+ end
52
+ rescue NameError
53
+ raise ArgumentError, "Unknown anonymizer: '#{key}'"
54
+ end
55
+
56
+ @view_columns[column.to_sym] = klass.anonymize(table_name, column, options)
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+
63
+ ActiveRecord::Base.extend Tidus::Anonymization
@@ -0,0 +1,44 @@
1
+ module Tidus
2
+ class CondAnonymizer
3
+ def self.anonymize(table_name, column_name, options = {})
4
+ adapter = ActiveRecord::Base.connection.instance_values["config"][:adapter]
5
+ case adapter
6
+ when "postgresql"
7
+ name = "#{table_name}.#{column_name}"
8
+
9
+ type = options[:result_type] || "text"
10
+ default = options[:default].nil? ? "#{name}::#{type}" : "'#{options[:default]}'::#{type}"
11
+
12
+ if options[:conditions].blank?
13
+ raise "Missing option :conditions for CondAnonymizer on #{name}"
14
+ elsif options[:conditions].kind_of?(Array)
15
+ conditions = options[:conditions]
16
+ else
17
+ conditions = [options[:conditions]]
18
+ end
19
+
20
+ command = "CASE "
21
+
22
+ conditions.each do |cond|
23
+ raise ":column for condition must be set" if cond[:column].blank?
24
+ raise ":value for condition must be set" if cond[:value].nil?
25
+ raise ":result for condition must be set" if cond[:result].nil?
26
+ cond_column = cond[:column]
27
+ cond_value = cond[:value]
28
+ cond_type = cond[:type] || "text"
29
+ comparator = cond[:comparator] || "="
30
+ cond_result = cond[:result]
31
+
32
+ command += "WHEN ((#{table_name}.#{cond_column})::#{cond_type} #{comparator} " +
33
+ "'#{cond_value}'::#{cond_type}) THEN '#{cond_result}'::#{type} "
34
+ end
35
+
36
+ command += "ELSE #{default} END"
37
+
38
+ return command
39
+ else
40
+ raise "#{self.name} not implemented for #{adapter}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ module Tidus
2
+ class EmailAnonymizer
3
+ def self.anonymize(table_name, column_name, options = {})
4
+ adapter = ActiveRecord::Base.connection.instance_values["config"][:adapter]
5
+ case adapter
6
+ when "postgresql"
7
+ name = "#{table_name}.#{column_name}"
8
+ options[:length] ||= 15
9
+
10
+ return "CASE WHEN ((#{name})::text ~~ '%@%'::text) " +
11
+ "THEN (((\"left\"(md5((#{name})::text), #{options[:length]}) || '@'::text) " +
12
+ "|| split_part((#{name})::text, '@'::text, 2)))::character varying " +
13
+ "ELSE #{name} END"
14
+ else
15
+ raise "#{self.name} not implemented for #{adapter}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+
@@ -0,0 +1,13 @@
1
+ module Tidus
2
+ class NullAnonymizer
3
+ def self.anonymize(table_name, column_name, options = {})
4
+ adapter = ActiveRecord::Base.connection.instance_values["config"][:adapter]
5
+ case adapter
6
+ when "postgresql"
7
+ return "NULL::unknown"
8
+ else
9
+ raise "#{self.name} not implemented for #{adapter}"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ module Tidus
2
+ class OverlayAnonymizer
3
+ def self.anonymize(table_name, column_name, options = {})
4
+ adapter = ActiveRecord::Base.connection.instance_values["config"][:adapter]
5
+ case adapter
6
+ when "postgresql"
7
+ name = "#{table_name}.#{column_name}"
8
+
9
+ raise "Missing option :start for OverlayAnonymizer on #{name}" if options[:start].blank?
10
+ raise "Missing option :length for OverlayAnonymizer on #{name}" if options[:length].blank?
11
+
12
+ overlay_char = options[:char] || "X"
13
+ overlay = overlay_char * options[:length]
14
+ return "\"overlay\"((#{name})::text, " +
15
+ "'#{overlay}'::text, #{options[:start]})"
16
+ else
17
+ raise "#{self.name} not implemented for #{adapter}"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module Tidus
2
+ class StaticAnonymizer
3
+ def self.anonymize(table_name, column_name, options = {})
4
+ adapter = ActiveRecord::Base.connection.instance_values["config"][:adapter]
5
+ case adapter
6
+ when "postgresql"
7
+ raise "Missing option :value for StaticAnonymizer on #{table_name}.#{column_name}" if options[:value].blank?
8
+
9
+ return "'#{options[:value]}'"
10
+ else
11
+ raise "#{self.name} not implemented for #{adapter}"
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+
@@ -0,0 +1,28 @@
1
+ module Tidus
2
+ class TextAnonymizer
3
+ def self.anonymize(table_name, column_name, options = {})
4
+ base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZCßüäöÜÄÖ"
5
+ adapter = ActiveRecord::Base.connection.instance_values["config"][:adapter]
6
+ case adapter
7
+ when "postgresql"
8
+ return "translate((#{table_name}.#{column_name})::text, " +
9
+ "'#{base}'::text, '#{generate_mapping(base)}'::text)"
10
+ else
11
+ raise "#{self.name} not implemented for #{adapter}"
12
+ end
13
+ end
14
+
15
+ private
16
+ def self.generate_mapping(base)
17
+ result = ""
18
+ base.split("").each do |letter|
19
+ if letter == letter.upcase
20
+ result += ("A".."Z").to_a.shuffle.first
21
+ else
22
+ result += ("a".."z").to_a.shuffle.first
23
+ end
24
+ end
25
+ result
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Tidus
2
+ VERSION = "1.0.0"
3
+ end
data/lib/tidus.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "rake"
2
+ require "active_record"
3
+
4
+ require "tidus/version"
5
+ require "tidus/anonymization"
6
+ require "tidus/strategies/cond_anonymizer.rb"
7
+ require "tidus/strategies/email_anonymizer.rb"
8
+ require "tidus/strategies/null_anonymizer.rb"
9
+ require "tidus/strategies/overlay_anonymizer.rb"
10
+ require "tidus/strategies/static_anonymizer.rb"
11
+ require "tidus/strategies/text_anonymizer.rb"
12
+
13
+ load "active_record/railties/databases.rake"
14
+ load "tasks/views.rake"
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tidus
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Schoknecht
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 10.0.4
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 10.0.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.14.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.14.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.3.10
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.10
55
+ - !ruby/object:Gem::Dependency
56
+ name: activerecord
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ description: Creates views which allow anonymization of database tables.
70
+ email:
71
+ - tobias.schoknecht@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/tasks/views.rake
77
+ - lib/tidus.rb
78
+ - lib/tidus/anonymization.rb
79
+ - lib/tidus/strategies/cond_anonymizer.rb
80
+ - lib/tidus/strategies/email_anonymizer.rb
81
+ - lib/tidus/strategies/null_anonymizer.rb
82
+ - lib/tidus/strategies/overlay_anonymizer.rb
83
+ - lib/tidus/strategies/static_anonymizer.rb
84
+ - lib/tidus/strategies/text_anonymizer.rb
85
+ - lib/tidus/version.rb
86
+ homepage: ''
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.4.5
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Gem for creating anonymization views.
110
+ test_files: []