Class: MyObfuscate

Inherits:
Object
  • Object
show all
Defined in:
lib/my_obfuscate.rb

Constant Summary collapse

INSERT_REGEX =
/^\s*INSERT INTO `(.*?)` \((.*?)\) VALUES\s*/i
NUMBER_CHARS =
"1234567890"
USERNAME_CHARS =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" + NUMBER_CHARS
SENSIBLE_CHARS =
USERNAME_CHARS + '+-=[{]}/?|!@#$%^&*()`~'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(configuration = {}) ⇒ MyObfuscate

Returns a new instance of MyObfuscate.



12
13
14
# File 'lib/my_obfuscate.rb', line 12

def initialize(configuration = {})
  @config = configuration
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



5
6
7
# File 'lib/my_obfuscate.rb', line 5

def config
  @config
end

Class Method Details

.apply_table_config(row, table_config, columns) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/my_obfuscate.rb', line 106

def self.apply_table_config(row, table_config, columns)
  return row unless table_config.is_a?(Hash)

  table_config.each do |column, definition|
    # Don't change email addresses when they end in @honk.com.
    index = columns.index(column)

    if definition[:skip_regexes]
      next if definition[:skip_regexes].any? {|regex| row[index] =~ regex}
    end

    row[index.to_i] = case definition[:type]
      when :email
        random_string(4..10, USERNAME_CHARS) + "@example.com"
      when :string
        random_string(definition[:length], definition[:chars] || SENSIBLE_CHARS)
      when :integer
        random_integer(definition[:between] || (0..1000)).to_s
      when :fixed
        if definition[:one_of]
          definition[:one_of][(rand * definition[:one_of].length).to_i]
        else
          definition[:string]
        end
      when :null
        nil
      else
        row[index]
    end
  end
  row
end

.context_aware_mysql_string_split(string) ⇒ Object

Note: Strings must be quoted in single quotes!



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/my_obfuscate.rb', line 51

def self.context_aware_mysql_string_split(string)
  in_sub_insert = false
  in_quoted_string = false
  escaped = false
  current_field = nil
  length = string.length
  index = 0
  fields = []
  output = []
  string.each_char do |i|
    if escaped
      escaped = false
      current_field ||= ""
      current_field << i
    else
     if i == "\\"
       escaped = true
       current_field ||= ""
       current_field << i
     elsif i == "(" && !in_quoted_string && !in_sub_insert
       in_sub_insert = true
     elsif i == ")" && !in_quoted_string && in_sub_insert
       fields << current_field unless current_field.nil?
       output << fields unless fields.length == 0
       in_sub_insert = false
       fields = []
       current_field = nil
     elsif i == "'" && !in_quoted_string
       fields << current_field unless current_field.nil?
       current_field = ''
       in_quoted_string = true
     elsif i == "'" && in_quoted_string
       fields << current_field unless current_field.nil?
       current_field = nil
       in_quoted_string = false
     elsif i == "," && !in_quoted_string && in_sub_insert
       fields << current_field unless current_field.nil?
       current_field = nil
     elsif i == "L" && !in_quoted_string && in_sub_insert && current_field == "NUL"
       current_field = nil
       fields << current_field
     elsif (i == " " || i == "\t") && !in_quoted_string
       # Don't add whitespace not in a string
     elsif in_sub_insert
       current_field ||= ""
       current_field << i
     end
    end
    index += 1
  end
  fields << current_field unless current_field.nil?
  output << fields unless fields.length == 0
  output
end

.random_integer(between) ⇒ Object



139
140
141
# File 'lib/my_obfuscate.rb', line 139

def self.random_integer(between)
  (between.min + (between.max - between.min) * rand).round
end

.random_string(length_or_range, chars) ⇒ Object



143
144
145
146
147
148
149
# File 'lib/my_obfuscate.rb', line 143

def self.random_string(length_or_range, chars)
  length_or_range = (length_or_range..length_or_range) if length_or_range.is_a?(Fixnum)
  times = random_integer(length_or_range)
  out = ""
  times.times { out << chars[rand * chars.length] }
  out
end

.reasembling_each_insert(line, table_name, columns) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/my_obfuscate.rb', line 33

def self.reasembling_each_insert(line, table_name, columns)
  line = line.gsub(INSERT_REGEX, '').gsub(/\s*;\s*$/, '')
  output = context_aware_mysql_string_split(line).map do |sub_insert|
    result = yield(sub_insert)
    result = result.map do |i|
      if i.nil?
        "NULL"
      else
        "'" + i + "'"
      end
    end
    result = result.join(",")
    "(" + result + ")"
  end.join(",")
  "INSERT INTO `#{table_name}` (`#{columns.join('`, `')}`) VALUES #{output};"
end

Instance Method Details

#check_for_missing_columns(table_name, columns) ⇒ Object



151
152
153
154
155
156
157
158
159
# File 'lib/my_obfuscate.rb', line 151

def check_for_missing_columns(table_name, columns)
  missing_columns = config[table_name].keys - columns
  unless missing_columns.length == 0
    error_message = missing_columns.map do |missing_column|
      "Column '#{missing_column}' could not be found in table '#{table_name}', please fix your obfuscator config."
    end.join("\n")
     raise RuntimeError.new(error_message)
  end
end

#obfuscate(input_io, output_io) ⇒ Object

We assume that every INSERT INTO line occupies one line in the file, with no internal linebreaks.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/my_obfuscate.rb', line 17

def obfuscate(input_io, output_io)
  input_io.each do |line|
    if regex_result = INSERT_REGEX.match(line)
      table_name = regex_result[1].to_sym
      columns = regex_result[2].split(/`\s*,\s*`/).map { |col| col.gsub('`',"").to_sym }
      if config[table_name]
        output_io.puts obfuscate_line(line, table_name, columns)
      else
        output_io.write line
      end
    else
      output_io.write line
    end
  end
end

#obfuscate_line(line, table_name, columns) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/my_obfuscate.rb', line 161

def obfuscate_line(line, table_name, columns)
  table_config = config[table_name]
  if table_config == :truncate
    ""
  else
    check_for_missing_columns(table_name, columns)
    # Note: Remember to SQL escape strings in what you pass back.
    MyObfuscate.reasembling_each_insert(line, table_name, columns) do |row|
      MyObfuscate.apply_table_config(row, table_config, columns)
    end
  end
end