Skip to content

Commit

Permalink
Move Class logic into Object
Browse files Browse the repository at this point in the history
  • Loading branch information
shuber committed Jan 14, 2009
1 parent c150536 commit bcfafe5
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 228 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG
@@ -1,3 +1,6 @@
2009-01-14 - Sean Huber (shuber@huberry.com)
* Move Class logic into Object

2009-01-13 - Sean Huber (shuber@huberry.com)
* :marshal no longer defaults to true in ORM adapters
* Load gem dependencies
Expand Down
4 changes: 1 addition & 3 deletions attr_encrypted.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = 'attr_encrypted'
s.version = '1.0.6'
s.version = '1.0.7'
s.date = '2009-01-13'

s.summary = 'Generates attr_accessors that encrypt and decrypt attributes transparently'
Expand All @@ -21,8 +21,6 @@ Gem::Specification.new do |s|
lib/huberry/attr_encrypted/adapters/active_record.rb
lib/huberry/attr_encrypted/adapters/data_mapper.rb
lib/huberry/attr_encrypted/adapters/sequel.rb
lib/huberry/attr_encrypted/class.rb
lib/huberry/attr_encrypted/object.rb
MIT-LICENSE
Rakefile
README.markdown
Expand Down
221 changes: 217 additions & 4 deletions lib/attr_encrypted.rb
Expand Up @@ -4,10 +4,223 @@
gem 'shuber-encryptor'
require 'encryptor'

require 'huberry/attr_encrypted/class'
Class.send :include, Huberry::AttrEncrypted::Class
module Huberry
module AttrEncrypted
def self.extended(base)
base.attr_encrypted_options = {}
base.instance_variable_set('@encrypted_attributes', {})
end

# Default options to use with calls to <tt>attr_encrypted</tt>.
#
# It will inherit existing options from its parent class
def attr_encrypted_options
@attr_encrypted_options ||= superclass.attr_encrypted_options.nil? ? {} : superclass.attr_encrypted_options.dup
end

# Sets default options to use with calls to <tt>attr_encrypted</tt>.
def attr_encrypted_options=(options)
@attr_encrypted_options = options
end

# Contains a hash of encrypted attributes with virtual attribute names as keys and real attribute
# names as values
#
# Example
#
# class User
# attr_encrypted :email
# end
#
# User.encrypted_attributes # { 'email' => 'encrypted_email' }
def encrypted_attributes
@encrypted_attributes ||= superclass.encrypted_attributes.nil? ? {} : superclass.encrypted_attributes.dup
end

# Checks if an attribute has been configured to be encrypted
#
# Example
#
# class User
# attr_accessor :name
# attr_encrypted :email
# end
#
# User.attr_encrypted?(:name) # false
# User.attr_encrypted?(:email) # true
def attr_encrypted?(attribute)
encrypted_attributes.keys.include?(attribute.to_s)
end

protected

# Generates attr_accessors that encrypt and decrypt attributes transparently
#
# Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
#
# :attribute => The name of the referenced encrypted attribute. For example
# <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
# attribute named 'ee' to store the encrypted email. This is useful when defining
# one attribute to encrypt at a time or when the :prefix and :suffix options
# aren't enough. Defaults to nil.
#
# :prefix => A prefix used to generate the name of the referenced encrypted attributes.
# For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
# generate attributes named 'crypted_email' and 'crypted_password' to store the
# encrypted email and password. Defaults to 'encrypted_'.
#
# :suffix => A suffix used to generate the name of the referenced encrypted attributes.
# For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
# would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
# encrypted email. Defaults to ''.
#
# :key => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
# a symbol representing an instance method then the :key option will be replaced with the result of the
# method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
# Any other key types will be passed directly to the encryptor.
#
# :encode => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
# planning on storing the encrypted attributes in a database. The default encoding is 'm*' (base64),
# however this can be overwritten by setting the :encode option to some other encoding string instead of
# just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
# Defaults to false unless you're using it with ActiveRecord or DataMapper.
#
# :marshal => If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning
# on encrypting something other than a string. Defaults to false unless you're using it with ActiveRecord
# or DataMapper.
#
# :encryptor => The object to use for encrypting. Defaults to Huberry::Encryptor.
#
# :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to :encrypt.
#
# :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to :decrypt.
#
# :if => Attributes are only encrypted if this option evaluates to true. If you pass a symbol representing an instance
# method then the result of the method will be evaluated. Any objects that respond to :call are evaluated as well.
# Defaults to true.
#
# :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
# method then the result of the method will be evaluated. Any objects that respond to :call are evaluated as well.
# Defaults to false.
#
# You can specify your own default options
#
# class User
# # now all attributes will be encoded and marshaled by default
# attr_encrypted_options.merge!(:encode => true, :marshal => true, :some_other_option => true)
# attr_encrypted :configuration
# end
#
#
# Example
#
# class User
# attr_encrypted :email, :credit_card, :key => 'some secret key'
# attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
# end
#
# @user = User.new
# @user.encrypted_email # returns nil
# @user.email = 'test@example.com'
# @user.encrypted_email # returns the encrypted version of 'test@example.com'
#
# @user.configuration = { :time_zone => 'UTC' }
# @user.encrypted_configuration # returns the encrypted version of configuration
#
# See README for more examples
def attr_encrypted(*attrs)
options = {
:prefix => 'encrypted_',
:suffix => '',
:encryptor => Huberry::Encryptor,
:encrypt_method => :encrypt,
:decrypt_method => :decrypt,
:encode => false,
:marshal => false,
:if => true,
:unless => false
}.merge(attr_encrypted_options).merge(attrs.last.is_a?(Hash) ? attrs.pop : {})
options[:encode] = 'm*' if options[:encode] == true

