Skip to content

Commit

Permalink
Encryption on single attribute.
Browse files Browse the repository at this point in the history
  • Loading branch information
Travis Reeder committed Dec 6, 2009
1 parent cf935a1 commit cd7efa8
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 48 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Expand Up @@ -13,7 +13,7 @@ begin
gemspec.description = "Drop in replacement for ActiveRecord to Amazon SimpleDB instead."
gemspec.authors = ["Travis Reeder", "Chad Arimura", "RightScale"]
gemspec.files = FileList['lib/**/*.rb']
gemspec.add_dependency 'aws'
gemspec.add_dependency 'aws', 'encryptor'
end
rescue LoadError
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
Expand Down
187 changes: 150 additions & 37 deletions lib/simple_record.rb
Expand Up @@ -25,6 +25,7 @@

require 'aws'
require 'sdb/active_sdb'
require "base64"
#require 'results_array' # why the heck isn't this picking up???
require File.expand_path(File.dirname(__FILE__) + "/results_array")
require File.expand_path(File.dirname(__FILE__) + "/stats")
Expand Down Expand Up @@ -54,13 +55,18 @@ def self.stats
# :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET)
# :logger => Logger Object # Logger instance: logs to STDOUT if omitted
def self.establish_connection(aws_access_key=nil, aws_secret_key=nil, params={})
@@options = params
Aws::ActiveSdb.establish_connection(aws_access_key, aws_secret_key, params)
end

def self.close_connection()
Aws::ActiveSdb.close_connection
end

def self.options
@@options
end

class Base < Aws::ActiveSdb::Base

include SimpleRecord::Callbacks
Expand Down Expand Up @@ -169,8 +175,9 @@ def run_#{callback}
class Attribute
attr_accessor :type, :options

def initialize(type)
def initialize(type, options=nil)
@type = type
@options = options
end

end
Expand Down Expand Up @@ -242,11 +249,7 @@ def self.domain
domain_name_for_class
end


# Since SimpleDB supports multiple attributes per value, the values are an array.
# This method will return the value unwrapped if it's the only, otherwise it will return the array.
def get_attribute(arg)
arg = arg.to_s
def get_attribute_sdb(arg)
if self[arg].class==Array
if self[arg].length==1
ret = self[arg][0]
Expand All @@ -256,32 +259,60 @@ def get_attribute(arg)
else
ret = self[arg]
end
ret
return ret
end

# Since SimpleDB supports multiple attributes per value, the values are an array.
# This method will return the value unwrapped if it's the only, otherwise it will return the array.
def get_attribute(arg)
# Check if this arg is already converted
arg_s = arg.to_s
instance_var = ("@" + arg_s)
# @ssn = "FUCK"
puts "defined?(#{instance_var.to_sym}) " + (defined?(instance_var.to_sym)).inspect
# if defined?(instance_var.to_sym) # this returns "method" for some reason??
puts "attribute #{instance_var} is defined"
ret = instance_variable_get(instance_var)
puts 'ret=' + ret.to_s
return ret if !ret.nil?
# end
ret = get_attribute_sdb(arg)
ret = sdb_to_ruby(arg, ret)
puts "Setting instance var #{instance_var}"
instance_variable_set(instance_var, ret)
return ret
end

def make_dirty(arg, value)
# todo: only set dirty if it changed
#puts 'making dirty arg=' + arg.to_s + ' --- ' + @dirty.inspect
@dirty[arg] = get_attribute(arg) # Store old value (not sure if we need it?)
@dirty[arg] = get_attribute_sdb(arg) # Store old value (not sure if we need it?)
#puts 'end making dirty ' + @dirty.inspect
end

def self.has_attributes(*args)
args.each do |arg|
defined_attributes[arg] = SimpleRecord::Base::Attribute.new(:string) if defined_attributes[arg].nil?
arg_options = nil
if arg.is_a?(Hash)
# then attribute may have extra options
arg_options = arg
arg = arg_options[:name]
end
attr = SimpleRecord::Base::Attribute.new(:string, arg_options)
defined_attributes[arg] = attr if defined_attributes[arg].nil?
# define reader method
arg_s = arg.to_s # to get rid of all the to_s calls
send(:define_method, arg) do
ret = nil
ret = get_attribute(arg)
return nil if ret.nil?
return un_offset_if_int(arg, ret)
return ret
end

