Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to obfuscate fields #3

Merged
merged 2 commits into from Aug 7, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Expand Up @@ -98,6 +98,22 @@ ON DUPLICATE KEY UPDATE id = VALUES(id), name = VALUES(name)

ps: this is a MySQL only implementation (PRs are welcome)

### Sensitive Fields
You can use the `obfuscate` option to obfuscate sensitive fields like emails or
user logins.

```ruby
Polo::Traveler.collect(AR::Chef, 1).translate(obfuscate: [:email])
```

```sql
INSERT INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', 'eahorctmaagfo.nitm@l')
```

Warning: This is not a security feature. Fields can still easily be rearranged back to their original format. Polo will simply scramble the order of strings so you don't accidentaly end up causing side effects when using production data in development.

It is not a good practice to use highly sensitive data in development.

## Installation

Add this line to your application's Gemfile:
Expand Down
4 changes: 2 additions & 2 deletions lib/polo.rb
Expand Up @@ -6,7 +6,7 @@ module Polo

class Traveler

def self.collect(base_class, id, dependencies)
def self.collect(base_class, id, dependencies={})
selects = Collector.new(base_class, id, dependencies).collect
new(selects)
end
Expand Down Expand Up @@ -53,6 +53,6 @@ def translate(options={})
# [ :books, { author: :avatar } ]
#
def self.explore(base_class, id, dependencies={})
Traveler.new.collect(base_class, id, dependencies).translate
Traveler.collect(base_class, id, dependencies).translate
end
end
22 changes: 21 additions & 1 deletion lib/polo/translator.rb
Expand Up @@ -15,11 +15,31 @@ def initialize(selects, options={})
# Public: Translates SELECT queries into INSERTS.
#
def translate
SqlTranslator.new(instances, @options).to_sql.uniq
end

def instances
active_record_instances = @selects.flat_map do |select|
select[:klass].find_by_sql(select[:sql]).to_a
end

SqlTranslator.new(active_record_instances, @options).to_sql.uniq
if fields = @options[:obfuscate] and fields.is_a?(Array)
obfuscate!(active_record_instances, fields.map(&:to_s))
end

active_record_instances
end

private

def obfuscate!(instances, fields)
instances.each do |instance|
next if (instance.attributes.keys & fields).empty?
fields.each do |field|
value = instance.attributes[field] || ''
instance.send("#{field}=", value.split('').shuffle.join)
end
end
end
end
end
16 changes: 15 additions & 1 deletion spec/polo_spec.rb
Expand Up @@ -8,7 +8,7 @@

it 'generates an insert query for the base object' do
exp = Polo.explore(AR::Chef, 1)
insert = "INSERT INTO `chefs` (`id`, `name`) VALUES (1, 'Netto')"
insert = "INSERT INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', 'nettofarah@gmail.com')"
expect(exp).to include(insert)
end

Expand Down Expand Up @@ -50,4 +50,18 @@
expect(inserts).to include(many_to_many_insert)
end
end

describe "Advanced Options" do
describe 'obfuscate: [fields]' do

it 'scrambles a predefined field' do
exp = Polo::Traveler.collect(AR::Chef, 1).translate(obfuscate: [:email])
insert = /INSERT INTO `chefs` \(`id`, `name`, `email`\) VALUES \(1, 'Netto', (.+)\)/
scrambled_email = insert.match(exp.first)[1]

expect(scrambled_email).to_not eq('nettofarah@gmail.com')
expect(insert).to match(exp.first)
end
end
end
end
6 changes: 3 additions & 3 deletions spec/sql_translator_spec.rb
Expand Up @@ -11,15 +11,15 @@
end

it 'translates records to inserts' do
insert_netto = [%q{INSERT INTO `chefs` (`id`, `name`) VALUES (1, 'Netto')}]
insert_netto = [%q{INSERT INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', 'nettofarah@gmail.com')}]
netto_to_sql = Polo::SqlTranslator.new(netto).to_sql
expect(netto_to_sql).to eq(insert_netto)
end

describe "options" do
describe "on_duplicate: :ignore" do
it 'uses INSERT IGNORE as opposed to regular inserts' do
insert_netto = [%q{INSERT IGNORE INTO `chefs` (`id`, `name`) VALUES (1, 'Netto')}]
insert_netto = [%q{INSERT IGNORE INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', 'nettofarah@gmail.com')}]
netto_to_sql = Polo::SqlTranslator.new(netto, on_duplicate: :ignore ).to_sql
expect(netto_to_sql).to eq(insert_netto)
end
Expand All @@ -28,7 +28,7 @@
describe "on_duplicate: :override" do
it 'appends ON DUPLICATE KEY UPDATE to the statement' do
insert_netto = [
%q{INSERT INTO `chefs` (`id`, `name`) VALUES (1, 'Netto') ON DUPLICATE KEY UPDATE id = VALUES(id), name = VALUES(name)}
%q{INSERT INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', 'nettofarah@gmail.com') ON DUPLICATE KEY UPDATE id = VALUES(id), name = VALUES(name), email = VALUES(email)}
]

netto_to_sql = Polo::SqlTranslator.new(netto, on_duplicate: :override).to_sql
Expand Down
19 changes: 1 addition & 18 deletions spec/support/factories.rb
@@ -1,24 +1,7 @@
module TestData
def self.create_tom
AR::Chef.create(name: 'Tom').tap do |tom|
AR::Recipe.create(title: 'Pastrami Sandwich', chef: tom).tap do |r|
r.ingredients.create(name: 'Pastrami', quantity: 'a lot')
r.ingredients.create(name: 'Cheese', quantity: '1 slice')
end

AR::Recipe.create(title: 'Belly Burger', chef: tom).tap do |r|
r.ingredients.create(name: 'Pork Belly', quantity: 'plenty')
r.ingredients.create(name: 'Green Apple', quantity: '2 slices')
end

AR::Restaurant.create(name: 'Chef Tom Belly Burgers', owner: tom) do |res|
res.create_rating(value: '3 stars')
end
end
end

def self.create_netto
AR::Chef.create(name: 'Netto').tap do |netto|
AR::Chef.create(name: 'Netto', email: 'nettofarah@gmail.com').tap do |netto|
AR::Recipe.create(title: 'Turkey Sandwich', chef: netto).tap do |r|
r.ingredients.create(name: 'Turkey', quantity: 'a lot')
r.ingredients.create(name: 'Cheese', quantity: '1 slice')
Expand Down
1 change: 1 addition & 0 deletions spec/support/schema.rb
Expand Up @@ -17,6 +17,7 @@

create_table :chefs, force: true do |t|
t.column :name, :string
t.column :email, :string
end

create_table :restaurants, force: true do |t|
Expand Down
27 changes: 27 additions & 0 deletions spec/translator_spec.rb
@@ -0,0 +1,27 @@
require 'spec_helper'

describe Polo::Translator do

let(:finder) do
{
klass: AR::Chef,
sql: AR::Chef.where(email: 'nettofarah@gmail.com').to_sql
}
end

before(:all) do
TestData.create_netto
end

describe "options" do
describe "obfuscate: [fields]" do
it 'scrambles an specific field' do
translator = Polo::Translator.new([finder], obfuscate: [:email])
netto = translator.instances.first

expect(netto.email).to_not be_nil
expect(netto.email).to_not eq('nettofarah@gmail.com')
end
end
end
end