iterationlabs-my_obfuscate 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/LICENSE +19 -0
- data/README.rdoc +85 -0
- data/Rakefile +8 -0
- data/iterationlabs-my_obfuscate.gemspec +22 -0
- data/lib/my_obfuscate.rb +213 -0
- data/lib/my_obfuscate/mysql.rb +87 -0
- data/lib/my_obfuscate/sql_server.rb +79 -0
- data/lib/my_obfuscate/version.rb +3 -0
- data/spec/my_obfuscate_spec.rb +531 -0
- data/spec/mysql_spec.rb +50 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/sql_server_spec.rb +58 -0
- metadata +97 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Honk
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
= my_obfuscate
|
2
|
+
|
3
|
+
Standalone Ruby code for the selective rewriting of SQL dumps in order to protect user privacy. Supports MySQL and SQL Server.
|
4
|
+
|
5
|
+
= Install
|
6
|
+
|
7
|
+
sudo gem install my_obfuscate
|
8
|
+
|
9
|
+
= Example Usage
|
10
|
+
|
11
|
+
Make an obfuscator.rb script:
|
12
|
+
|
13
|
+
#!/usr/bin/env ruby
|
14
|
+
require "rubygems"
|
15
|
+
require "my_obfuscate"
|
16
|
+
|
17
|
+
obfuscator = MyObfuscate.new({
|
18
|
+
:people => {
|
19
|
+
:email => { :type => :email, :skip_regexes => [/^[\w\.\_]+@my_company\.com$/i] },
|
20
|
+
:ethnicity => :keep,
|
21
|
+
:crypted_password => { :type => :fixed, :string => "SOME_FIXED_PASSWORD_FOR_EASE_OF_DEBUGGING" },
|
22
|
+
:salt => { :type => :fixed, :string => "SOME_THING" },
|
23
|
+
:remember_token => :null,
|
24
|
+
:remember_token_expires_at => :null,
|
25
|
+
:age => { :type => :null, :unless => lambda { |person| person[:email] == "hello@example.com" } },
|
26
|
+
:photo_file_name => :null,
|
27
|
+
:photo_content_type => :null,
|
28
|
+
:photo_file_size => :null,
|
29
|
+
:photo_updated_at => :null,
|
30
|
+
:postal_code => { :type => :fixed, :string => "94109", :unless => lambda {|person| person[:postal_code] == "12345"} },
|
31
|
+
:name => :name,
|
32
|
+
:full_address => :address,
|
33
|
+
:bio => { :type => :lorem, :number => 4 },
|
34
|
+
:relationship_status => { :type => :fixed, :one_of => ["Single", "Divorced", "Married", "Engaged", "In a Relationship"] },
|
35
|
+
:has_children => { :type => :integer, :between => 0..1 },
|
36
|
+
},
|
37
|
+
|
38
|
+
:invites => :truncate,
|
39
|
+
:invite_requests => :truncate,
|
40
|
+
:tags => :keep,
|
41
|
+
|
42
|
+
:relationships => {
|
43
|
+
:account_id => :keep,
|
44
|
+
:code => { :type => :string, :length => 8, :chars => MyObfuscate::USERNAME_CHARS }
|
45
|
+
}
|
46
|
+
})
|
47
|
+
obfuscator.fail_on_unspecified_columns = true # if you want it to require every column in the table to be in the above definition
|
48
|
+
obfuscator.globally_kept_columns = %w[id created_at updated_at] # if you set fail_on_unspecified_columns, you may want this as well
|
49
|
+
obfuscator.obfuscate(STDIN, STDOUT)
|
50
|
+
|
51
|
+
And to get an obfuscated dump:
|
52
|
+
mysqldump -c --add-drop-table -u user -ppassword database | ruby obfuscator.rb > obfuscated_dump.sql
|
53
|
+
Note that the -c option on mysqldump is required to use my_obfuscator.
|
54
|
+
|
55
|
+
== Database Server
|
56
|
+
|
57
|
+
By default the database type is assumed to be MySQL, but you can use the
|
58
|
+
builtin SQL Server support by specifying:
|
59
|
+
|
60
|
+
obfuscator.database_type = :sql_server
|
61
|
+
|
62
|
+
== Changes
|
63
|
+
|
64
|
+
* Support for SQL Server
|
65
|
+
* :unless and :if now support :nil as a shorthand for a Proc that checks for nil
|
66
|
+
* :name, :lorem, and :address are all now supported types. You can pass :number to :lorem to specify how many sentences to generate. The default is one.
|
67
|
+
* <tt>{ :type => :whatever }</tt> is now optional when no additional options are needed. Just use <tt>:whatever</tt>.
|
68
|
+
* Warnings are thrown when an unknown column type or table is encountered. Use <tt>:keep</tt> in both cases.
|
69
|
+
* <tt>{ :type => :fixed, :string => Proc { |row| ... } }</tt> is now available.
|
70
|
+
|
71
|
+
== Note on Patches/Pull Requests
|
72
|
+
|
73
|
+
* Fork the project.
|
74
|
+
* Make your feature addition or bug fix.
|
75
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
76
|
+
* Commit, do not mess with rakefile, version, or history. (If you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
77
|
+
* Send me a pull request. Bonus points for topic branches.
|
78
|
+
|
79
|
+
== Thanks
|
80
|
+
|
81
|
+
Thanks to Mavenlink and Pivotal Labs for patches and updates!
|
82
|
+
|
83
|
+
== Copyright
|
84
|
+
|
85
|
+
Copyright (c) 2009 Honk. Now maintained by Iteration Labs, LLC. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "my_obfuscate/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = %q{iterationlabs-my_obfuscate}
|
7
|
+
s.version = MyObfuscate::VERSION
|
8
|
+
|
9
|
+
s.authors = ["Andrew Cantino", "Dave Willett", "Mike Grafton", "Mason Glaves", "Greg Bell", "Mavenlink"]
|
10
|
+
s.description = %q{Standalone Ruby code for the selective rewriting of MySQL dumps in order to protect user privacy.}
|
11
|
+
s.email = %q{andrew@iterationlabs.com}
|
12
|
+
s.homepage = %q{http://github.com/iterationlabs/myobfuscate}
|
13
|
+
s.summary = %q{Standalone Ruby code for the selective rewriting of MySQL dumps in order to protect user privacy.}
|
14
|
+
|
15
|
+
s.add_development_dependency "rspec"
|
16
|
+
s.add_dependency "faker", "=0.9.5"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
end
|
data/lib/my_obfuscate.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'jcode' if RUBY_VERSION < '1.9'
|
2
|
+
require 'faker'
|
3
|
+
require 'my_obfuscate/mysql'
|
4
|
+
require 'my_obfuscate/sql_server'
|
5
|
+
|
6
|
+
# Class for obfuscating MySQL dumps. This can parse mysqldump outputs when using the -c option, which includes
|
7
|
+
# column names in the insert statements.
|
8
|
+
class MyObfuscate
|
9
|
+
attr_accessor :config, :globally_kept_columns, :fail_on_unspecified_columns, :database_type
|
10
|
+
|
11
|
+
NUMBER_CHARS = "1234567890"
|
12
|
+
USERNAME_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" + NUMBER_CHARS
|
13
|
+
SENSIBLE_CHARS = USERNAME_CHARS + '+-=[{]}/?|!@#$%^&*()`~'
|
14
|
+
|
15
|
+
# Make a new MyObfuscate object. Pass in a configuration structure to define how the obfuscation should be
|
16
|
+
# performed. See the README.rdoc file for more information.
|
17
|
+
def initialize(configuration = {})
|
18
|
+
@config = configuration
|
19
|
+
end
|
20
|
+
|
21
|
+
def fail_on_unspecified_columns?
|
22
|
+
@fail_on_unspecified_columns
|
23
|
+
end
|
24
|
+
|
25
|
+
def database_helper
|
26
|
+
if @database_helper.nil?
|
27
|
+
if @database_type == :sql_server
|
28
|
+
@database_helper = SqlServer.new
|
29
|
+
else
|
30
|
+
@database_helper = Mysql.new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@database_helper
|
35
|
+
end
|
36
|
+
|
37
|
+
# Read an input stream and dump out an obfuscated output stream. These streams could be StringIO objects, Files,
|
38
|
+
# or STDIN and STDOUT.
|
39
|
+
def obfuscate(input_io, output_io)
|
40
|
+
|
41
|
+
# We assume that every INSERT INTO line occupies one line in the file, with no internal linebreaks.
|
42
|
+
input_io.each do |line|
|
43
|
+
if table_data = database_helper.parse_insert_statement(line)
|
44
|
+
table_name = table_data[:table_name]
|
45
|
+
columns = table_data[:column_names]
|
46
|
+
if config[table_name]
|
47
|
+
output_io.puts obfuscate_bulk_insert_line(line, table_name, columns)
|
48
|
+
else
|
49
|
+
$stderr.puts "Deprecated: #{table_name} was not specified in the config. A future release will cause this to be an error. Please specify the table definition or set it to :keep."
|
50
|
+
output_io.write line
|
51
|
+
end
|
52
|
+
else
|
53
|
+
output_io.write line
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def reassembling_each_insert(line, table_name, columns)
|
59
|
+
output = database_helper.rows_to_be_inserted(line).map do |sub_insert|
|
60
|
+
result = yield(sub_insert)
|
61
|
+
result = result.map do |i|
|
62
|
+
database_helper.make_valid_value_string(i)
|
63
|
+
end
|
64
|
+
result = result.join(",")
|
65
|
+
"(" + result + ")"
|
66
|
+
end.join(",")
|
67
|
+
database_helper.make_insert_statement(table_name, columns, output)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.row_as_hash(row, columns)
|
71
|
+
columns.zip(row).inject({}) {|m, (name, value)| m[name] = value; m}
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.make_conditional_method(conditional_method, index, row)
|
75
|
+
if conditional_method.is_a?(Symbol)
|
76
|
+
if conditional_method == :blank
|
77
|
+
conditional_method = lambda { |row_hash| row[index].nil? || row[index] == '' }
|
78
|
+
elsif conditional_method == :nil
|
79
|
+
conditional_method = lambda { |row_hash| row[index].nil? }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
conditional_method
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.apply_table_config(row, table_config, columns)
|
86
|
+
return row unless table_config.is_a?(Hash)
|
87
|
+
row_hash = row_as_hash(row, columns)
|
88
|
+
|
89
|
+
table_config.each do |column, definition|
|
90
|
+
index = columns.index(column)
|
91
|
+
|
92
|
+
definition = { :type => definition } if definition.is_a?(Symbol)
|
93
|
+
|
94
|
+
if definition.has_key?(:unless)
|
95
|
+
unless_check = make_conditional_method(definition[:unless], index, row)
|
96
|
+
|
97
|
+
next if unless_check.call(row_hash)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
if definition.has_key?(:if)
|
102
|
+
if_check = make_conditional_method(definition[:if], index, row)
|
103
|
+
|
104
|
+
next unless if_check.call(row_hash)
|
105
|
+
end
|
106
|
+
|
107
|
+
if definition[:skip_regexes]
|
108
|
+
next if definition[:skip_regexes].any? {|regex| row[index] =~ regex}
|
109
|
+
end
|
110
|
+
|
111
|
+
row[index.to_i] = case definition[:type]
|
112
|
+
when :email
|
113
|
+
random_string(definition[:length] || (4..10), USERNAME_CHARS) + "@example.com"
|
114
|
+
when :string
|
115
|
+
random_string(definition[:length] || 30, definition[:chars] || SENSIBLE_CHARS)
|
116
|
+
when :lorem
|
117
|
+
clean_bad_whitespace(clean_quotes(Faker::Lorem.sentences(definition[:number] || 1).join(". ")))
|
118
|
+
when :name
|
119
|
+
clean_quotes(Faker::Name.name)
|
120
|
+
when :first_name
|
121
|
+
clean_quotes(Faker::Name.first_name)
|
122
|
+
when :last_name
|
123
|
+
clean_quotes(Faker::Name.last_name)
|
124
|
+
when :address
|
125
|
+
clean_quotes("#{Faker::Address.street_address}\\n#{Faker::Address.city}, #{Faker::Address.state_abbr} #{Faker::Address.zip_code}")
|
126
|
+
when :street_address
|
127
|
+
clean_bad_whitespace(clean_quotes(Faker::Address.street_address))
|
128
|
+
when :city
|
129
|
+
clean_quotes(Faker::Address.city)
|
130
|
+
when :state
|
131
|
+
Faker::Address.state_abbr
|
132
|
+
when :zip_code
|
133
|
+
Faker::Address.zip_code
|
134
|
+
when :phone
|
135
|
+
Faker::PhoneNumber.phone_number
|
136
|
+
when :integer
|
137
|
+
random_integer(definition[:between] || (0..1000)).to_s
|
138
|
+
when :fixed
|
139
|
+
if definition[:one_of]
|
140
|
+
definition[:one_of][(rand * definition[:one_of].length).to_i]
|
141
|
+
else
|
142
|
+
definition[:string].is_a?(Proc) ? definition[:string].call(row_hash) : definition[:string]
|
143
|
+
end
|
144
|
+
when :null
|
145
|
+
nil
|
146
|
+
when :keep
|
147
|
+
row[index]
|
148
|
+
else
|
149
|
+
$stderr.puts "Keeping a column value by providing an unknown type (#{definition[:type]}) is deprecated. Use :keep instead."
|
150
|
+
row[index]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
row
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.random_integer(between)
|
157
|
+
(between.min + (between.max - between.min) * rand).round
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.random_string(length_or_range, chars)
|
161
|
+
length_or_range = (length_or_range..length_or_range) if length_or_range.is_a?(Fixnum)
|
162
|
+
times = random_integer(length_or_range)
|
163
|
+
out = ""
|
164
|
+
times.times { out << chars[rand * chars.length] }
|
165
|
+
out
|
166
|
+
end
|
167
|
+
|
168
|
+
def check_for_defined_columns_not_in_table(table_name, columns)
|
169
|
+
missing_columns = config[table_name].keys - columns
|
170
|
+
unless missing_columns.length == 0
|
171
|
+
error_message = missing_columns.map do |missing_column|
|
172
|
+
"Column '#{missing_column}' could not be found in table '#{table_name}', please fix your obfuscator config."
|
173
|
+
end.join("\n")
|
174
|
+
raise RuntimeError.new(error_message)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def check_for_table_columns_not_in_definition(table_name, columns)
|
179
|
+
missing_columns = columns - (config[table_name].keys + (globally_kept_columns || []).map {|i| i.to_sym}).uniq
|
180
|
+
unless missing_columns.length == 0
|
181
|
+
error_message = missing_columns.map do |missing_column|
|
182
|
+
"Column '#{missing_column}' defined in table '#{table_name}', but not found in table definition, please fix your obfuscator config."
|
183
|
+
end.join("\n")
|
184
|
+
raise RuntimeError.new(error_message)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def obfuscate_bulk_insert_line(line, table_name, columns)
|
189
|
+
table_config = config[table_name]
|
190
|
+
if table_config == :truncate
|
191
|
+
""
|
192
|
+
elsif table_config == :keep
|
193
|
+
line
|
194
|
+
else
|
195
|
+
check_for_defined_columns_not_in_table(table_name, columns)
|
196
|
+
check_for_table_columns_not_in_definition(table_name, columns) if fail_on_unspecified_columns?
|
197
|
+
# Note: Remember to SQL escape strings in what you pass back.
|
198
|
+
reassembling_each_insert(line, table_name, columns) do |row|
|
199
|
+
MyObfuscate.apply_table_config(row, table_config, columns)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
def self.clean_quotes(value)
|
207
|
+
value.gsub(/['"]/, '')
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.clean_bad_whitespace(value)
|
211
|
+
value.gsub(/[\n\t\r]/, '')
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class MyObfuscate::Mysql
|
2
|
+
INSERT_REGEX = /^\s*INSERT INTO `(.*?)` \((.*?)\) VALUES\s*/i
|
3
|
+
|
4
|
+
def parse_insert_statement(line)
|
5
|
+
if regex_match = INSERT_REGEX.match(line)
|
6
|
+
{
|
7
|
+
:table_name => regex_match[1].to_sym,
|
8
|
+
:column_names => regex_match[2].split(/`\s*,\s*`/).map { |col| col.gsub('`', "").to_sym }
|
9
|
+
}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def rows_to_be_inserted(line)
|
14
|
+
line = line.gsub(INSERT_REGEX, '').gsub(/\s*;\s*$/, '')
|
15
|
+
context_aware_mysql_string_split(line)
|
16
|
+
end
|
17
|
+
|
18
|
+
def make_valid_value_string(value)
|
19
|
+
if value.nil?
|
20
|
+
"NULL"
|
21
|
+
else
|
22
|
+
"'" + value + "'"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def make_insert_statement(table_name, column_names, values_strings)
|
27
|
+
"INSERT INTO `#{table_name}` (`#{column_names.join('`, `')}`) VALUES #{values_strings};"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Be aware, strings must be quoted in single quotes!
|
33
|
+
def context_aware_mysql_string_split(string)
|
34
|
+
in_sub_insert = false
|
35
|
+
in_quoted_string = false
|
36
|
+
escaped = false
|
37
|
+
current_field = nil
|
38
|
+
length = string.length
|
39
|
+
fields = []
|
40
|
+
output = []
|
41
|
+
|
42
|
+
string.each_char do |i|
|
43
|
+
if escaped
|
44
|
+
escaped = false
|
45
|
+
current_field ||= ""
|
46
|
+
current_field << i
|
47
|
+
else
|
48
|
+
if i == "\\"
|
49
|
+
escaped = true
|
50
|
+
current_field ||= ""
|
51
|
+
current_field << i
|
52
|
+
elsif i == "(" && !in_quoted_string && !in_sub_insert
|
53
|
+
in_sub_insert = true
|
54
|
+
elsif i == ")" && !in_quoted_string && in_sub_insert
|
55
|
+
fields << current_field unless current_field.nil?
|
56
|
+
output << fields unless fields.length == 0
|
57
|
+
in_sub_insert = false
|
58
|
+
fields = []
|
59
|
+
current_field = nil
|
60
|
+
elsif i == "'" && !in_quoted_string
|
61
|
+
fields << current_field unless current_field.nil?
|
62
|
+
current_field = ''
|
63
|
+
in_quoted_string = true
|
64
|
+
elsif i == "'" && in_quoted_string
|
65
|
+
fields << current_field unless current_field.nil?
|
66
|
+
current_field = nil
|
67
|
+
in_quoted_string = false
|
68
|
+
elsif i == "," && !in_quoted_string && in_sub_insert
|
69
|
+
fields << current_field unless current_field.nil?
|
70
|
+
current_field = nil
|
71
|
+
elsif i == "L" && !in_quoted_string && in_sub_insert && current_field == "NUL"
|
72
|
+
current_field = nil
|
73
|
+
fields << current_field
|
74
|
+
elsif (i == " " || i == "\t") && !in_quoted_string
|
75
|
+
# Don't add whitespace not in a string
|
76
|
+
elsif in_sub_insert
|
77
|
+
current_field ||= ""
|
78
|
+
current_field << i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
fields << current_field unless current_field.nil?
|
84
|
+
output << fields unless fields.length == 0
|
85
|
+
output
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class MyObfuscate::SqlServer
|
2
|
+
INSERT_REGEX = /^\s*INSERT (?:INTO )?\[dbo\]\.\[(.*?)\] \((.*?)\) VALUES\s*/i
|
3
|
+
|
4
|
+
def parse_insert_statement(line)
|
5
|
+
if regex_match = INSERT_REGEX.match(line)
|
6
|
+
{
|
7
|
+
:table_name => regex_match[1].to_sym,
|
8
|
+
:column_names => regex_match[2].split(/\]\s*,\s*\[/).map { |col| col.gsub(/[\[\]]/, "").to_sym }
|
9
|
+
}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def rows_to_be_inserted(line)
|
14
|
+
line = line.gsub(INSERT_REGEX, '').gsub(/\s*;?\s*$/, '').gsub(/^\(/, '').gsub(/\)$/, '')
|
15
|
+
context_aware_sql_server_string_split(line)
|
16
|
+
end
|
17
|
+
|
18
|
+
def make_valid_value_string(value)
|
19
|
+
if value.nil?
|
20
|
+
"NULL"
|
21
|
+
elsif value.match(/^[A-Z]+\(.*?\)$/)
|
22
|
+
value
|
23
|
+
else
|
24
|
+
"N'#{value}'"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def make_insert_statement(table_name, column_names, values_strings)
|
29
|
+
"INSERT [dbo].[#{table_name}] ([#{column_names.join("], [")}]) VALUES #{values_strings};"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def context_aware_sql_server_string_split(string)
|
35
|
+
in_quoted_string = false
|
36
|
+
backslash_escape = false
|
37
|
+
previous_char_single_quote = false
|
38
|
+
current_field_value = nil
|
39
|
+
completed_fields = []
|
40
|
+
|
41
|
+
string.each_char do |char|
|
42
|
+
if char == "'" && !in_quoted_string
|
43
|
+
if current_field_value != "N"
|
44
|
+
completed_fields << current_field_value unless current_field_value.nil?
|
45
|
+
end
|
46
|
+
current_field_value = ""
|
47
|
+
in_quoted_string = true
|
48
|
+
elsif previous_char_single_quote
|
49
|
+
previous_char_single_quote = false
|
50
|
+
if char == "'"
|
51
|
+
current_field_value << "''"
|
52
|
+
else
|
53
|
+
completed_fields << current_field_value unless current_field_value.nil?
|
54
|
+
in_quoted_string = false
|
55
|
+
current_field_value = nil
|
56
|
+
end
|
57
|
+
elsif char == "'" && in_quoted_string
|
58
|
+
previous_char_single_quote = true
|
59
|
+
elsif char == "," && !in_quoted_string
|
60
|
+
completed_fields << current_field_value unless current_field_value.nil?
|
61
|
+
current_field_value = nil
|
62
|
+
elsif char == "L" && !in_quoted_string && current_field_value == "NUL"
|
63
|
+
current_field_value = nil
|
64
|
+
completed_fields << current_field_value
|
65
|
+
elsif (char == " " || char == "\t") && !in_quoted_string
|
66
|
+
if !current_field_value.nil? && current_field_value.start_with?("CAST(")
|
67
|
+
current_field_value << char
|
68
|
+
end
|
69
|
+
# Don't add whitespace not in a string
|
70
|
+
else
|
71
|
+
current_field_value ||= ""
|
72
|
+
current_field_value << char
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
completed_fields << current_field_value unless current_field_value.nil?
|
77
|
+
[completed_fields]
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,531 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MyObfuscate do
|
4
|
+
describe "MyObfuscate.reassembling_each_insert" do
|
5
|
+
before do
|
6
|
+
@column_names = [:a, :b, :c, :d]
|
7
|
+
@test_insert = "INSERT INTO `some_table` (`a`, `b`, `c`, `d`) VALUES ('(\\'bob@bob.com','b()ob','some(thingelse1','25)('),('joe@joe.com','joe','somethingelse2','54');"
|
8
|
+
@test_insert_passes = [
|
9
|
+
["(\\'bob@bob.com", "b()ob", "some(thingelse1", "25)("],
|
10
|
+
["joe@joe.com", "joe", "somethingelse2", "54"]
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should yield each subinsert and reassemble the result" do
|
15
|
+
count = 0
|
16
|
+
reassembled = MyObfuscate.new.reassembling_each_insert(@test_insert, "some_table", @column_names) do |sub_insert|
|
17
|
+
sub_insert.should == @test_insert_passes.shift
|
18
|
+
count += 1
|
19
|
+
sub_insert
|
20
|
+
end
|
21
|
+
count.should == 2
|
22
|
+
reassembled.should == @test_insert
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "MyObfuscate.apply_table_config" do
|
27
|
+
it "should work on email addresses" do
|
28
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else"], {:a => {:type => :email}}, [:a, :b])
|
29
|
+
new_row.length.should == 2
|
30
|
+
new_row.first.should =~ /^\w+\@\w+\.\w+$/
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should work on strings" do
|
34
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "something crazy"], {:b => {:type => :string, :length => 7}}, [:a, :b, :c])
|
35
|
+
new_row.length.should == 3
|
36
|
+
new_row[1].length.should == 7
|
37
|
+
new_row[1].should_not == "something_else"
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "conditional directives" do
|
41
|
+
it "should honor :unless conditionals" do
|
42
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :unless => lambda { |row| row[:a] == "blah" }}}, [:a, :b, :c])
|
43
|
+
new_row[0].should_not == "123"
|
44
|
+
new_row[0].should == "blah"
|
45
|
+
|
46
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :unless => lambda { |row| row[:a] == "not blah" }}}, [:a, :b, :c])
|
47
|
+
new_row[0].should == "123"
|
48
|
+
|
49
|
+
new_row = MyObfuscate.apply_table_config([nil, "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :unless => :nil}, :b=> {:type => :fixed, :string => "123", :unless => :nil}}, [:a, :b, :c])
|
50
|
+
new_row[0].should == nil
|
51
|
+
new_row[1].should == "123"
|
52
|
+
|
53
|
+
new_row = MyObfuscate.apply_table_config(['', "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :unless => :blank}, :b=> {:type => :fixed, :string => "123", :unless => :blank}}, [:a, :b, :c])
|
54
|
+
new_row[0].should == ''
|
55
|
+
new_row[1].should == "123"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should honor :if conditionals" do
|
59
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :if => lambda { |row| row[:a] == "blah" }}}, [:a, :b, :c])
|
60
|
+
new_row[0].should == "123"
|
61
|
+
|
62
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :if=> lambda { |row| row[:a] == "not blah" }}}, [:a, :b, :c])
|
63
|
+
new_row[0].should_not == "123"
|
64
|
+
new_row[0].should == "blah"
|
65
|
+
|
66
|
+
new_row = MyObfuscate.apply_table_config([nil, "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :if => :nil}, :b=> {:type => :fixed, :string => "123", :if => :nil}}, [:a, :b, :c])
|
67
|
+
new_row[0].should == "123"
|
68
|
+
new_row[1].should == "something_else"
|
69
|
+
|
70
|
+
new_row = MyObfuscate.apply_table_config(['', "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :if => :blank}, :b=> {:type => :fixed, :string => "123", :if => :blank}}, [:a, :b, :c])
|
71
|
+
new_row[0].should == "123"
|
72
|
+
new_row[1].should == "something_else"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should supply the original row values to the conditional" do
|
76
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else"], {:a => {:type => :fixed, :string => "123"}, :b => {:type => :fixed, :string => "yup", :if => lambda { |row| row[:a] == "blah" }}}, [:a, :b])
|
77
|
+
new_row[0].should == "123"
|
78
|
+
new_row[1].should == "yup"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should honor combined :unless and :if conditionals" do
|
82
|
+
#both true
|
83
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :if => lambda { |row| row[:a] == "blah" }, :unless => lambda { |row| row[:b] == "something_else" }}}, [:a, :b, :c])
|
84
|
+
new_row[0].should == "blah"
|
85
|
+
|
86
|
+
#both false
|
87
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :if => lambda { |row| row[:a] == "not blah" }, :unless => lambda { |row| row[:b] == "not something_else" }}}, [:a, :b, :c])
|
88
|
+
new_row[0].should == "blah"
|
89
|
+
|
90
|
+
#if true, #unless false
|
91
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :if => lambda { |row| row[:a] == "blah" }, :unless => lambda { |row| row[:b] == "not something_else" }}}, [:a, :b, :c])
|
92
|
+
new_row[0].should == "123"
|
93
|
+
|
94
|
+
#if false, #unless true
|
95
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a=> {:type => :fixed, :string => "123", :if => lambda { |row| row[:a] == "not blah" }, :unless => lambda { |row| row[:b] == "something_else" }}}, [:a, :b, :c])
|
96
|
+
new_row[0].should == "blah"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should be able to generate random integers in ranges" do
|
101
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:c => {:type => :integer, :between => 10..100}}, [:a, :b, :c])
|
102
|
+
new_row.length.should == 3
|
103
|
+
new_row[2].to_i.to_s.should == new_row[2] # It should be an integer.
|
104
|
+
new_row[2].should_not == "5"
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should be able to substitute fixed strings" do
|
108
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:b => {:type => :fixed, :string => "hello"}}, [:a, :b, :c])
|
109
|
+
new_row.length.should == 3
|
110
|
+
new_row[1].should == "hello"
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should be able to substitute a proc that returns a string" do
|
114
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:b => {:type => :fixed, :string => proc { "Hello World" }}}, [:a, :b, :c])
|
115
|
+
new_row.length.should == 3
|
116
|
+
new_row[1].should == "Hello World"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should provide the row to the proc" do
|
120
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:b => {:type => :fixed, :string => proc { |a| a[:b] }}}, [:a, :b, :c])
|
121
|
+
new_row.length.should == 3
|
122
|
+
new_row[1].should == "something_else"
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should be able to substitute fixed strings from a random set" do
|
126
|
+
looking_for = ["hello", "world"]
|
127
|
+
original_looking_for = looking_for.dup
|
128
|
+
guard = 0
|
129
|
+
while !looking_for.empty? && guard < 1000
|
130
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a => {:type => :fixed, :one_of => ["hello", "world"]}}, [:a, :b, :c])
|
131
|
+
new_row.length.should == 3
|
132
|
+
original_looking_for.should include(new_row[0])
|
133
|
+
looking_for.delete new_row[0]
|
134
|
+
guard += 1
|
135
|
+
end
|
136
|
+
looking_for.should be_empty
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should treat a symbol in the column definition as an implicit { :type => symbol }" do
|
140
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:b => :null, :a => :keep}, [:a, :b, :c])
|
141
|
+
new_row.length.should == 3
|
142
|
+
new_row[0].should == "blah"
|
143
|
+
new_row[1].should == nil
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should be able to set things NULL" do
|
147
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:b => {:type => :null}}, [:a, :b, :c])
|
148
|
+
new_row.length.should == 3
|
149
|
+
new_row[1].should == nil
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should be able to :keep the value the same" do
|
153
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:b => {:type => :keep}}, [:a, :b, :c])
|
154
|
+
new_row.length.should == 3
|
155
|
+
new_row[1].should == "something_else"
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should keep the value when given an unknown type, but should display a warning" do
|
159
|
+
$stderr = error_output = StringIO.new
|
160
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:b => {:type => :unknown_type}}, [:a, :b, :c])
|
161
|
+
$stderr = STDERR
|
162
|
+
new_row.length.should == 3
|
163
|
+
new_row[1].should == "something_else"
|
164
|
+
error_output.rewind
|
165
|
+
error_output.read.should =~ /Keeping a column value by.*?unknown_type/
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should be able to substitute lorem ipsum text" do
|
169
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a => :lorem, :b => {:type => :lorem, :number => 2}}, [:a, :b, :c])
|
170
|
+
new_row.length.should == 3
|
171
|
+
new_row[0].should_not == "blah"
|
172
|
+
new_row[0].should_not =~ /\w\.(?!\Z)/
|
173
|
+
new_row[1].should_not == "something_else"
|
174
|
+
new_row[1].should =~ /\w\.(?!\Z)/
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should be able to generate an :address" do
|
178
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a => :address}, [:a, :b, :c])
|
179
|
+
new_row.length.should == 3
|
180
|
+
new_row[0].should_not == "blah"
|
181
|
+
new_row[0].should =~ /\d+ \w/
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should be able to generate a :name" do
|
185
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a => :name}, [:a, :b, :c])
|
186
|
+
new_row.length.should == 3
|
187
|
+
new_row[0].should_not == "blah"
|
188
|
+
new_row[0].should =~ / /
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should be able to generate just a street address" do
|
192
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a => :street_address}, [:a, :b, :c])
|
193
|
+
new_row.length.should == 3
|
194
|
+
new_row[0].should_not == "blah"
|
195
|
+
new_row[0].should =~ /\d+ \w/
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should be able to generate a city" do
|
199
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a => :city}, [:a, :b, :c])
|
200
|
+
new_row.length.should == 3
|
201
|
+
new_row[0].should_not == "blah"
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should be able to generate a state" do
|
205
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a => :state}, [:a, :b, :c])
|
206
|
+
new_row.length.should == 3
|
207
|
+
new_row[0].should_not == "blah"
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should be able to generate a zip code" do
|
211
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a => :zip_code}, [:a, :b, :c])
|
212
|
+
new_row.length.should == 3
|
213
|
+
new_row[0].should_not == "blah"
|
214
|
+
new_row[0].should =~ /\d+/
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should be able to generate a phone number" do
|
218
|
+
new_row = MyObfuscate.apply_table_config(["blah", "something_else", "5"], {:a => :phone}, [:a, :b, :c])
|
219
|
+
new_row.length.should == 3
|
220
|
+
new_row[0].should_not == "blah"
|
221
|
+
new_row[0].should =~ /\d+/
|
222
|
+
end
|
223
|
+
|
224
|
+
describe "when faker generates values with quotes in them" do
|
225
|
+
before do
|
226
|
+
Faker::Address.stub(:city).and_return("O'ReillyTown")
|
227
|
+
Faker::Name.stub(:name).and_return("Foo O'Reilly")
|
228
|
+
Faker::Name.stub(:first_name).and_return("O'Foo")
|
229
|
+
Faker::Name.stub(:last_name).and_return("O'Reilly")
|
230
|
+
Faker::Lorem.stub(:sentences).with(any_args).and_return(["Foo bar O'Thingy"])
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should remove single quotes from the value" do
|
234
|
+
new_row = MyObfuscate.apply_table_config(["address", "city", "first", "last", "fullname", "some text"],
|
235
|
+
{:a => :address, :b => :city, :c => :first_name, :d => :last_name, :e => :name, :f => :lorem},
|
236
|
+
[:a, :b, :c, :d, :e, :f])
|
237
|
+
new_row.each {|value| value.should_not include("'")}
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
describe "MyObfuscate.row_as_hash" do
|
243
|
+
it "will map row values into a hash with column names as keys" do
|
244
|
+
MyObfuscate.row_as_hash([1, 2, 3, 4], [:a, :b, :c, :d]).should == {:a => 1, :b => 2, :c => 3, :d => 4}
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe "#obfuscate" do
|
249
|
+
describe "when using MySQL" do
|
250
|
+
context "when there is nothing to obfuscate" do
|
251
|
+
it "should accept an IO object for input and output, and copy the input to the output" do
|
252
|
+
ddo = MyObfuscate.new
|
253
|
+
string = "hello, world\nsup?"
|
254
|
+
input = StringIO.new(string)
|
255
|
+
output = StringIO.new
|
256
|
+
ddo.obfuscate(input, output)
|
257
|
+
input.rewind
|
258
|
+
output.rewind
|
259
|
+
output.read.should == string
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
context "when the dump to obfuscate is missing columns" do
|
264
|
+
before do
|
265
|
+
@database_dump = StringIO.new(<<-SQL)
|
266
|
+
INSERT INTO `some_table` (`email`, `name`, `something`, `age`) VALUES ('bob@honk.com','bob', 'some\\'thin,ge())lse1', 25),('joe@joe.com','joe', 'somethingelse2', 54);
|
267
|
+
SQL
|
268
|
+
@ddo = MyObfuscate.new({
|
269
|
+
:some_table => {
|
270
|
+
:email => {:type => :email, :honk_email_skip => true},
|
271
|
+
:name => {:type => :string, :length => 8, :chars => MyObfuscate::USERNAME_CHARS},
|
272
|
+
:gender => {:type => :fixed, :string => "m"}
|
273
|
+
}})
|
274
|
+
@output = StringIO.new
|
275
|
+
end
|
276
|
+
|
277
|
+
it "should raise an error if a column name can't be found" do
|
278
|
+
lambda {
|
279
|
+
@ddo.obfuscate(@database_dump, @output)
|
280
|
+
}.should raise_error
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
context "when there is something to obfuscate" do
|
285
|
+
before do
|
286
|
+
@database_dump = StringIO.new(<<-SQL)
|
287
|
+
INSERT INTO `some_table` (`email`, `name`, `something`, `age`) VALUES ('bob@honk.com','bob', 'some\\'thin,ge())lse1', 25),('joe@joe.com','joe', 'somethingelse2', 54),('dontmurderme@direwolf.com','direwolf', 'somethingelse3', 44);
|
288
|
+
INSERT INTO `another_table` (`a`, `b`, `c`, `d`) VALUES (1,2,3,4), (5,6,7,8);
|
289
|
+
INSERT INTO `some_table_to_keep` (`a`, `b`, `c`, `d`) VALUES (1,2,3,4), (5,6,7,8);
|
290
|
+
INSERT INTO `one_more_table` (`a`, `password`, `c`, `d,d`) VALUES ('hello','kjhjd^&dkjh', 'aawefjkafe'), ('hello1','kjhj!', 892938), ('hello2','moose!!', NULL);
|
291
|
+
INSERT INTO `an_ignored_table` (`col`, `col2`) VALUES ('hello','kjhjd^&dkjh'), ('hello1','kjhj!'), ('hello2','moose!!');
|
292
|
+
SQL
|
293
|
+
|
294
|
+
@ddo = MyObfuscate.new({
|
295
|
+
:some_table => {
|
296
|
+
:email => {:type => :email, :skip_regexes => [/^[\w\.\_]+@honk\.com$/i, /^dontmurderme@direwolf.com$/]},
|
297
|
+
:name => {:type => :string, :length => 8, :chars => MyObfuscate::USERNAME_CHARS},
|
298
|
+
:age => {:type => :integer, :between => 10...80}
|
299
|
+
},
|
300
|
+
:another_table => :truncate,
|
301
|
+
:some_table_to_keep => :keep,
|
302
|
+
:one_more_table => {
|
303
|
+
# Note: fixed strings must be pre-SQL escaped!
|
304
|
+
:password => {:type => :fixed, :string => "monkey"},
|
305
|
+
:c => {:type => :null}
|
306
|
+
}
|
307
|
+
})
|
308
|
+
@output = StringIO.new
|
309
|
+
$stderr = @error_output = StringIO.new
|
310
|
+
@ddo.obfuscate(@database_dump, @output)
|
311
|
+
$stderr = STDERR
|
312
|
+
@output.rewind
|
313
|
+
@output_string = @output.read
|
314
|
+
end
|
315
|
+
|
316
|
+
it "should be able to truncate tables" do
|
317
|
+
@output_string.should_not include("INSERT INTO `another_table`")
|
318
|
+
@output_string.should include("INSERT INTO `one_more_table`")
|
319
|
+
end
|
320
|
+
|
321
|
+
it "should be able to declare tables to keep" do
|
322
|
+
@output_string.should include("INSERT INTO `some_table_to_keep` (`a`, `b`, `c`, `d`) VALUES (1,2,3,4), (5,6,7,8);")
|
323
|
+
end
|
324
|
+
|
325
|
+
it "should ignore tables that it doesn't know about, but should warn" do
|
326
|
+
@output_string.should include("INSERT INTO `an_ignored_table` (`col`, `col2`) VALUES ('hello','kjhjd^&dkjh'), ('hello1','kjhj!'), ('hello2','moose!!');")
|
327
|
+
@error_output.rewind
|
328
|
+
@error_output.read.should =~ /an_ignored_table was not specified in the config/
|
329
|
+
end
|
330
|
+
|
331
|
+
it "should obfuscate the tables" do
|
332
|
+
@output_string.should include("INSERT INTO `some_table` (`email`, `name`, `something`, `age`) VALUES (")
|
333
|
+
@output_string.should include("INSERT INTO `one_more_table` (`a`, `password`, `c`, `d,d`) VALUES (")
|
334
|
+
@output_string.should include("'some\\'thin,ge())lse1'")
|
335
|
+
@output_string.should include("INSERT INTO `one_more_table` (`a`, `password`, `c`, `d,d`) VALUES ('hello','monkey',NULL),('hello1','monkey',NULL),('hello2','monkey',NULL);")
|
336
|
+
@output_string.should_not include("INSERT INTO `one_more_table` (`a`, `password`, `c`, `d,d`) VALUES ('hello','kjhjd^&dkjh', 'aawefjkafe'), ('hello1','kjhj!', 892938), ('hello2','moose!!', NULL);")
|
337
|
+
@output_string.should_not include("INSERT INTO `one_more_table` (`a`, `password`, `c`, `d,d`) VALUES ('hello','kjhjd^&dkjh','aawefjkafe'),('hello1','kjhj!',892938),('hello2','moose!!',NULL);")
|
338
|
+
@output_string.should_not include("INSERT INTO `some_table` (`email`, `name`, `something`, `age`) VALUES ('bob@honk.com','bob', 'some\\'thin,ge())lse1', 25),('joe@joe.com','joe', 'somethingelse2', 54);")
|
339
|
+
end
|
340
|
+
|
341
|
+
it "honors a special case: on the people table, rows with anything@honk.com in a slot marked with :honk_email_skip do not change this slot" do
|
342
|
+
@output_string.should include("('bob@honk.com',")
|
343
|
+
@output_string.should include("('dontmurderme@direwolf.com',")
|
344
|
+
@output_string.should_not include("joe@joe.com")
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
context "when fail_on_unspecified_columns is set to true" do
|
349
|
+
before do
|
350
|
+
@database_dump = StringIO.new(<<-SQL)
|
351
|
+
INSERT INTO `some_table` (`email`, `name`, `something`, `age`) VALUES ('bob@honk.com','bob', 'some\\'thin,ge())lse1', 25),('joe@joe.com','joe', 'somethingelse2', 54),('dontmurderme@direwolf.com','direwolf', 'somethingelse3', 44);
|
352
|
+
SQL
|
353
|
+
|
354
|
+
@ddo = MyObfuscate.new({
|
355
|
+
:some_table => {
|
356
|
+
:email => {:type => :email, :skip_regexes => [/^[\w\.\_]+@honk\.com$/i, /^dontmurderme@direwolf.com$/]},
|
357
|
+
:name => {:type => :string, :length => 8, :chars => MyObfuscate::USERNAME_CHARS},
|
358
|
+
:age => {:type => :integer, :between => 10...80}
|
359
|
+
}
|
360
|
+
})
|
361
|
+
@ddo.fail_on_unspecified_columns = true
|
362
|
+
end
|
363
|
+
|
364
|
+
it "should raise an exception when an unspecified column is found" do
|
365
|
+
lambda {
|
366
|
+
@ddo.obfuscate(@database_dump, StringIO.new)
|
367
|
+
}.should raise_error(/column 'something' defined/i)
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should accept columns defined in globally_kept_columns" do
|
371
|
+
@ddo.globally_kept_columns = %w[something]
|
372
|
+
lambda {
|
373
|
+
@ddo.obfuscate(@database_dump, StringIO.new)
|
374
|
+
}.should_not raise_error
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
describe "when using MS SQL Server" do
|
380
|
+
context "when there is nothing to obfuscate" do
|
381
|
+
it "should accept an IO object for input and output, and copy the input to the output" do
|
382
|
+
ddo = MyObfuscate.new
|
383
|
+
ddo.database_type = :sql_server
|
384
|
+
string = "hello, world\nsup?"
|
385
|
+
input = StringIO.new(string)
|
386
|
+
output = StringIO.new
|
387
|
+
ddo.obfuscate(input, output)
|
388
|
+
input.rewind
|
389
|
+
output.rewind
|
390
|
+
output.read.should == string
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
context "when the dump to obfuscate is missing columns" do
|
395
|
+
before do
|
396
|
+
@database_dump = StringIO.new(<<-SQL)
|
397
|
+
INSERT [dbo].[some_table] ([email], [name], [something], [age]) VALUES ('bob@honk.com','bob', 'some''thin,ge())lse1', 25);
|
398
|
+
SQL
|
399
|
+
@ddo = MyObfuscate.new({
|
400
|
+
:some_table => {
|
401
|
+
:email => {:type => :email, :honk_email_skip => true},
|
402
|
+
:name => {:type => :string, :length => 8, :chars => MyObfuscate::USERNAME_CHARS},
|
403
|
+
:gender => {:type => :fixed, :string => "m"}
|
404
|
+
}})
|
405
|
+
@ddo.database_type = :sql_server
|
406
|
+
@output = StringIO.new
|
407
|
+
end
|
408
|
+
|
409
|
+
it "should raise an error if a column name can't be found" do
|
410
|
+
lambda {
|
411
|
+
@ddo.obfuscate(@database_dump, @output)
|
412
|
+
}.should raise_error
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
context "when there is something to obfuscate" do
|
417
|
+
before do
|
418
|
+
@database_dump = StringIO.new(<<-SQL)
|
419
|
+
INSERT [dbo].[some_table] ([email], [name], [something], [age], [bday]) VALUES (N'bob@honk.com',N'bob', N'some''thin,ge())lse1', 25, CAST(0x00009E1A00000000 AS DATETIME));
|
420
|
+
INSERT [dbo].[some_table] ([email], [name], [something], [age], [bday]) VALUES (N'joe@joe.com',N'joe', N'somethingelse2', 54, CAST(0x00009E1A00000000 AS DATETIME));
|
421
|
+
INSERT [dbo].[some_table] ([email], [name], [something], [age], [bday]) VALUES (N'dontmurderme@direwolf.com',N'direwolf', N'somethingelse3', 44, CAST(0x00009E1A00000000 AS DATETIME));
|
422
|
+
INSERT [dbo].[another_table] ([a], [b], [c], [d]) VALUES (1,2,3,4);
|
423
|
+
INSERT [dbo].[another_table] ([a], [b], [c], [d]) VALUES (5,6,7,8);
|
424
|
+
INSERT [dbo].[some_table_to_keep] ([a], [b], [c], [d]) VALUES (1,2,3,4);
|
425
|
+
INSERT [dbo].[some_table_to_keep] ([a], [b], [c], [d]) VALUES (5,6,7,8);
|
426
|
+
INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (N'hello',N'kjhjd^&dkjh', N'aawefjkafe');
|
427
|
+
INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (N'hello1',N'kjhj!', 892938);
|
428
|
+
INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (N'hello2',N'moose!!', NULL);
|
429
|
+
INSERT [dbo].[an_ignored_table] ([col], [col2]) VALUES (N'hello',N'kjhjd^&dkjh');
|
430
|
+
INSERT [dbo].[an_ignored_table] ([col], [col2]) VALUES (N'hello1',N'kjhj!');
|
431
|
+
INSERT [dbo].[an_ignored_table] ([col], [col2]) VALUES (N'hello2',N'moose!!');
|
432
|
+
SQL
|
433
|
+
|
434
|
+
@ddo = MyObfuscate.new({
|
435
|
+
:some_table => {
|
436
|
+
:email => {:type => :email, :skip_regexes => [/^[\w\.\_]+@honk\.com$/i, /^dontmurderme@direwolf.com$/]},
|
437
|
+
:name => {:type => :string, :length => 8, :chars => MyObfuscate::USERNAME_CHARS},
|
438
|
+
:age => {:type => :integer, :between => 10...80},
|
439
|
+
:bday => :keep
|
440
|
+
},
|
441
|
+
:another_table => :truncate,
|
442
|
+
:some_table_to_keep => :keep,
|
443
|
+
:one_more_table => {
|
444
|
+
# Note: fixed strings must be pre-SQL escaped!
|
445
|
+
:password => {:type => :fixed, :string => "monkey"},
|
446
|
+
:c => {:type => :null}
|
447
|
+
}
|
448
|
+
})
|
449
|
+
@ddo.database_type = :sql_server
|
450
|
+
|
451
|
+
@output = StringIO.new
|
452
|
+
$stderr = @error_output = StringIO.new
|
453
|
+
@ddo.obfuscate(@database_dump, @output)
|
454
|
+
$stderr = STDERR
|
455
|
+
@output.rewind
|
456
|
+
@output_string = @output.read
|
457
|
+
end
|
458
|
+
|
459
|
+
it "should be able to truncate tables" do
|
460
|
+
@output_string.should_not include("INSERT [dbo].[another_table]")
|
461
|
+
@output_string.should include("INSERT [dbo].[one_more_table]")
|
462
|
+
end
|
463
|
+
|
464
|
+
it "should be able to declare tables to keep" do
|
465
|
+
@output_string.should include("INSERT [dbo].[some_table_to_keep] ([a], [b], [c], [d]) VALUES (1,2,3,4);")
|
466
|
+
@output_string.should include("INSERT [dbo].[some_table_to_keep] ([a], [b], [c], [d]) VALUES (5,6,7,8);")
|
467
|
+
end
|
468
|
+
|
469
|
+
it "should ignore tables that it doesn't know about, but should warn" do
|
470
|
+
@output_string.should include("INSERT [dbo].[an_ignored_table] ([col], [col2]) VALUES (N'hello',N'kjhjd^&dkjh');")
|
471
|
+
@output_string.should include("INSERT [dbo].[an_ignored_table] ([col], [col2]) VALUES (N'hello1',N'kjhj!');")
|
472
|
+
@output_string.should include("INSERT [dbo].[an_ignored_table] ([col], [col2]) VALUES (N'hello2',N'moose!!');")
|
473
|
+
@error_output.rewind
|
474
|
+
@error_output.read.should =~ /an_ignored_table was not specified in the config/
|
475
|
+
end
|
476
|
+
|
477
|
+
it "should obfuscate the tables" do
|
478
|
+
@output_string.should include("INSERT [dbo].[some_table] ([email], [name], [something], [age], [bday]) VALUES (")
|
479
|
+
@output_string.should include("CAST(0x00009E1A00000000 AS DATETIME)")
|
480
|
+
@output_string.should include("INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (")
|
481
|
+
@output_string.should include("'some''thin,ge())lse1'")
|
482
|
+
@output_string.should include("INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (N'hello',N'monkey',NULL);")
|
483
|
+
@output_string.should include("INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (N'hello1',N'monkey',NULL);")
|
484
|
+
@output_string.should include("INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (N'hello2',N'monkey',NULL);")
|
485
|
+
@output_string.should_not include("INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (N'hello',N'kjhjd^&dkjh', N'aawefjkafe');")
|
486
|
+
@output_string.should_not include("INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (N'hello1',N'kjhj!', 892938);")
|
487
|
+
@output_string.should_not include("INSERT [dbo].[one_more_table] ([a], [password], [c], [d,d]) VALUES (N'hello2',N'moose!!', NULL);")
|
488
|
+
@output_string.should_not include("INSERT [dbo].[some_table] ([email], [name], [something], [age]) VALUES (N'bob@honk.com',N'bob', N'some''thin,ge())lse1', 25, CAST(0x00009E1A00000000 AS DATETIME));")
|
489
|
+
@output_string.should_not include("INSERT [dbo].[some_table] ([email], [name], [something], [age]) VALUES (N'joe@joe.com',N'joe', N'somethingelse2', 54, CAST(0x00009E1A00000000 AS DATETIME));")
|
490
|
+
end
|
491
|
+
|
492
|
+
it "honors a special case: on the people table, rows with anything@honk.com in a slot marked with :honk_email_skip do not change this slot" do
|
493
|
+
@output_string.should include("(N'bob@honk.com',")
|
494
|
+
@output_string.should include("(N'dontmurderme@direwolf.com',")
|
495
|
+
@output_string.should_not include("joe@joe.com")
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
context "when fail_on_unspecified_columns is set to true" do
|
500
|
+
before do
|
501
|
+
@database_dump = StringIO.new(<<-SQL)
|
502
|
+
INSERT INTO [dbo].[some_table] ([email], [name], [something], [age]) VALUES ('bob@honk.com','bob', 'some''thin,ge())lse1', 25);
|
503
|
+
SQL
|
504
|
+
|
505
|
+
@ddo = MyObfuscate.new({
|
506
|
+
:some_table => {
|
507
|
+
:email => {:type => :email, :skip_regexes => [/^[\w\.\_]+@honk\.com$/i, /^dontmurderme@direwolf.com$/]},
|
508
|
+
:name => {:type => :string, :length => 8, :chars => MyObfuscate::USERNAME_CHARS},
|
509
|
+
:age => {:type => :integer, :between => 10...80}
|
510
|
+
}
|
511
|
+
})
|
512
|
+
@ddo.database_type = :sql_server
|
513
|
+
@ddo.fail_on_unspecified_columns = true
|
514
|
+
end
|
515
|
+
|
516
|
+
it "should raise an exception when an unspecified column is found" do
|
517
|
+
lambda {
|
518
|
+
@ddo.obfuscate(@database_dump, StringIO.new)
|
519
|
+
}.should raise_error(/column 'something' defined/i)
|
520
|
+
end
|
521
|
+
|
522
|
+
it "should accept columns defined in globally_kept_columns" do
|
523
|
+
@ddo.globally_kept_columns = %w[something]
|
524
|
+
lambda {
|
525
|
+
@ddo.obfuscate(@database_dump, StringIO.new)
|
526
|
+
}.should_not raise_error
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
data/spec/mysql_spec.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MyObfuscate::Mysql do
|
4
|
+
describe "#parse_insert_statement" do
|
5
|
+
it "should return nil for other SQL syntaxes (MS SQL Server)" do
|
6
|
+
subject.parse_insert_statement("INSERT [dbo].[TASKS] ([TaskID], [TaskName]) VALUES (61, N'Report Thing')").should be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should return nil for MySQL non-insert statements" do
|
10
|
+
subject.parse_insert_statement("CREATE TABLE `some_table`;").should be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return a hash of table name, column names for MySQL insert statements" do
|
14
|
+
hash = subject.parse_insert_statement("INSERT INTO `some_table` (`email`, `name`, `something`, `age`) VALUES ('bob@honk.com','bob', 'some\\'thin,ge())lse1', 25),('joe@joe.com','joe', 'somethingelse2', 54);")
|
15
|
+
hash.should == {:table_name => :some_table, :column_names => [:email, :name, :something, :age]}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#rows_to_be_inserted" do
|
20
|
+
it "should split a mysql string into fields" do
|
21
|
+
string = "INSERT INTO `some_table` (thing1,thing2) VALUES ('bob@bob.com','bob', 'somethingelse1', 25, '2', 10, 'hi') ; "
|
22
|
+
fields = [['bob@bob.com', 'bob', 'somethingelse1', '25', '2', '10', "hi"]]
|
23
|
+
subject.rows_to_be_inserted(string).should == fields
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should work ok with escaped characters" do
|
27
|
+
string = "INSERT INTO `some_table` (thing1,thing2) VALUES ('bob,@bob.c , om', 'bo\\', b', 'some\"thin\\gel\\\\\\'se1', 25, '2', 10, 'hi', 5) ; "
|
28
|
+
fields = [['bob,@bob.c , om', 'bo\\\', b', 'some"thin\\gel\\\\\\\'se1', '25', '2', '10', "hi", "5"]]
|
29
|
+
subject.rows_to_be_inserted(string).should == fields
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should work with multiple subinserts" do
|
33
|
+
string = "INSERT INTO `some_table` (thing1,thing2) VALUES (1,2,3, '((m))(oo()s,e'), ('bob,@bob.c , om', 'bo\\', b', 'some\"thin\\gel\\\\\\'se1', 25, '2', 10, 'hi', 5) ;"
|
34
|
+
fields = [["1", "2", "3", "((m))(oo()s,e"], ['bob,@bob.c , om', 'bo\\\', b', 'some"thin\\gel\\\\\\\'se1', '25', '2', '10', "hi", "5"]]
|
35
|
+
subject.rows_to_be_inserted(string).should == fields
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should work ok with NULL values" do
|
39
|
+
string = "INSERT INTO `some_table` (thing1,thing2) VALUES (NULL , 'bob@bob.com','bob', NULL, 25, '2', NULL, 'hi', NULL ); "
|
40
|
+
fields = [[nil, 'bob@bob.com', 'bob', nil, '25', '2', nil, "hi", nil]]
|
41
|
+
subject.rows_to_be_inserted(string).should == fields
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should work with empty strings" do
|
45
|
+
string = "INSERT INTO `some_table` (thing1,thing2) VALUES (NULL , '', '' , '', 25, '2','', 'hi','') ;"
|
46
|
+
fields = [[nil, '', '', '', '25', '2', '', "hi", '']]
|
47
|
+
subject.rows_to_be_inserted(string).should == fields
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require(:default, :development)
|
4
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
require 'my_obfuscate'
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
# config.mock_with :rr
|
10
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MyObfuscate::SqlServer do
|
4
|
+
describe "#parse_insert_statement" do
|
5
|
+
it "should return a hash of table_name, column_names for SQL Server input statements" do
|
6
|
+
hash = subject.parse_insert_statement("INSERT [dbo].[TASKS] ([TaskID], [TaskName]) VALUES (61, N'Report Thing')")
|
7
|
+
hash.should == { :table_name => :TASKS, :column_names => [:TaskID, :TaskName] }
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return nil for SQL Server non-insert statements" do
|
11
|
+
subject.parse_insert_statement("CREATE TABLE [dbo].[WORKFLOW](").should be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return nil for non-SQL Server insert statements (MySQL)" do
|
15
|
+
subject.parse_insert_statement("INSERT INTO `some_table` (`email`, `name`, `something`, `age`) VALUES ('bob@honk.com','bob', 'some\\'thin,ge())lse1', 25),('joe@joe.com','joe', 'somethingelse2', 54);").should be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#rows_to_be_inserted" do
|
20
|
+
it "should split a SQL Server string into fields" do
|
21
|
+
string = "INSERT [dbo].[some_table] ([thing1],[thing2]) VALUES (N'bob@bob.com',N'bob', N'somethingelse1',25, '2', 10, 'hi', CAST(0x00009E1A00000000 AS DATETIME)) ; "
|
22
|
+
fields = [['bob@bob.com', 'bob', 'somethingelse1', '25', '2', '10', "hi", "CAST(0x00009E1A00000000 AS DATETIME)"]]
|
23
|
+
subject.rows_to_be_inserted(string).should == fields
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should work ok with single quote escape" do
|
27
|
+
string = "INSERT [dbo].[some_table] ([thing1],[thing2]) VALUES (N'bob,@bob.c , om', 'bo'', b', N'some\"thingel''se1', 25, '2', 10, 'hi', 5) ; "
|
28
|
+
fields = [['bob,@bob.c , om', "bo'', b", "some\"thingel''se1", '25', '2', '10', "hi", "5"]]
|
29
|
+
subject.rows_to_be_inserted(string).should == fields
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should work ok with NULL values" do
|
33
|
+
string = "INSERT [dbo].[some_table] ([thing1],[thing2]) VALUES (NULL , N'bob@bob.com','bob', NULL, 25, N'2', NULL, 'hi', NULL ); "
|
34
|
+
fields = [[nil, 'bob@bob.com', 'bob', nil, '25', '2', nil, "hi", nil]]
|
35
|
+
subject.rows_to_be_inserted(string).should == fields
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should work with empty strings" do
|
39
|
+
string = "INSERT [dbo].[some_table] ([thing1],[thing2]) VALUES (NULL , N'', '' , '', 25, '2','', N'hi','') ;"
|
40
|
+
fields = [[nil, '', '','', '25', '2', '', "hi", '']]
|
41
|
+
subject.rows_to_be_inserted(string).should == fields
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#make_valid_value_string" do
|
46
|
+
it "should output 'NULL' when the value is nil" do
|
47
|
+
subject.make_valid_value_string(nil).should == "NULL"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should enclose the value in quotes if it's a string" do
|
51
|
+
subject.make_valid_value_string("something").should == "N'something'"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not enclose the value in quotes if it is a method call" do
|
55
|
+
subject.make_valid_value_string("CAST(0x00009E1A00000000 AS DATETIME)").should == "CAST(0x00009E1A00000000 AS DATETIME)"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iterationlabs-my_obfuscate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Cantino
|
9
|
+
- Dave Willett
|
10
|
+
- Mike Grafton
|
11
|
+
- Mason Glaves
|
12
|
+
- Greg Bell
|
13
|
+
- Mavenlink
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
date: 2012-04-10 00:00:00.000000000 Z
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: rspec
|
21
|
+
requirement: !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
type: :development
|
28
|
+
prerelease: false
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ! '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: faker
|
37
|
+
requirement: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - '='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.9.5
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - '='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 0.9.5
|
51
|
+
description: Standalone Ruby code for the selective rewriting of MySQL dumps in order
|
52
|
+
to protect user privacy.
|
53
|
+
email: andrew@iterationlabs.com
|
54
|
+
executables: []
|
55
|
+
extensions: []
|
56
|
+
extra_rdoc_files: []
|
57
|
+
files:
|
58
|
+
- .gitignore
|
59
|
+
- Gemfile
|
60
|
+
- LICENSE
|
61
|
+
- README.rdoc
|
62
|
+
- Rakefile
|
63
|
+
- iterationlabs-my_obfuscate.gemspec
|
64
|
+
- lib/my_obfuscate.rb
|
65
|
+
- lib/my_obfuscate/mysql.rb
|
66
|
+
- lib/my_obfuscate/sql_server.rb
|
67
|
+
- lib/my_obfuscate/version.rb
|
68
|
+
- spec/my_obfuscate_spec.rb
|
69
|
+
- spec/mysql_spec.rb
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
- spec/sql_server_spec.rb
|
72
|
+
homepage: http://github.com/iterationlabs/myobfuscate
|
73
|
+
licenses: []
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.8.21
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Standalone Ruby code for the selective rewriting of MySQL dumps in order
|
96
|
+
to protect user privacy.
|
97
|
+
test_files: []
|