omelettes 0.2.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.
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +10 -0
- data/init.rb +1 -0
- data/lib/generators/omelettes/config/USAGE +2 -0
- data/lib/generators/omelettes/config/config_generator.rb +11 -0
- data/lib/generators/omelettes/config/templates/omelettes.rb +30 -0
- data/lib/omelettes/column.rb +48 -0
- data/lib/omelettes/model_additions.rb +45 -0
- data/lib/omelettes/obfuscate.rb +64 -0
- data/lib/omelettes/railtie.rb +10 -0
- data/lib/omelettes/words.rb +36 -0
- data/lib/omelettes.rb +19 -0
- data/lib/tasks/omelettes.rake +10 -0
- data/spec/README.rdoc +10 -0
- data/spec/omelettes/column_spec.rb +53 -0
- data/spec/omelettes/model_additions_spec.rb +62 -0
- data/spec/omelettes/obfuscate_spec.rb +113 -0
- data/spec/omelettes/words_spec.rb +70 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +24 -0
- metadata +107 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Mark Sim
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Omelettes #
|
2
|
+
|
3
|
+
## Basic Usage ##
|
4
|
+
|
5
|
+
gem install omelettes
|
6
|
+
rails g omelettes:config
|
7
|
+
rake db:cook
|
8
|
+
|
9
|
+
## Configuration ##
|
10
|
+
|
11
|
+
After running 'rails g omelettes:config' you will find a config file in 'config/initializers/omelettes.rb'
|
12
|
+
|
13
|
+
Omelettes.setup do |config|
|
14
|
+
# Ignore tables or columns using strings or regular expressions
|
15
|
+
config.ignore_tables = ["schema_migrations", "my_reporting_table", /(a-z_)user/]
|
16
|
+
config.ignore_columns = [/(a-z_)*type/i, "name", "city"]
|
17
|
+
|
18
|
+
# Override non-standard table names => classes like this
|
19
|
+
# config.models['admin_users'] = Admin::User
|
20
|
+
|
21
|
+
# Override model specific columns
|
22
|
+
|
23
|
+
User.treat(:nickname).as(:name)
|
24
|
+
Person.scramble(:street) {|value| "#{value.reverse}"}
|
25
|
+
|
26
|
+
# Freeze / Ignore columns on specific models
|
27
|
+
User.harden(:password)
|
28
|
+
|
29
|
+
# To make a normally "Faker" obfuscated column behave normally, override with :omelette
|
30
|
+
User.scramble(:name).as(:omelette)
|
31
|
+
|
32
|
+
# To override the model associated with a specific table, make sure and specify it here:
|
33
|
+
config.models['logins'] = User
|
34
|
+
end
|
35
|
+
|
36
|
+
By default, the following columns will be Faker-ified (replaced with faker info RATHER than obfuscated using omelettes same-length-and-initial-character)
|
37
|
+
|
38
|
+
* name
|
39
|
+
* first_name
|
40
|
+
* last_name
|
41
|
+
* city
|
42
|
+
* state
|
43
|
+
* country
|
44
|
+
* street_address
|
45
|
+
* street_name
|
46
|
+
* zip_code
|
47
|
+
* company_name
|
48
|
+
* company
|
49
|
+
* email
|
50
|
+
* user_name
|
51
|
+
* phone
|
52
|
+
|
53
|
+
And the following can be used to 'treat' a column 'as':
|
54
|
+
* paragraph
|
55
|
+
* paragraphs
|
56
|
+
* sentence
|
57
|
+
* sentences
|
58
|
+
* words
|
59
|
+
|
60
|
+
You can also keep model-level configurations within the model itself
|
61
|
+
|
62
|
+
class User < ActiveRecord::Base
|
63
|
+
treat(:login).as(:user_name)
|
64
|
+
ignore :password
|
65
|
+
end
|
66
|
+
|
67
|
+
## About ##
|
68
|
+
|
69
|
+
omelettets is obfuscated 'obfuscate'. Omelettes takes strings in your database and replaces them with worlds of the same length and same initial letter.
|
data/Rakefile
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'omelettes'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Omelettes Obfuscation Setup
|
2
|
+
#
|
3
|
+
# Columns with the following names will be automatically Faker-ified.
|
4
|
+
# You can make columns with other names Faker-ified by overriding the model
|
5
|
+
# specific columns a la: User.scramble(:nickname).as(:first_name)
|
6
|
+
#
|
7
|
+
# :name, :first_name, :last_name
|
8
|
+
# :city, :state, :country, :street_address, :street_name, :zip_code
|
9
|
+
# :company_name, :company, :email, :user_name, :phone
|
10
|
+
# :paragraph, :paragraphs, :sentence, :sentences, :words
|
11
|
+
|
12
|
+
Omelettes.setup do |config|
|
13
|
+
# Ignore tables or columns using strings or regular expressions
|
14
|
+
# config.ignore_tables = ["schema_migrations", "my_reporting_table", /(a-z_)user/]
|
15
|
+
# config.ignore_columns = [/(a-z_)*type/i, "name", "city"]
|
16
|
+
|
17
|
+
config.ignore_columns = [/[a-z_]*type/i, /[a-z_]*password[a-z_]*/i]
|
18
|
+
config.ignore_tables = ['schema_migrations']
|
19
|
+
|
20
|
+
# Override non-standard table names => classes like this
|
21
|
+
# config.models['admin_users'] = Admin::User
|
22
|
+
|
23
|
+
# Override model specific columns
|
24
|
+
# User.scramble(:nickname).as(:name)
|
25
|
+
# Person.scramble(:street) {|value| "#{value.reverse}"}
|
26
|
+
# Freeze / Ignore columns on specific models
|
27
|
+
# User.harden(:password)
|
28
|
+
# To make a normally "Faker" obfuscated column behave normally, override with :omelette
|
29
|
+
# User.scramble(:name).as(:omelette)
|
30
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'faker'
|
2
|
+
|
3
|
+
module Omelettes
|
4
|
+
class Column
|
5
|
+
attr_accessor :name, :style, :custom_block
|
6
|
+
def initialize(name, style=nil, &block)
|
7
|
+
@name = name
|
8
|
+
@style = style
|
9
|
+
@custom_block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
def process(string)
|
13
|
+
if @custom_block
|
14
|
+
return @custom_block.call(string)
|
15
|
+
else
|
16
|
+
Column.default(@style || @name, string)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.default(name, value)
|
21
|
+
name = name.downcase.to_sym
|
22
|
+
case name
|
23
|
+
when :hardened
|
24
|
+
return value
|
25
|
+
when :name, :first_name, :last_name
|
26
|
+
return Faker::Name.send(name)
|
27
|
+
when :city, :state, :country, :street_address, :street_name, :zip_code
|
28
|
+
return Faker::Address.send(name)
|
29
|
+
when :company_name, :company
|
30
|
+
return Faker::Company.name
|
31
|
+
when :email, :user_name
|
32
|
+
return Faker::Internet.send(name)
|
33
|
+
when :paragraph, :paragraphs, :sentence, :sentences, :words
|
34
|
+
return Faker::Lorem.send(name)
|
35
|
+
when :phone
|
36
|
+
return Faker::PhoneNumber.phone_number
|
37
|
+
else
|
38
|
+
return Omelettes::Obfuscate.obfuscate(value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def as(style)
|
43
|
+
@style = style unless @style == :hardened
|
44
|
+
self
|
45
|
+
end
|
46
|
+
alias :like :as
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Omelettes
|
2
|
+
module ModelAdditions
|
3
|
+
module ClassMethods
|
4
|
+
def column_config(column_name)
|
5
|
+
@column_config ||= {}
|
6
|
+
@column_config[column_name.to_s]
|
7
|
+
end
|
8
|
+
|
9
|
+
def treat(column_name, style=nil, &block)
|
10
|
+
@column_config ||= {}
|
11
|
+
column = Column.new(column_name, style, &block)
|
12
|
+
@column_config[column_name.to_s] = column
|
13
|
+
column
|
14
|
+
end
|
15
|
+
alias :scramble :treat
|
16
|
+
|
17
|
+
def ignore(column_name)
|
18
|
+
@column_config ||= {}
|
19
|
+
column = Column.new(column_name, :hardened)
|
20
|
+
@column_config[column_name.to_s] = column
|
21
|
+
column
|
22
|
+
end
|
23
|
+
alias :harden :ignore
|
24
|
+
end
|
25
|
+
|
26
|
+
def obfuscate(column_name)
|
27
|
+
column = self.class.column_config(column_name)
|
28
|
+
original_value = self.send(column_name)
|
29
|
+
if column
|
30
|
+
value = column.process(original_value)
|
31
|
+
else
|
32
|
+
value = Column.default(column_name, original_value)
|
33
|
+
end
|
34
|
+
self.update_attribute(column_name, value) if original_value != value
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.included(base)
|
38
|
+
base.extend ClassMethods
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ActiveRecord::Base.class_eval do
|
44
|
+
include Omelettes::ModelAdditions
|
45
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Omelettes
|
2
|
+
class Obfuscate
|
3
|
+
class << self
|
4
|
+
def cook(silent=false)
|
5
|
+
total_tables = 0
|
6
|
+
total_attributes = 0
|
7
|
+
Words.load(word_list || "/usr/share/dict/words")
|
8
|
+
tables.each do |table|
|
9
|
+
next if ignore_table?(table)
|
10
|
+
print "\nProcessing #{model(table).name}" unless silent
|
11
|
+
model(table).find_each do |object|
|
12
|
+
model(table).columns.each do |column|
|
13
|
+
next if ignore_column?(column.name) || column.type != :string
|
14
|
+
object.obfuscate(column.name)
|
15
|
+
total_attributes += 1
|
16
|
+
end
|
17
|
+
print "." unless silent
|
18
|
+
end
|
19
|
+
total_tables += 1
|
20
|
+
end
|
21
|
+
print "\n" unless silent
|
22
|
+
[total_tables, total_attributes]
|
23
|
+
end
|
24
|
+
|
25
|
+
def tables
|
26
|
+
ActiveRecord::Base.connection.tables
|
27
|
+
end
|
28
|
+
|
29
|
+
def model(table)
|
30
|
+
self.models ||= {}
|
31
|
+
self.models[table] ||= table.camelcase.singularize.constantize
|
32
|
+
self.models[table]
|
33
|
+
end
|
34
|
+
|
35
|
+
def ignore_table?(table)
|
36
|
+
ignore_tables.each do |ignore|
|
37
|
+
return true if table.match(ignore).to_s == table
|
38
|
+
end
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def ignore_column?(column)
|
43
|
+
ignore_columns.each do |ignore|
|
44
|
+
return true if column.match(ignore).to_s == column
|
45
|
+
end
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def obfuscate(string)
|
50
|
+
return nil if string.nil?
|
51
|
+
result = []
|
52
|
+
string.split(/(\s+)|([[:punct:]])/).each do |word|
|
53
|
+
result << (word.match(/[a-zA-Z]+/).nil? ? word : Words.replace(word))
|
54
|
+
end
|
55
|
+
result.join("")
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_accessor :ignore_tables
|
59
|
+
attr_accessor :ignore_columns
|
60
|
+
attr_accessor :word_list
|
61
|
+
attr_accessor :models
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Omelettes
|
2
|
+
class Words
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def word_hash
|
6
|
+
@word_hash ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(word)
|
10
|
+
key = "#{word[0].downcase}#{word.length}"
|
11
|
+
@word_hash[key] ||= []
|
12
|
+
@word_hash[key] << word
|
13
|
+
end
|
14
|
+
|
15
|
+
def replace(word)
|
16
|
+
key = "#{word[0].downcase}#{word.length}"
|
17
|
+
valid_words = (@word_hash[key] || [])
|
18
|
+
new_word = valid_words[rand(valid_words.size)]
|
19
|
+
return new_word.send(word[0].upcase == word[0] ? :capitalize : :downcase) unless new_word.nil?
|
20
|
+
word
|
21
|
+
end
|
22
|
+
|
23
|
+
def load(path="/usr/share/dict/words")
|
24
|
+
clear
|
25
|
+
(path.is_a?(Array) ? path : File.readlines(path)).each do |word|
|
26
|
+
add(word.strip)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear
|
31
|
+
@word_hash = {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/omelettes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_record'
|
3
|
+
rescue LoadError
|
4
|
+
require 'activerecord' unless defined?(ActiveRecord)
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'omelettes/column'
|
8
|
+
require 'omelettes/model_additions'
|
9
|
+
require 'omelettes/obfuscate'
|
10
|
+
require 'omelettes/words'
|
11
|
+
|
12
|
+
module Omelettes
|
13
|
+
require 'omelettes/railtie' if defined?(Rails)
|
14
|
+
|
15
|
+
def self.setup
|
16
|
+
Omelettes::Obfuscate.models = {}
|
17
|
+
yield Omelettes::Obfuscate
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
namespace :db do
|
2
|
+
desc "Obfuscate the database with Omelettes"
|
3
|
+
task :cook => :environment do
|
4
|
+
print "Are you sure you want to scramble all strings in the database? (y/n): "
|
5
|
+
input = $stdin.gets.strip
|
6
|
+
if input == "y"
|
7
|
+
Omelettes::Obfuscate.cook
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/spec/README.rdoc
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Omelettes::Column do
|
4
|
+
describe "creation" do
|
5
|
+
it "accepts a name" do
|
6
|
+
c = nil
|
7
|
+
lambda { c = Omelettes::Column.new(:name) }.should_not raise_error
|
8
|
+
c.name.should == :name
|
9
|
+
end
|
10
|
+
|
11
|
+
it "accepts a name and an optional style" do
|
12
|
+
c = nil
|
13
|
+
lambda { c = Omelettes::Column.new(:name, :city) }.should_not raise_error
|
14
|
+
c.name.should == :name
|
15
|
+
c.style.should == :city
|
16
|
+
end
|
17
|
+
|
18
|
+
it "accepts a block" do
|
19
|
+
c = Omelettes::Column.new(:name) {|value| value.reverse}
|
20
|
+
c.custom_block.should_not be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "allows 'as' or 'like' syntax to alter the style" do
|
24
|
+
Omelettes::Column.new(:name).as(:test).style.should == :test
|
25
|
+
Omelettes::Column.new(:value).like(:key).style.should == :key
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "process" do
|
30
|
+
it "executes a block if present" do
|
31
|
+
c = Omelettes::Column.new(:name) {|value| value.reverse}
|
32
|
+
c.process("eggs").should == "sgge"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "calls the default processing method if no block present" do
|
36
|
+
c = Omelettes::Column.new(:name)
|
37
|
+
Faker::Name.should_receive(:name).once
|
38
|
+
c.process("Mark")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "calls the passed 'style' if passed, overriding the name" do
|
42
|
+
c = Omelettes::Column.new(:name).as(:paragraph)
|
43
|
+
Faker::Lorem.should_receive(:paragraph).once
|
44
|
+
c.process("Some stuff")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "calls the standard obfuscation method if the style is not recognized" do
|
48
|
+
c = Omelettes::Column.new(:name).as(:omelettes)
|
49
|
+
Omelettes::Obfuscate.should_receive(:obfuscate).once
|
50
|
+
c.process("Some Stuff")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Model Additions" do
|
4
|
+
describe "treat / scramble" do
|
5
|
+
it "should create a column with a specified style for a model" do
|
6
|
+
User.treat(:middle_name).as(:first_name)
|
7
|
+
User.column_config(:middle_name).style.should == :first_name
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should create a column with a custom block specified for a model" do
|
11
|
+
User.scramble(:middle_name) {|value| value.reverse}
|
12
|
+
User.column_config(:middle_name).custom_block.should_not be_nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "ignore / harden" do
|
17
|
+
it "should create a column with a style of ':hardened'" do
|
18
|
+
User.ignore(:middle_name)
|
19
|
+
User.column_config(:middle_name).style.should == :hardened
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should not be overwritten with 'as'" do
|
23
|
+
User.ignore(:middle_name).as(:first_name)
|
24
|
+
User.column_config(:middle_name).style.should == :hardened
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "obfuscate" do
|
29
|
+
before(:each) do
|
30
|
+
@user = User.new
|
31
|
+
@user.stub(:first_name).and_return("Ed")
|
32
|
+
@user.stub(:middle_name).and_return("Jeremy")
|
33
|
+
@user.stub(:last_name).and_return("Stevens")
|
34
|
+
@user.stub(:city).and_return("Stuckeyville")
|
35
|
+
User.stub(:find_each).and_return([@user])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should process the column result back to the database" do
|
39
|
+
User.treat(:middle_name).as(:first_name)
|
40
|
+
@user.should_receive(:update_attribute).once
|
41
|
+
@user.obfuscate(:middle_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should not write anything to the database if the column is ignored" do
|
45
|
+
User.ignore(:city)
|
46
|
+
@user.should_not_receive(:update_attribute)
|
47
|
+
@user.obfuscate(:city)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should call the passed block and write the result to the database" do
|
51
|
+
User.treat(:last_name) {|value| "Vessy"}
|
52
|
+
@user.should_receive(:update_attribute).with(:last_name, "Vessy").once
|
53
|
+
@user.obfuscate(:last_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should call Faker for default column names, even if not explicitly configured" do
|
57
|
+
Faker::Name.should_receive(:first_name).once
|
58
|
+
@user.should_receive(:update_attribute).once
|
59
|
+
@user.obfuscate(:first_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Omelettes::Obfuscate do
|
4
|
+
|
5
|
+
describe "models" do
|
6
|
+
it "allows for override" do
|
7
|
+
Omelettes.setup do |config|
|
8
|
+
config.models['logins'] = User
|
9
|
+
end
|
10
|
+
|
11
|
+
Omelettes::Obfuscate.model("logins").should == User
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns the model" do
|
15
|
+
Omelettes::Obfuscate.model("users").should == User
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "ignore tables" do
|
20
|
+
it "ignores matching strings" do
|
21
|
+
Omelettes.setup do |config|
|
22
|
+
config.ignore_tables = ["alpha", "beta", "kappa"]
|
23
|
+
end
|
24
|
+
|
25
|
+
Omelettes::Obfuscate.ignore_table?("alpha").should be_true
|
26
|
+
Omelettes::Obfuscate.ignore_table?("beta").should be_true
|
27
|
+
Omelettes::Obfuscate.ignore_table?("kappa").should be_true
|
28
|
+
Omelettes::Obfuscate.ignore_table?("delta").should be_false
|
29
|
+
end
|
30
|
+
|
31
|
+
it "ignores matching regex" do
|
32
|
+
Omelettes.setup do |config|
|
33
|
+
config.ignore_tables = [/[a-z_]*type/i]
|
34
|
+
end
|
35
|
+
|
36
|
+
Omelettes::Obfuscate.ignore_table?("type").should be_true
|
37
|
+
Omelettes::Obfuscate.ignore_table?("my_favorite_type").should be_true
|
38
|
+
Omelettes::Obfuscate.ignore_table?("matype").should be_true
|
39
|
+
Omelettes::Obfuscate.ignore_table?("typedef").should be_false
|
40
|
+
Omelettes::Obfuscate.ignore_table?("another").should be_false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "ignore columns" do
|
45
|
+
it "ignores matching strings" do
|
46
|
+
Omelettes.setup do |config|
|
47
|
+
config.ignore_columns = ["alpha", "beta", "kappa"]
|
48
|
+
end
|
49
|
+
|
50
|
+
Omelettes::Obfuscate.ignore_column?("alpha").should be_true
|
51
|
+
Omelettes::Obfuscate.ignore_column?("beta").should be_true
|
52
|
+
Omelettes::Obfuscate.ignore_column?("kappa").should be_true
|
53
|
+
Omelettes::Obfuscate.ignore_column?("delta").should be_false
|
54
|
+
end
|
55
|
+
|
56
|
+
it "ignores matching regex" do
|
57
|
+
Omelettes.setup do |config|
|
58
|
+
config.ignore_columns = [/[a-z_]*type/i]
|
59
|
+
end
|
60
|
+
|
61
|
+
Omelettes::Obfuscate.ignore_column?("type").should be_true
|
62
|
+
Omelettes::Obfuscate.ignore_column?("my_favorite_type").should be_true
|
63
|
+
Omelettes::Obfuscate.ignore_column?("matype").should be_true
|
64
|
+
Omelettes::Obfuscate.ignore_column?("typedef").should be_false
|
65
|
+
Omelettes::Obfuscate.ignore_column?("another").should be_false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "obfuscate" do
|
70
|
+
it "returns nil if passed nil" do
|
71
|
+
Omelettes::Obfuscate.obfuscate(nil).should be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it "replaces words" do
|
75
|
+
Omelettes::Words.clear
|
76
|
+
["barfs", "foo", "queen", "tad"].each do |word|
|
77
|
+
Omelettes::Words.add(word)
|
78
|
+
end
|
79
|
+
Omelettes::Obfuscate.obfuscate("the quick brown fox").should == "tad queen barfs foo"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "preserves punctuation" do
|
83
|
+
Omelettes::Words.clear
|
84
|
+
["barfs", "foo", "queen", "tad"].each do |word|
|
85
|
+
Omelettes::Words.add(word)
|
86
|
+
end
|
87
|
+
Omelettes::Obfuscate.obfuscate("the: quick, brown! fox :)").should == "tad: queen, barfs! foo :)"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "cook" do
|
92
|
+
before(:each) do
|
93
|
+
Omelettes.setup do |config|
|
94
|
+
config.word_list = %w(barfs foo queen tad pole)
|
95
|
+
end
|
96
|
+
|
97
|
+
Omelettes::Obfuscate.stub(:tables).and_return(["users"])
|
98
|
+
|
99
|
+
User.stub(:find_each).and_return(User.new(:first_name => "Mark", :last_name => "Sim", :city => "Austin"))
|
100
|
+
end
|
101
|
+
|
102
|
+
it "ignores specified tables" do
|
103
|
+
Omelettes::Obfuscate.ignore_tables = ["users"]
|
104
|
+
Omelettes::Obfuscate.cook(true).should == [0, 0]
|
105
|
+
end
|
106
|
+
|
107
|
+
it "ignores specified columns" do
|
108
|
+
Omelettes::Obfuscate.ignore_tables = []
|
109
|
+
Omelettes::Obfuscate.ignore_columns = ['first_name', 'middle_name', 'last_name', 'city']
|
110
|
+
Omelettes::Obfuscate.cook(true).should == [1, 0]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Omelettes::Words do
|
4
|
+
describe "load" do
|
5
|
+
it "loads from the default path" do
|
6
|
+
Omelettes::Words.clear
|
7
|
+
Omelettes::Words.word_hash.should == {}
|
8
|
+
Omelettes::Words.load
|
9
|
+
Omelettes::Words.word_hash.should_not == {}
|
10
|
+
Omelettes::Words.word_hash.should_not be_nil
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
it "optionally accepts a load path" do
|
15
|
+
File.should_receive(:readlines).once.and_return(["one"])
|
16
|
+
Omelettes::Words.load("test.path")
|
17
|
+
Omelettes::Words.word_hash.should == {"o3" => ["one"]}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "add" do
|
22
|
+
before(:each) do
|
23
|
+
Omelettes::Words.clear
|
24
|
+
end
|
25
|
+
it "adds a word to the values of the word_hash" do
|
26
|
+
Omelettes::Words.add("fate")
|
27
|
+
Omelettes::Words.word_hash.values.flatten.should include("fate")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "indexes based on word length and first letter" do
|
31
|
+
Omelettes::Words.add("tony")
|
32
|
+
Omelettes::Words.word_hash["t4"].should include("tony")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "does not care the case" do
|
36
|
+
Omelettes::Words.add("FaT")
|
37
|
+
Omelettes::Words.word_hash["f3"].should include("FaT")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "replace" do
|
42
|
+
before(:each) do
|
43
|
+
Omelettes::Words.clear
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns a word of the same length" do
|
47
|
+
Omelettes::Words.add("bra")
|
48
|
+
Omelettes::Words.add("brak")
|
49
|
+
Omelettes::Words.add("brake")
|
50
|
+
|
51
|
+
Omelettes::Words.replace("bat").should == "bra"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "returns a word starting with the same letter" do
|
55
|
+
Omelettes::Words.add("abc")
|
56
|
+
Omelettes::Words.add("bcd")
|
57
|
+
Omelettes::Words.add("cde")
|
58
|
+
|
59
|
+
Omelettes::Words.replace("att").should == "abc"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns itself if there is no word in the dictionary matching the first letter and length" do
|
63
|
+
Omelettes::Words.add("bra")
|
64
|
+
Omelettes::Words.add("cake")
|
65
|
+
Omelettes::Words.add("brake")
|
66
|
+
|
67
|
+
Omelettes::Words.replace("bork").should == "bork"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'omelettes'
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
end
|
6
|
+
|
7
|
+
class TablelessModel < ActiveRecord::Base
|
8
|
+
def self.columns() @columns ||= []; end
|
9
|
+
|
10
|
+
def self.column(name, sql_type = nil, default = nil, null = true)
|
11
|
+
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class User < TablelessModel
|
17
|
+
column :id, :integer
|
18
|
+
column :first_name, :string
|
19
|
+
column :middle_name, :string
|
20
|
+
column :last_name, :string
|
21
|
+
column :city, :string
|
22
|
+
column :age, :integer
|
23
|
+
end
|
24
|
+
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: omelettes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mark Sim
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-07-27 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.1.0
|
24
|
+
type: :development
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: faker
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.9.5
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: activerecord
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "3.0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id003
|
48
|
+
description: Low-to-no configuration solution for obfuscating sensitive data in your database.
|
49
|
+
email: mark@quarternotecoda.com
|
50
|
+
executables: []
|
51
|
+
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files: []
|
55
|
+
|
56
|
+
files:
|
57
|
+
- lib/generators/omelettes/config/config_generator.rb
|
58
|
+
- lib/generators/omelettes/config/templates/omelettes.rb
|
59
|
+
- lib/generators/omelettes/config/USAGE
|
60
|
+
- lib/omelettes/column.rb
|
61
|
+
- lib/omelettes/model_additions.rb
|
62
|
+
- lib/omelettes/obfuscate.rb
|
63
|
+
- lib/omelettes/railtie.rb
|
64
|
+
- lib/omelettes/words.rb
|
65
|
+
- lib/omelettes.rb
|
66
|
+
- lib/tasks/omelettes.rake
|
67
|
+
- spec/omelettes/column_spec.rb
|
68
|
+
- spec/omelettes/model_additions_spec.rb
|
69
|
+
- spec/omelettes/obfuscate_spec.rb
|
70
|
+
- spec/omelettes/words_spec.rb
|
71
|
+
- spec/README.rdoc
|
72
|
+
- spec/spec.opts
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- Gemfile
|
75
|
+
- LICENSE
|
76
|
+
- Rakefile
|
77
|
+
- README.md
|
78
|
+
- init.rb
|
79
|
+
homepage: http://github.com/marksim/omelettes
|
80
|
+
licenses: []
|
81
|
+
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: "0"
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 1.3.4
|
99
|
+
requirements: []
|
100
|
+
|
101
|
+
rubyforge_project: omelettes
|
102
|
+
rubygems_version: 1.8.4
|
103
|
+
signing_key:
|
104
|
+
specification_version: 3
|
105
|
+
summary: Database obfuscation.
|
106
|
+
test_files: []
|
107
|
+
|