attrs.each do |attribute|
encrypted_attribute_name = options[:attribute].nil? ? options[:prefix].to_s + attribute.to_s + options[:suffix].to_s : options[:attribute].to_s

encrypted_attributes[attribute.to_s] = encrypted_attribute_name

attr_reader encrypted_attribute_name.to_sym unless instance_methods.include?(encrypted_attribute_name)
attr_writer encrypted_attribute_name.to_sym unless instance_methods.include?("#{encrypted_attribute_name}=")

define_class_method "encrypt_#{attribute}" do |value|
if options[:if] && !options[:unless]
if value.nil?
encrypted_value = nil
else
value = Marshal.dump(value) if options[:marshal]
encrypted_value = options[:encryptor].send options[:encrypt_method], options.merge(:value => value)
encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
end
encrypted_value
else
value
end
end

define_class_method "decrypt_#{attribute}" do |encrypted_value|
if options[:if] && !options[:unless]
if encrypted_value.nil?
decrypted_value = nil
else
encrypted_value = encrypted_value.unpack(options[:encode]).to_s if options[:encode]
decrypted_value = options[:encryptor].send(options[:decrypt_method], options.merge(:value => encrypted_value))
decrypted_value = Marshal.load(decrypted_value) if options[:marshal]
end
decrypted_value
else
encrypted_value
end
end

define_method "#{attribute}" do
value = instance_variable_get("@#{attribute}")
encrypted_value = send(encrypted_attribute_name.to_sym)
original_options = [:key, :if, :unless].inject({}) do |hash, option|
hash[option] = options[option]
options[option] = self.class.send :evaluate_attr_encrypted_option, options[option], self
hash
end
value = instance_variable_set("@#{attribute}", self.class.send("decrypt_#{attribute}".to_sym, encrypted_value)) if value.nil? && !encrypted_value.nil?
options.merge!(original_options)
value
end

define_method "#{attribute}=" do |value|
original_options = [:key, :if, :unless].inject({}) do |hash, option|
hash[option] = options[option]
options[option] = self.class.send :evaluate_attr_encrypted_option, options[option], self
hash
end
send("#{encrypted_attribute_name}=".to_sym, self.class.send("encrypt_#{attribute}".to_sym, value))
options.merge!(original_options)
instance_variable_set("@#{attribute}", value)
end
end
end

# Evaluates an option specified as a symbol representing an instance method or a proc
#
# If the option is not a symbol or proc then the original option is returned
def evaluate_attr_encrypted_option(option, object)
if option.is_a?(Symbol) && object.respond_to?(option)
object.send(option)
elsif option.respond_to?(:call)
option.call(object)
else
option
end
end
end
end

require 'huberry/attr_encrypted/object'
Object.extend Huberry::AttrEncrypted::Object
Object.extend Huberry::AttrEncrypted

Dir[File.join(File.dirname(__FILE__), 'huberry', 'attr_encrypted', 'adapters', '*.rb')].each { |file| require file }

0 comments on commit bcfafe5

Please sign in to comment.