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