# define writer method
send(:define_method, arg_s+"=") do |value|
make_dirty(arg_s, value)
self[arg_s]=value
instance_var = "@" + arg_s
instance_variable_set(instance_var, value)
self[arg_s] = ruby_to_sdb(arg, value)
end

# Now for dirty methods: http://api.rubyonrails.org/classes/ActiveRecord/Dirty.html
Expand Down Expand Up @@ -358,10 +389,8 @@ def self.has_virtuals(*args)
# This method will also create an {association)_id method that will return the ID of the foreign object
# without actually materializing it.
def self.belongs_to(association_id, options = {})
attribute = SimpleRecord::Base::Attribute.new(:belongs_to)
attribute = SimpleRecord::Base::Attribute.new(:belongs_to, options)
defined_attributes[association_id] = attribute
attribute.options = options
#@@belongs_to_map[association_id] = options
arg = association_id
arg_s = arg.to_s
arg_id = arg.to_s + '_id'
Expand Down Expand Up @@ -587,21 +616,19 @@ def save(options={})
# puts 'SAVING: ' + self.inspect
clear_errors
# todo: decide whether this should go before pre_save or after pre_save? pre_save dirties "updated" and perhaps other items due to callbacks
if options[:dirty] # Only used in simple_record right now
if options[:dirty]
# puts '@dirty=' + @dirty.inspect
return true if @dirty.size == 0 # Nothing to save so skip it
options[:dirty_atts] = @dirty
end
is_create = self[:id].nil?
ok = pre_save(options)
if ok
begin
# puts 'is frozen? ' + self.frozen?.to_s + ' - ' + self.inspect
# if options[:dirty] # Only used in simple_record right now
if options[:dirty]
# puts '@dirty=' + @dirty.inspect
# return true if @dirty.size == 0 # Nothing to save so skip it
# options[:dirty_atts] = @dirty
# end
return true if @dirty.size == 0 # This should probably never happen because after pre_save, created/updated dates are changed
options[:dirty_atts] = @dirty
end
to_delete = get_atts_to_delete # todo: this should use the @dirty hash now
# puts 'done to_delete ' + to_delete.inspect
SimpleRecord.stats.puts += 1
Expand Down Expand Up @@ -663,8 +690,36 @@ def pad_and_offset_ints_to_sdb()

def convert_dates_to_sdb()

defined_attributes_local.each_pair do |name, att_meta|
# defined_attributes_local.each_pair do |name, att_meta|
# puts 'int encoding: ' + i.to_s

# end
end

def convert_all_atts_to_sdb ()

return # not using this anymore

# Should just do all translations and options in this one method/loop
pad_and_offset_ints_to_sdb()
convert_dates_to_sdb()

defined_attributes_local.each_pair do |name, att_meta|

instance_var = "@" + arg_s
instance_variable_get(instance_var.to_sym)
self[arg_s] = ruby_to_sdb(arg, value)

if att_meta.type == :int && !self[name.to_s].nil?
# puts 'before: ' + self[i.to_s].inspect
# puts @attributes.inspect
# puts @attributes[i.to_s].inspect
arr = @attributes[name.to_s]
arr.collect!{ |x| self.class.pad_and_offset(x) }
@attributes[name.to_s] = arr
# puts 'after: ' + @attributes[i.to_s].inspect
end

if att_meta.type == :date && !self[name.to_s].nil?
# puts 'before: ' + self[i.to_s].inspect
# puts @attributes.inspect
Expand All @@ -677,9 +732,44 @@ def convert_dates_to_sdb()
else
# puts 'was nil'
end

if !att_meta.options.nil?
if att_meta.options[:encrypted]
arr = @attributes[name.to_s]
arr.collect!{ |x| self.class.encrypt(x) }
@attributes[name.to_s] = arr
end
end

end
end

def self.get_encryption_key(key)
key = SimpleRecord.options[:encryption_key] if key.nil?
if key.nil?
puts 'WARNING: Encrypting attributes with your AWS Access Key. You should use your own :encryption_key so it doesn\'t change'
key = connection.aws_access_key_id # default to aws access key. NOT recommended in case you start using a new key
end
return key
end

def self.encrypt(value, key=nil)
key = get_encryption_key(key)
secret_key = Digest::SHA256.hexdigest(key)
encrypted_value = Huberry::Encryptor.encrypt(:value => value, :key => secret_key)
encoded_value = Base64.encode64(encrypted_value)
encoded_value
end


def self.decrypt(value, key=nil)
unencoded_value = Base64.decode64(value)
key = get_encryption_key(key)
secret_key = Digest::SHA256.hexdigest(key)
decrypted_value = Huberry::Encryptor.decrypt(:value => unencoded_value, :key => secret_key)
decrypted_value
end

def pre_save(options)

is_create = self[:id].nil?
Expand Down Expand Up @@ -710,10 +800,8 @@ def pre_save(options)
ok = run_before_save && (is_create ? run_before_create : run_before_update)
end
if ok
# puts 'ABOUT TO SAVE: ' + self.inspect
# First we gotta pad and offset
pad_and_offset_ints_to_sdb()
convert_dates_to_sdb()
# Now translate all fields into SimpleDB friendly strings
convert_all_atts_to_sdb()
end
ok
end
Expand Down Expand Up @@ -806,9 +894,17 @@ def delete_niled(to_delete)
end
end

def un_offset_if_int(arg, x)
# Convert value from SimpleDB String version to real ruby value.
def sdb_to_ruby(arg, x)
puts 'sdb_to_ruby arg=' + arg.inspect + ' - ' + arg.class.name
att_meta = defined_attributes_local[arg]
# puts 'int encoding: ' + i.to_s

if !att_meta.options.nil?
if att_meta.options[:encrypted]
x = self.class.decrypt(x)
end
end

if att_meta.type == :int
x = Base.un_offset_int(x)
elsif att_meta.type == :date
Expand All @@ -819,14 +915,37 @@ def un_offset_if_int(arg, x)
x
end

def ruby_to_sdb(arg, value)

return nil if value.nil?

att_meta = defined_attributes_local[arg]

if att_meta.type == :int
ret = self.class.pad_and_offset(value)

elsif att_meta.type == :date
ret = self.class.pad_and_offset(value)
else
ret = value.to_s
end

if !att_meta.options.nil?
if att_meta.options[:encrypted]
ret = self.class.encrypt(ret)
end
end

return ret

end

def to_date(x)
if x.is_a?(String)
DateTime.parse(x)
else
x
end

end

def to_bool(x)
Expand All @@ -837,7 +956,6 @@ def to_bool(x)
end
end


def self.un_offset_int(x)
if x.is_a?(String)
x2 = x.to_i
Expand All @@ -857,11 +975,6 @@ def unpad(i, attributes)
un_offset_int(x)

}
# for x in self[i]
# x = self[i][0].to_i
# x -= @@offset
# self[i] = x
# end
end
end

Expand Down
6 changes: 6 additions & 0 deletions test/model_with_enc.rb
@@ -0,0 +1,6 @@
require 'attr_encrypted'

class ModelWithEnc < SimpleRecord::Base
has_strings :name,
{:name=>:ssn, :encrypted=>true}
end
6 changes: 3 additions & 3 deletions test/temp_test.rb
Expand Up @@ -3,7 +3,7 @@
require 'minitest/unit'
require File.expand_path(File.dirname(__FILE__) + "/../lib/simple_record")
require "yaml"
require 'right_aws'
require 'aws'
require 'my_model'
require 'my_child_model'

Expand All @@ -12,12 +12,12 @@ def setup
@config = YAML::load(File.read('test-config.yml'))
puts 'akey=' + @config['amazon']['access_key']
puts 'skey=' + @config['amazon']['secret_key']
RightAws::ActiveSdb.establish_connection(@config['amazon']['access_key'], @config['amazon']['secret_key'], :port=>80, :protocol=>"http")
SimpleRecord.establish_connection(@config['amazon']['access_key'], @config['amazon']['secret_key'], :port=>80, :protocol=>"http")
SimpleRecord::Base.set_domain_prefix("simplerecord_tests_")
end

def teardown
RightAws::ActiveSdb.close_connection()
SimpleRecord.close_connection()
end

def test_dates
Expand Down

0 comments on commit cd7efa8

Please sign in to comment.