attr-encrypted-1.3.4/ 0000755 0000764 0000764 00000000000 12544776574 013545 5 ustar pravi pravi attr-encrypted-1.3.4/README.rdoc 0000644 0000764 0000764 00000027772 12544776574 015372 0 ustar pravi pravi = attr_encrypted {
}[https://travis-ci.org/attr-encrypted/attr_encrypted] {
}[https://codeclimate.com/github/attr-encrypted/attr_encrypted]{
}[https://codeclimate.com/github/attr-encrypted/attr_encrypted]
Generates attr_accessors that encrypt and decrypt attributes transparently
It works with ANY class, however, you get a few extra features when you're using it with ActiveRecord, DataMapper, or Sequel
== Installation
gem install attr_encrypted
== Usage
=== Basic
Encrypting attributes has never been easier:
class User
attr_accessor :name
attr_encrypted :ssn, :key => 'a secret key'
def load
# loads the stored data
end
def save
# saves the :name and :encrypted_ssn attributes somewhere (e.g. filesystem, database, etc)
end
end
@user = User.new
@user.ssn = '123-45-6789'
@user.encrypted_ssn # returns the encrypted version of :ssn
@user.save
@user = User.load
@user.ssn # decrypts :encrypted_ssn and returns '123-45-6789'
The attr_encrypted method is also aliased as attr_encryptor to conform to Ruby's attr_ naming conventions. I should have called this project attr_encryptor but it was too late when I realized it ='(.
=== Adding required columns via database migration
By default, attr_encrypted uses the :single_iv_and_salt
encryption mode for compatibility with previous versions of the gem. This mode
uses a single IV and salt for each encrypted column. Create or modify your model
to add a column with the encrypted_ prefix (which can be modified, see
below), e.g. encrypted_ssn via a migration like the following:
create_table :users do |t|
t.string :name
t.string :encrypted_ssn
t.timestamps
end
For enhanced security, you can use the :per_attribute_iv_and_salt mode.
This requires additional _salt and _iv columns with the
encrypted_ prefix as follows and generates a unique salt and IV per
attribute:
create_table :users do |t|
t.string :name
t.string :encrypted_ssn
t.string :encrypted_ssn_salt
t.string :encrypted_ssn_iv
t.string :domain
t.timestamps
end
This mode is enabled by specifying a value of :per_attribute_iv_and_salt
via the :mode option as follows:
class User
attr_accessor :name
attr_encrypted :ssn, :key => 'a secret key', :mode => :per_attribute_iv_and_salt
end
Note that there are alternatives to storing the IV and salt in separate columns:
for example, see here[https://github.com/attr-encrypted/attr_encrypted/issues/118#issuecomment-45806629].
Note that migration from the old encryption scheme to the new is nontrivial. One
approach is described here[http://jjasonclark.com/switching_from_attr_encrypted_to_attr_encryptor],
though these instructions describe the now-defunct attr_encryptor gem
whose functionality has been merged into this project.
=== Specifying the encrypted attribute name
By default, the encrypted attribute name is encrypted_#{attribute} (e.g. attr_encrypted :email would create an attribute named encrypted_email). So, if you're storing the encrypted attribute in the database, you need to make sure the encrypted_#{attribute} field exists in your table. You have a couple of options if you want to name your attribute something else.
==== The :attribute option
You can simply pass the name of the encrypted attribute as the :attribute option:
class User
attr_encrypted :email, :key => 'a secret key', :attribute => 'email_encrypted'
end
This would generate an attribute named email_encrypted
==== The :prefix and :suffix options
If you're planning on encrypting a few different attributes and you don't like the encrypted_#{attribute} naming convention then you can specify your own:
class User
attr_encrypted :email, :credit_card, :ssn, :key => 'a secret key', :prefix => 'secret_', :suffix => '_crypted'
end
This would generate the following attributes: secret_email_crypted, secret_credit_card_crypted, and secret_ssn_crypted.
=== Encryption keys
Although a :key option may not be required (see custom encryptor below), it has a few special features
==== Unique keys for each attribute
You can specify unique keys for each attribute if you'd like:
class User
attr_encrypted :email, :key => 'a secret key'
attr_encrypted :ssn, :key => 'a different secret key'
end
==== Symbols representing instance methods as keys
If your class has an instance method that determines the encryption key to use, simply pass a symbol representing it like so:
class User
attr_encrypted :email, :key => :encryption_key
def encryption_key
# does some fancy logic and returns an encryption key
end
end
==== Procs as keys
You can pass a proc/lambda object as the :key option as well:
class User
attr_encrypted :email, :key => proc { |user| user.key }
end
This can be used to create asymmetrical encryption by requiring users to provide their own encryption keys.
=== Conditional encrypting
There may be times that you want to only encrypt when certain conditions are met. For example maybe you're using rails and you don't want to encrypt
attributes when you're in development mode. You can specify conditions like this:
class User < ActiveRecord::Base
attr_encrypted :email, :key => 'a secret key', :unless => Rails.env.development?
end
You can specify both :if and :unless options. 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.
=== Custom encryptor
The Encryptor (see http://github.com/shuber/encryptor) class is used by default. You may use your own custom encryptor by specifying
the :encryptor, :encrypt_method, and :decrypt_method options
Lets suppose you'd like to use this custom encryptor class:
class SillyEncryptor
def self.silly_encrypt(options)
(options[:value] + options[:secret_key]).reverse
end
def self.silly_decrypt(options)
options[:value].reverse.gsub(/#{options[:secret_key]}$/, '')
end
end
Simply set up your class like so:
class User
attr_encrypted :email, :secret_key => 'a secret key', :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt
end
Any options that you pass to attr_encrypted will be passed to the encryptor along with the :value option which contains the string to encrypt/decrypt. Notice it uses :secret_key instead of :key.
=== Custom algorithms
The default Encryptor uses the standard ruby OpenSSL library. It's default algorithm is aes-256-cbc. You can modify this by passing the :algorithm option to the attr_encrypted call like so:
class User
attr_encrypted :email, :key => 'a secret key', :algorithm => 'bf'
end
Run openssl list-cipher-commands to view a list of algorithms supported on your platform. See http://github.com/shuber/encryptor for more information.
aes-128-cbc
aes-128-ecb
aes-192-cbc
aes-192-ecb
aes-256-cbc
aes-256-ecb
base64
bf
bf-cbc
bf-cfb
bf-ecb
bf-ofb
cast
cast-cbc
cast5-cbc
cast5-cfb
cast5-ecb
cast5-ofb
des
des-cbc
des-cfb
des-ecb
des-ede
des-ede-cbc
des-ede-cfb
des-ede-ofb
des-ede3
des-ede3-cbc
des-ede3-cfb
des-ede3-ofb
des-ofb
des3
desx
idea
idea-cbc
idea-cfb
idea-ecb
idea-ofb
rc2
rc2-40-cbc
rc2-64-cbc
rc2-cbc
rc2-cfb
rc2-ecb
rc2-ofb
rc4
rc4-40
=== Default options
Let's imagine that you have a few attributes that you want to encrypt with different keys, but you don't like the encrypted_#{attribute} naming convention. Instead of having to define your class like this:
class User
attr_encrypted :email, :key => 'a secret key', :prefix => '', :suffix => '_crypted'
attr_encrypted :ssn, :key => 'a different secret key', :prefix => '', :suffix => '_crypted'
attr_encrypted :credit_card, :key => 'another secret key', :prefix => '', :suffix => '_crypted'
end
You can simply define some default options like so:
class User
attr_encrypted_options.merge!(:prefix => '', :suffix => '_crypted')
attr_encrypted :email, :key => 'a secret key'
attr_encrypted :ssn, :key => 'a different secret key'
attr_encrypted :credit_card, :key => 'another secret key'
end
This should help keep your classes clean and DRY.
=== Encoding
You're probably going to be storing your encrypted attributes somehow (e.g. filesystem, database, etc) and may run into some issues trying to store a weird
encrypted string. I've had this problem myself using MySQL. You can simply pass the :encode option to automatically encode/decode when encrypting/decrypting.
class User
attr_encrypted :email, :key => 'some secret key', :encode => true
end
The default encoding is m* (base64). You can change this by setting :encode => 'some encoding'. See the Array#pack method at http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding options.
=== Marshaling
You may want to encrypt objects other than strings (e.g. hashes, arrays, etc). If this is the case, simply pass the :marshal option to automatically marshal when encrypting/decrypting.
class User
attr_encrypted :credentials, :key => 'some secret key', :marshal => true
end
You may also optionally specify :marshaler, :dump_method, and :load_method if you want to use something other than the default Marshal object.
=== Encrypt/decrypt attribute methods
If you use the same key to encrypt every record (per attribute) like this:
class User
attr_encrypted :email, :key => 'a secret key'
end
Then you'll have these two class methods available for each attribute: User.encrypt_email(email_to_encrypt) and User.decrypt_email(email_to_decrypt). This can be useful when you're using ActiveRecord (see below).
=== ActiveRecord
If you're using this gem with ActiveRecord, you get a few extra features:
==== Default options
For your convenience, the :encode option is set to true by default since you'll be storing everything in a database.
==== Dynamic find_by_ and scoped_by_ methods
Let's say you'd like to encrypt your user's email addresses, but you also need a way for them to login. Simply set up your class like so:
class User < ActiveRecord::Base
attr_encrypted :email, :key => 'a secret key'
attr_encrypted :password, :key => 'some other secret key'
end
You can now lookup and login users like so:
User.find_by_email_and_password('test@example.com', 'testing')
The call to find_by_email_and_password is intercepted and modified to find_by_encrypted_email_and_encrypted_password('ENCRYPTED EMAIL', 'ENCRYPTED PASSWORD'). The dynamic scope methods like scoped_by_email_and_password work the same way.
NOTE: This only works if all records are encrypted with the same encryption key (per attribute).
=== DataMapper and Sequel
Just like the default options for ActiveRecord, the :encode option is set to true by default since you'll be storing everything in a database.
== Note on Patches/Pull Requests
* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a
future version unintentionally.
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
* Send me a pull request. Bonus points for topic branches.
attr-encrypted-1.3.4/test/ 0000755 0000764 0000764 00000000000 12544776574 014524 5 ustar pravi pravi attr-encrypted-1.3.4/test/sequel_test.rb 0000644 0000764 0000764 00000002773 12544776574 017417 0 ustar pravi pravi require File.expand_path('../test_helper', __FILE__)
DB.create_table :humans do
primary_key :id
column :encrypted_email, :string
column :encrypted_email_salt, String
column :encrypted_email_iv, :string
column :password, :string
column :encrypted_credentials, :string
column :encrypted_credentials_iv, :string
column :encrypted_credentials_salt, String
end
class Human < Sequel::Model(:humans)
self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
attr_encrypted :email, :key => SECRET_KEY
attr_encrypted :credentials, :key => SECRET_KEY, :marshal => true
def after_initialize(attrs = {})
self.credentials ||= { :username => 'example', :password => 'test' }
end
end
class SequelTest < Minitest::Test
def setup
Human.all.each(&:destroy)
end
def test_should_encrypt_email
@human = Human.new :email => 'test@example.com'
assert @human.save
refute_nil @human.encrypted_email
refute_equal @human.email, @human.encrypted_email
assert_equal @human.email, Human.first.email
end
def test_should_marshal_and_encrypt_credentials
@human = Human.new :credentials => { :username => 'example', :password => 'test' }
assert @human.save
refute_nil @human.encrypted_credentials
refute_equal @human.credentials, @human.encrypted_credentials
assert_equal @human.credentials, Human.first.credentials
assert Human.first.credentials.is_a?(Hash)
end
def test_should_encode_by_default
assert Human.attr_encrypted_options[:encode]
end
end
attr-encrypted-1.3.4/test/active_record_test.rb 0000644 0000764 0000764 00000021654 12544776574 020731 0 ustar pravi pravi require File.expand_path('../test_helper', __FILE__)
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
def create_tables
silence_stream(STDOUT) do
ActiveRecord::Schema.define(:version => 1) do
create_table :people do |t|
t.string :encrypted_email
t.string :password
t.string :encrypted_credentials
t.binary :salt
t.string :encrypted_email_salt
t.string :encrypted_credentials_salt
t.string :encrypted_email_iv
t.string :encrypted_credentials_iv
end
create_table :accounts do |t|
t.string :encrypted_password
t.string :encrypted_password_iv
t.string :encrypted_password_salt
end
create_table :users do |t|
t.string :login
t.string :encrypted_password
t.boolean :is_admin
end
create_table :prime_ministers do |t|
t.string :encrypted_name
end
end
end
end
# The table needs to exist before defining the class
create_tables
ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
ActiveRecord::Base.logger = Logger.new(nil) if ::ActiveRecord::VERSION::STRING < "3.0"
if ::ActiveRecord::VERSION::STRING > "4.0"
module Rack
module Test
class UploadedFile; end
end
end
require 'action_controller/metal/strong_parameters'
end
class Person < ActiveRecord::Base
self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
attr_encrypted :email, :key => SECRET_KEY
attr_encrypted :credentials, :key => Proc.new { |user| Encryptor.encrypt(:value => user.salt, :key => SECRET_KEY) }, :marshal => true
if ActiveRecord::VERSION::STRING < "3"
def after_initialize
initialize_salt_and_credentials
end
else
after_initialize :initialize_salt_and_credentials
end
protected
def initialize_salt_and_credentials
self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15]
self.credentials ||= { :username => 'example', :password => 'test' }
end
end
class PersonWithValidation < Person
validates_presence_of :email
end
class PersonWithProcMode < Person
attr_encrypted :email, :key => SECRET_KEY, :mode => Proc.new { :per_attribute_iv_and_salt }
attr_encrypted :credentials, :key => SECRET_KEY, :mode => Proc.new { :single_iv_and_salt }
end
class Account < ActiveRecord::Base
attr_accessor :key
attr_encrypted :password, :key => Proc.new {|account| account.key}
end
class PersonWithSerialization < ActiveRecord::Base
self.table_name = 'people'
attr_encrypted :email, :key => 'a secret key'
serialize :password
end
class UserWithProtectedAttribute < ActiveRecord::Base
self.table_name = 'users'
attr_encrypted :password, :key => 'a secret key'
attr_protected :is_admin if ::ActiveRecord::VERSION::STRING < "4.0"
end
class PersonUsingAlias < ActiveRecord::Base
self.table_name = 'people'
attr_encryptor :email, :key => 'a secret key'
end
class PrimeMinister < ActiveRecord::Base
attr_encrypted :name, :marshal => true, :key => 'SECRET_KEY'
end
class ActiveRecordTest < Minitest::Test
def setup
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
create_tables
Account.create!(:key => SECRET_KEY, :password => "password")
end
def test_should_encrypt_email
@person = Person.create :email => 'test@example.com'
refute_nil @person.encrypted_email
refute_equal @person.email, @person.encrypted_email
assert_equal @person.email, Person.find(:first).email
end
def test_should_marshal_and_encrypt_credentials
@person = Person.create
refute_nil @person.encrypted_credentials
refute_equal @person.credentials, @person.encrypted_credentials
assert_equal @person.credentials, Person.find(:first).credentials
end
def test_should_encode_by_default
assert Person.attr_encrypted_options[:encode]
end
def test_should_validate_presence_of_email
@person = PersonWithValidation.new
assert !@person.valid?
assert !@person.errors[:email].empty? || @person.errors.on(:email)
end
def test_should_encrypt_decrypt_with_iv
@person = Person.create :email => 'test@example.com'
@person2 = Person.find(@person.id)
refute_nil @person2.encrypted_email_iv
assert_equal 'test@example.com', @person2.email
end
def test_should_ensure_attributes_can_be_deserialized
@person = PersonWithSerialization.new :email => 'test@example.com', :password => %w(an array of strings)
@person.save
assert_equal @person.password, %w(an array of strings)
end
def test_should_create_an_account_regardless_of_arguments_order
Account.create!(:key => SECRET_KEY, :password => "password")
Account.create!(:password => "password" , :key => SECRET_KEY)
end
def test_should_set_attributes_regardless_of_arguments_order
Account.new.attributes = { :password => "password" , :key => SECRET_KEY }
end
def test_should_preserve_hash_key_type
hash = { :foo => 'bar' }
account = Account.create!(:key => hash)
assert_equal account.key, hash
end
if ::ActiveRecord::VERSION::STRING > "4.0"
def test_should_assign_attributes
@user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
@user.attributes = ActionController::Parameters.new(:login => 'modified', :is_admin => true).permit(:login)
assert_equal 'modified', @user.login
end
def test_should_not_assign_protected_attributes
@user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
@user.attributes = ActionController::Parameters.new(:login => 'modified', :is_admin => true).permit(:login)
assert !@user.is_admin?
end
def test_should_raise_exception_if_not_permitted
@user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
assert_raises ActiveModel::ForbiddenAttributesError do
@user.attributes = ActionController::Parameters.new(:login => 'modified', :is_admin => true)
end
end
def test_should_raise_exception_on_init_if_not_permitted
assert_raises ActiveModel::ForbiddenAttributesError do
@user = UserWithProtectedAttribute.new ActionController::Parameters.new(:login => 'modified', :is_admin => true)
end
end
else
def test_should_assign_attributes
@user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
@user.attributes = {:login => 'modified', :is_admin => true}
assert_equal 'modified', @user.login
end
def test_should_not_assign_protected_attributes
@user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
@user.attributes = {:login => 'modified', :is_admin => true}
assert !@user.is_admin?
end
def test_should_assign_protected_attributes
@user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
if ::ActiveRecord::VERSION::STRING > "3.1"
@user.send :assign_attributes, {:login => 'modified', :is_admin => true}, :without_protection => true
else
@user.send :attributes=, {:login => 'modified', :is_admin => true}, false
end
assert @user.is_admin?
end
end
def test_should_allow_assignment_of_nil_attributes
@person = Person.new
assert_nil(@person.attributes = nil)
end
def test_should_allow_proc_based_mode
@person = PersonWithProcMode.create :email => 'test@example.com', :credentials => 'password123'
# Email is :per_attribute_iv_and_salt
assert_equal @person.class.encrypted_attributes[:email][:mode].class, Proc
assert_equal @person.class.encrypted_attributes[:email][:mode].call, :per_attribute_iv_and_salt
refute_nil @person.encrypted_email_salt
refute_nil @person.encrypted_email_iv
# Credentials is :single_iv_and_salt
assert_equal @person.class.encrypted_attributes[:credentials][:mode].class, Proc
assert_equal @person.class.encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt
assert_nil @person.encrypted_credentials_salt
assert_nil @person.encrypted_credentials_iv
end
if ::ActiveRecord::VERSION::STRING > "3.1"
def test_should_allow_assign_attributes_with_nil
@person = Person.new
assert_nil(@person.assign_attributes nil)
end
end
def test_that_alias_encrypts_column
user = PersonUsingAlias.new
user.email = 'test@example.com'
user.save
refute_nil user.encrypted_email
refute_equal user.email, user.encrypted_email
assert_equal user.email, PersonUsingAlias.find(:first).email
end
# See https://github.com/attr-encrypted/attr_encrypted/issues/68
def test_should_invalidate_virtual_attributes_on_reload
pm = PrimeMinister.new(:name => 'Winston Churchill')
pm.save!
assert_equal 'Winston Churchill', pm.name
pm.name = 'Neville Chamberlain'
assert_equal 'Neville Chamberlain', pm.name
result = pm.reload
assert_equal pm, result
assert_equal 'Winston Churchill', pm.name
end
end
attr-encrypted-1.3.4/test/legacy_compatibility_test.rb 0000644 0000764 0000764 00000007446 12544776574 022320 0 ustar pravi pravi # -*- encoding: utf-8 -*-
require File.expand_path('../test_helper', __FILE__)
# Test to ensure that existing representations in database do not break on
# migrating to new versions of this gem. This ensures that future versions of
# this gem will retain backwards compatibility with data generated by earlier
# versions.
class LegacyCompatibilityTest < Minitest::Test
class LegacyNonmarshallingPet < ActiveRecord::Base
PET_NICKNAME_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-nickname-salt')
PET_NICKNAME_KEY = 'my-really-really-secret-pet-nickname-key'
PET_BIRTHDATE_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-birthdate-salt')
PET_BIRTHDATE_KEY = 'my-really-really-secret-pet-birthdate-key'
attr_encrypted :nickname,
:key => proc { Encryptor.encrypt(:value => PET_NICKNAME_SALT, :key => PET_NICKNAME_KEY) }
attr_encrypted :birthdate,
:key => proc { Encryptor.encrypt(:value => PET_BIRTHDATE_SALT, :key => PET_BIRTHDATE_KEY) }
end
class LegacyMarshallingPet < ActiveRecord::Base
PET_NICKNAME_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-nickname-salt')
PET_NICKNAME_KEY = 'my-really-really-secret-pet-nickname-key'
PET_BIRTHDATE_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-birthdate-salt')
PET_BIRTHDATE_KEY = 'my-really-really-secret-pet-birthdate-key'
attr_encrypted :nickname,
:key => proc { Encryptor.encrypt(:value => PET_NICKNAME_SALT, :key => PET_NICKNAME_KEY) },
:marshal => true
attr_encrypted :birthdate,
:key => proc { Encryptor.encrypt(:value => PET_BIRTHDATE_SALT, :key => PET_BIRTHDATE_KEY) },
:marshal => true
end
def setup
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
create_tables
end
def test_nonmarshalling_backwards_compatibility
pet = LegacyNonmarshallingPet.create!(
:name => 'Fido',
:encrypted_nickname => 'uSUB6KGzta87yxesyVc3DA==',
:encrypted_birthdate => 'I3d691B2PtFXLx15kO067g=='
)
assert_equal 'Fido', pet.name
assert_equal 'Fido the Dog', pet.nickname
assert_equal '2011-07-09', pet.birthdate
end
def test_marshalling_backwards_compatibility
# Marshalling formats changed significantly from Ruby 1.8.7 to 1.9.3.
# Also, Date class did not correctly support marshalling pre-1.9.3, so here
# we just marshal it as a string in the Ruby 1.8.7 case.
if RUBY_VERSION < '1.9.3'
pet = LegacyMarshallingPet.create!(
:name => 'Fido',
:encrypted_nickname => 'xhayxWxfkfbNyOS2w1qBMPV49Gfvs6dcZFBopMK2zQA=',
:encrypted_birthdate => 'f4ufXun4GXzahH4MQ1eTBQ=='
)
else
pet = LegacyMarshallingPet.create!(
:name => 'Fido',
:encrypted_nickname => '7RwoT64in4H+fGVBPYtRcN0K4RtriIy1EP4nDojUa8g=',
:encrypted_birthdate => 'bSp9sJhXQSp2QlNZHiujtcK4lRVBE8HQhn1y7moQ63bGJR20hvRSZ73ePAmm+wc5'
)
end
assert_equal 'Fido', pet.name
assert_equal 'Mummy\'s little helper', pet.nickname
# See earlier comment.
if RUBY_VERSION < '1.9.3'
assert_equal '2011-07-09', pet.birthdate
else
assert_equal Date.new(2011, 7, 9), pet.birthdate
end
end
private
def create_tables
silence_stream(STDOUT) do
ActiveRecord::Schema.define(:version => 1) do
create_table :legacy_nonmarshalling_pets do |t|
t.string :name
t.string :encrypted_nickname
t.string :encrypted_birthdate
t.string :salt
end
create_table :legacy_marshalling_pets do |t|
t.string :name
t.string :encrypted_nickname
t.string :encrypted_birthdate
t.string :salt
end
end
end
end
end
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
attr-encrypted-1.3.4/test/test_helper.rb 0000644 0000764 0000764 00000002266 12544776574 017375 0 ustar pravi pravi if RUBY_VERSION >= '1.9.3'
require 'simplecov'
require 'simplecov-rcov'
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
SimpleCov::Formatter::HTMLFormatter,
SimpleCov::Formatter::RcovFormatter,
]
SimpleCov.start do
add_filter 'test'
end
end
require 'minitest'
require "codeclimate-test-reporter"
CodeClimate::TestReporter.start
require 'minitest/autorun'
require 'digest/sha2'
require 'rubygems'
gem 'activerecord', ENV['ACTIVE_RECORD_VERSION'] if ENV['ACTIVE_RECORD_VERSION']
require 'active_record'
require 'data_mapper'
require 'sequel'
require 'mocha/test_unit'
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$:.unshift(File.dirname(__FILE__))
require 'attr_encrypted'
puts "\nTesting with ActiveRecord #{ActiveRecord::VERSION::STRING rescue ENV['ACTIVE_RECORD_VERSION']}"
DB = Sequel.sqlite
# The :after_initialize hook was removed in Sequel 4.0
# and had been deprecated for a while before that:
# http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Plugins/AfterInitialize.html
# This plugin re-enables it.
Sequel::Model.plugin :after_initialize
SECRET_KEY = 4.times.map { Digest::SHA256.hexdigest((Time.now.to_i * rand(5)).to_s) }.join
attr-encrypted-1.3.4/test/legacy_sequel_test.rb 0000644 0000764 0000764 00000002712 12544776574 020734 0 ustar pravi pravi require File.expand_path('../test_helper', __FILE__)
DB.create_table :legacy_humans do
primary_key :id
column :encrypted_email, :string
column :password, :string
column :encrypted_credentials, :string
column :salt, :string
end
class LegacyHuman < Sequel::Model(:legacy_humans)
attr_encrypted :email, :key => 'a secret key'
attr_encrypted :credentials, :key => Proc.new { |human| Encryptor.encrypt(:value => human.salt, :key => 'some private key') }, :marshal => true
def after_initialize(attrs = {})
self.salt ||= Digest::SHA1.hexdigest((Time.now.to_i * rand(5)).to_s)
self.credentials ||= { :username => 'example', :password => 'test' }
end
end
class LegacySequelTest < Minitest::Test
def setup
LegacyHuman.all.each(&:destroy)
end
def test_should_encrypt_email
@human = LegacyHuman.new :email => 'test@example.com'
assert @human.save
refute_nil @human.encrypted_email
refute_equal @human.email, @human.encrypted_email
assert_equal @human.email, LegacyHuman.first.email
end
def test_should_marshal_and_encrypt_credentials
@human = LegacyHuman.new
assert @human.save
refute_nil @human.encrypted_credentials
refute_equal @human.credentials, @human.encrypted_credentials
assert_equal @human.credentials, LegacyHuman.first.credentials
assert LegacyHuman.first.credentials.is_a?(Hash)
end
def test_should_encode_by_default
assert LegacyHuman.attr_encrypted_options[:encode]
end
end
attr-encrypted-1.3.4/test/attr_encrypted_test.rb 0000644 0000764 0000764 00000026422 12544776574 021145 0 ustar pravi pravi # encoding: UTF-8
require File.expand_path('../test_helper', __FILE__)
class SillyEncryptor
def self.silly_encrypt(options)
(options[:value] + options[:some_arg]).reverse
end
def self.silly_decrypt(options)
options[:value].reverse.gsub(/#{options[:some_arg]}$/, '')
end
end
class User
self.attr_encrypted_options[:key] = Proc.new { |user| SECRET_KEY } # default key
self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
attr_encrypted :email, :without_encoding, :key => SECRET_KEY
attr_encrypted :password, :prefix => 'crypted_', :suffix => '_test'
attr_encrypted :ssn, :key => :secret_key, :attribute => 'ssn_encrypted'
attr_encrypted :credit_card, :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt, :some_arg => 'test'
attr_encrypted :with_encoding, :key => SECRET_KEY, :encode => true
attr_encrypted :with_custom_encoding, :key => SECRET_KEY, :encode => 'm'
attr_encrypted :with_marshaling, :key => SECRET_KEY, :marshal => true
attr_encrypted :with_true_if, :key => SECRET_KEY, :if => true
attr_encrypted :with_false_if, :key => SECRET_KEY, :if => false
attr_encrypted :with_true_unless, :key => SECRET_KEY, :unless => true
attr_encrypted :with_false_unless, :key => SECRET_KEY, :unless => false
attr_encrypted :with_if_changed, :key => SECRET_KEY, :if => :should_encrypt
attr_encryptor :aliased, :key => SECRET_KEY
attr_accessor :salt
attr_accessor :should_encrypt
def initialize
self.salt = Time.now.to_i.to_s
self.should_encrypt = true
end
def secret_key
SECRET_KEY
end
end
class Admin < User
attr_encrypted :testing
end
class SomeOtherClass
def self.call(object)
object.class
end
end
class AttrEncryptedTest < Minitest::Test
def test_should_store_email_in_encrypted_attributes
assert User.encrypted_attributes.include?(:email)
end
def test_should_not_store_salt_in_encrypted_attributes
assert !User.encrypted_attributes.include?(:salt)
end
def test_attr_encrypted_should_return_true_for_email
assert User.attr_encrypted?('email')
end
def test_attr_encrypted_should_not_use_the_same_attribute_name_for_two_attributes_in_the_same_line
refute_equal User.encrypted_attributes[:email][:attribute], User.encrypted_attributes[:without_encoding][:attribute]
end
def test_attr_encrypted_should_return_false_for_salt
assert !User.attr_encrypted?('salt')
end
def test_should_generate_an_encrypted_attribute
assert User.new.respond_to?(:encrypted_email)
end
def test_should_generate_an_encrypted_attribute_with_a_prefix_and_suffix
assert User.new.respond_to?(:crypted_password_test)
end
def test_should_generate_an_encrypted_attribute_with_the_attribute_option
assert User.new.respond_to?(:ssn_encrypted)
end
def test_should_not_encrypt_nil_value
assert_nil User.encrypt_email(nil)
end
def test_should_not_encrypt_empty_string
assert_equal '', User.encrypt_email('')
end
def test_should_encrypt_email
refute_nil User.encrypt_email('test@example.com')
refute_equal 'test@example.com', User.encrypt_email('test@example.com')
end
def test_should_encrypt_email_when_modifying_the_attr_writer
@user = User.new
assert_nil @user.encrypted_email
@user.email = 'test@example.com'
refute_nil @user.encrypted_email
assert_equal User.encrypt_email('test@example.com'), @user.encrypted_email
end
def test_should_not_decrypt_nil_value
assert_nil User.decrypt_email(nil)
end
def test_should_not_decrypt_empty_string
assert_equal '', User.decrypt_email('')
end
def test_should_decrypt_email
encrypted_email = User.encrypt_email('test@example.com')
refute_equal 'test@test.com', encrypted_email
assert_equal 'test@example.com', User.decrypt_email(encrypted_email)
end
def test_should_decrypt_email_when_reading
@user = User.new
assert_nil @user.email
@user.encrypted_email = User.encrypt_email('test@example.com')
assert_equal 'test@example.com', @user.email
end
def test_should_encrypt_with_encoding
assert_equal User.encrypt_with_encoding('test'), [User.encrypt_without_encoding('test')].pack('m')
end
def test_should_decrypt_with_encoding
encrypted = User.encrypt_with_encoding('test')
assert_equal 'test', User.decrypt_with_encoding(encrypted)
assert_equal User.decrypt_with_encoding(encrypted), User.decrypt_without_encoding(encrypted.unpack('m').first)
end
def test_should_encrypt_with_custom_encoding
assert_equal User.encrypt_with_encoding('test'), [User.encrypt_without_encoding('test')].pack('m')
end
def test_should_decrypt_with_custom_encoding
encrypted = User.encrypt_with_encoding('test')
assert_equal 'test', User.decrypt_with_encoding(encrypted)
assert_equal User.decrypt_with_encoding(encrypted), User.decrypt_without_encoding(encrypted.unpack('m').first)
end
def test_should_encrypt_with_marshaling
@user = User.new
@user.with_marshaling = [1, 2, 3]
refute_nil @user.encrypted_with_marshaling
end
def test_should_use_custom_encryptor_and_crypt_method_names_and_arguments
assert_equal SillyEncryptor.silly_encrypt(:value => 'testing', :some_arg => 'test'), User.encrypt_credit_card('testing')
end
def test_should_evaluate_a_key_passed_as_a_symbol
@user = User.new
assert_nil @user.ssn_encrypted
@user.ssn = 'testing'
refute_nil @user.ssn_encrypted
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.ssn_encrypted_iv.unpack("m").first, :salt => @user.ssn_encrypted_salt )
assert_equal encrypted, @user.ssn_encrypted
end
def test_should_evaluate_a_key_passed_as_a_proc
@user = User.new
assert_nil @user.crypted_password_test
@user.password = 'testing'
refute_nil @user.crypted_password_test
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt)
assert_equal encrypted, @user.crypted_password_test
end
def test_should_use_options_found_in_the_attr_encrypted_options_attribute
@user = User.new
assert_nil @user.crypted_password_test
@user.password = 'testing'
refute_nil @user.crypted_password_test
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt)
assert_equal encrypted, @user.crypted_password_test
end
def test_should_inherit_encrypted_attributes
assert_equal [User.encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, Admin.encrypted_attributes.keys.collect { |key| key.to_s }.sort
end
def test_should_inherit_attr_encrypted_options
assert !User.attr_encrypted_options.empty?
assert_equal User.attr_encrypted_options, Admin.attr_encrypted_options
end
def test_should_not_inherit_unrelated_attributes
assert SomeOtherClass.attr_encrypted_options.empty?
assert SomeOtherClass.encrypted_attributes.empty?
end
def test_should_evaluate_a_symbol_option
assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, :class)
end
def test_should_evaluate_a_proc_option
assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, proc { |object| object.class })
end
def test_should_evaluate_a_lambda_option
assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, lambda { |object| object.class })
end
def test_should_evaluate_a_method_option
assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, SomeOtherClass.method(:call))
end
def test_should_return_a_string_option
assert_equal 'Object', Object.new.send(:evaluate_attr_encrypted_option, 'Object')
end
def test_should_encrypt_with_true_if
@user = User.new
assert_nil @user.encrypted_with_true_if
@user.with_true_if = 'testing'
refute_nil @user.encrypted_with_true_if
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_true_if_iv.unpack("m").first, :salt => @user.encrypted_with_true_if_salt)
assert_equal encrypted, @user.encrypted_with_true_if
end
def test_should_not_encrypt_with_false_if
@user = User.new
assert_nil @user.encrypted_with_false_if
@user.with_false_if = 'testing'
refute_nil @user.encrypted_with_false_if
assert_equal 'testing', @user.encrypted_with_false_if
end
def test_should_encrypt_with_false_unless
@user = User.new
assert_nil @user.encrypted_with_false_unless
@user.with_false_unless = 'testing'
refute_nil @user.encrypted_with_false_unless
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_false_unless_iv.unpack("m").first, :salt => @user.encrypted_with_false_unless_salt)
assert_equal encrypted, @user.encrypted_with_false_unless
end
def test_should_not_encrypt_with_true_unless
@user = User.new
assert_nil @user.encrypted_with_true_unless
@user.with_true_unless = 'testing'
refute_nil @user.encrypted_with_true_unless
assert_equal 'testing', @user.encrypted_with_true_unless
end
def test_should_work_with_aliased_attr_encryptor
assert User.encrypted_attributes.include?(:aliased)
end
def test_should_always_reset_options
@user = User.new
@user.with_if_changed = "encrypt_stuff"
@user.stubs(:instance_variable_get).returns(nil)
@user.stubs(:instance_variable_set).raises("BadStuff")
assert_raises RuntimeError do
@user.with_if_changed
end
@user = User.new
@user.should_encrypt = false
@user.with_if_changed = "not_encrypted_stuff"
assert_equal "not_encrypted_stuff", @user.with_if_changed
assert_equal "not_encrypted_stuff", @user.encrypted_with_if_changed
end
def test_should_cast_values_as_strings_before_encrypting
string_encrypted_email = User.encrypt_email('3')
assert_equal string_encrypted_email, User.encrypt_email(3)
assert_equal '3', User.decrypt_email(string_encrypted_email)
end
def test_should_create_query_accessor
@user = User.new
assert !@user.email?
@user.email = ''
assert !@user.email?
@user.email = 'test@example.com'
assert @user.email?
end
def test_should_vary_iv_per_attribute
@user = User.new
@user.email = 'email@example.com'
@user.password = 'p455w0rd'
refute_equal @user.encrypted_email_iv, @user.crypted_password_test_iv
end
def test_should_vary_iv_per_instance
@user1 = User.new
@user1.email = 'email@example.com'
@user2 = User.new
@user2.email = 'email@example.com'
refute_equal @user1.encrypted_email_iv, @user2.encrypted_email_iv
end
def test_should_vary_salt_per_attribute
@user = User.new
@user.email = 'email@example.com'
@user.password = 'p455w0rd'
refute_equal @user.encrypted_email_salt, @user.crypted_password_test_salt
end
def test_should_vary_salt_per_instance
@user1 = User.new
@user1.email = 'email@example.com'
@user2 = User.new
@user2.email = 'email@example.com'
refute_equal @user1.encrypted_email_salt, @user2.encrypted_email_salt
end
def test_should_decrypt_second_record
@user1 = User.new
@user1.email = 'test@example.com'
@user2 = User.new
@user2.email = 'test@example.com'
assert_equal 'test@example.com', @user1.decrypt(:email, @user1.encrypted_email)
end
end
attr-encrypted-1.3.4/test/data_mapper_test.rb 0000644 0000764 0000764 00000002772 12544776574 020375 0 ustar pravi pravi require File.expand_path('../test_helper', __FILE__)
DataMapper.setup(:default, 'sqlite3::memory:')
class Client
include DataMapper::Resource
property :id, Serial
property :encrypted_email, String
property :encrypted_email_iv, String
property :encrypted_email_salt, String
property :encrypted_credentials, Text
property :encrypted_credentials_iv, Text
property :encrypted_credentials_salt, Text
self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
attr_encrypted :email, :key => SECRET_KEY
attr_encrypted :credentials, :key => SECRET_KEY, :marshal => true
def initialize(attrs = {})
super attrs
self.credentials ||= { :username => 'example', :password => 'test' }
end
end
DataMapper.auto_migrate!
class DataMapperTest < Minitest::Test
def setup
Client.all.each(&:destroy)
end
def test_should_encrypt_email
@client = Client.new :email => 'test@example.com'
assert @client.save
refute_nil @client.encrypted_email
refute_equal @client.email, @client.encrypted_email
assert_equal @client.email, Client.first.email
end
def test_should_marshal_and_encrypt_credentials
@client = Client.new
assert @client.save
refute_nil @client.encrypted_credentials
refute_equal @client.credentials, @client.encrypted_credentials
assert_equal @client.credentials, Client.first.credentials
assert Client.first.credentials.is_a?(Hash)
end
def test_should_encode_by_default
assert Client.attr_encrypted_options[:encode]
end
end
attr-encrypted-1.3.4/test/legacy_attr_encrypted_test.rb 0000644 0000764 0000764 00000025076 12544776574 022475 0 ustar pravi pravi # -*- encoding: utf-8 -*-
require File.expand_path('../test_helper', __FILE__)
class LegacySillyEncryptor
def self.silly_encrypt(options)
(options[:value] + options[:some_arg]).reverse
end
def self.silly_decrypt(options)
options[:value].reverse.gsub(/#{options[:some_arg]}$/, '')
end
end
class LegacyUser
self.attr_encrypted_options[:key] = Proc.new { |user| user.class.to_s } # default key
attr_encrypted :email, :without_encoding, :key => 'secret key'
attr_encrypted :password, :prefix => 'crypted_', :suffix => '_test'
attr_encrypted :ssn, :key => :salt, :attribute => 'ssn_encrypted'
attr_encrypted :credit_card, :encryptor => LegacySillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt, :some_arg => 'test'
attr_encrypted :with_encoding, :key => 'secret key', :encode => true
attr_encrypted :with_custom_encoding, :key => 'secret key', :encode => 'm'
attr_encrypted :with_marshaling, :key => 'secret key', :marshal => true
attr_encrypted :with_true_if, :key => 'secret key', :if => true
attr_encrypted :with_false_if, :key => 'secret key', :if => false
attr_encrypted :with_true_unless, :key => 'secret key', :unless => true
attr_encrypted :with_false_unless, :key => 'secret key', :unless => false
attr_encrypted :with_if_changed, :key => 'secret key', :if => :should_encrypt
attr_encryptor :aliased, :key => 'secret_key'
attr_accessor :salt
attr_accessor :should_encrypt
def initialize
self.salt = Time.now.to_i.to_s
self.should_encrypt = true
end
end
class LegacyAdmin < LegacyUser
attr_encrypted :testing
end
class LegacySomeOtherClass
def self.call(object)
object.class
end
end
class LegacyAttrEncryptedTest < Minitest::Test
def test_should_store_email_in_encrypted_attributes
assert LegacyUser.encrypted_attributes.include?(:email)
end
def test_should_not_store_salt_in_encrypted_attributes
assert !LegacyUser.encrypted_attributes.include?(:salt)
end
def test_attr_encrypted_should_return_true_for_email
assert LegacyUser.attr_encrypted?('email')
end
def test_attr_encrypted_should_not_use_the_same_attribute_name_for_two_attributes_in_the_same_line
refute_equal LegacyUser.encrypted_attributes[:email][:attribute], LegacyUser.encrypted_attributes[:without_encoding][:attribute]
end
def test_attr_encrypted_should_return_false_for_salt
assert !LegacyUser.attr_encrypted?('salt')
end
def test_should_generate_an_encrypted_attribute
assert LegacyUser.new.respond_to?(:encrypted_email)
end
def test_should_generate_an_encrypted_attribute_with_a_prefix_and_suffix
assert LegacyUser.new.respond_to?(:crypted_password_test)
end
def test_should_generate_an_encrypted_attribute_with_the_attribute_option
assert LegacyUser.new.respond_to?(:ssn_encrypted)
end
def test_should_not_encrypt_nil_value
assert_nil LegacyUser.encrypt_email(nil)
end
def test_should_not_encrypt_empty_string
assert_equal '', LegacyUser.encrypt_email('')
end
def test_should_encrypt_email
refute_nil LegacyUser.encrypt_email('test@example.com')
refute_equal 'test@example.com', LegacyUser.encrypt_email('test@example.com')
end
def test_should_encrypt_email_when_modifying_the_attr_writer
@user = LegacyUser.new
assert_nil @user.encrypted_email
@user.email = 'test@example.com'
refute_nil @user.encrypted_email
assert_equal LegacyUser.encrypt_email('test@example.com'), @user.encrypted_email
end
def test_should_not_decrypt_nil_value
assert_nil LegacyUser.decrypt_email(nil)
end
def test_should_not_decrypt_empty_string
assert_equal '', LegacyUser.decrypt_email('')
end
def test_should_decrypt_email
encrypted_email = LegacyUser.encrypt_email('test@example.com')
refute_equal 'test@test.com', encrypted_email
assert_equal 'test@example.com', LegacyUser.decrypt_email(encrypted_email)
end
def test_should_decrypt_email_when_reading
@user = LegacyUser.new
assert_nil @user.email
@user.encrypted_email = LegacyUser.encrypt_email('test@example.com')
assert_equal 'test@example.com', @user.email
end
def test_should_encrypt_with_encoding
assert_equal LegacyUser.encrypt_with_encoding('test'), [LegacyUser.encrypt_without_encoding('test')].pack('m')
end
def test_should_decrypt_with_encoding
encrypted = LegacyUser.encrypt_with_encoding('test')
assert_equal 'test', LegacyUser.decrypt_with_encoding(encrypted)
assert_equal LegacyUser.decrypt_with_encoding(encrypted), LegacyUser.decrypt_without_encoding(encrypted.unpack('m').first)
end
def test_should_decrypt_utf8_with_encoding
encrypted = LegacyUser.encrypt_with_encoding("test\xC2\xA0utf-8\xC2\xA0text")
assert_equal "test\xC2\xA0utf-8\xC2\xA0text", LegacyUser.decrypt_with_encoding(encrypted)
assert_equal LegacyUser.decrypt_with_encoding(encrypted), LegacyUser.decrypt_without_encoding(encrypted.unpack('m').first)
end
def test_should_encrypt_with_custom_encoding
assert_equal LegacyUser.encrypt_with_custom_encoding('test'), [LegacyUser.encrypt_without_encoding('test')].pack('m')
end
def test_should_decrypt_with_custom_encoding
encrypted = LegacyUser.encrypt_with_custom_encoding('test')
assert_equal 'test', LegacyUser.decrypt_with_custom_encoding(encrypted)
assert_equal LegacyUser.decrypt_with_custom_encoding(encrypted), LegacyUser.decrypt_without_encoding(encrypted.unpack('m').first)
end
def test_should_encrypt_with_marshaling
@user = LegacyUser.new
@user.with_marshaling = [1, 2, 3]
refute_nil @user.encrypted_with_marshaling
assert_equal LegacyUser.encrypt_with_marshaling([1, 2, 3]), @user.encrypted_with_marshaling
end
def test_should_decrypt_with_marshaling
encrypted = LegacyUser.encrypt_with_marshaling([1, 2, 3])
@user = LegacyUser.new
assert_nil @user.with_marshaling
@user.encrypted_with_marshaling = encrypted
assert_equal [1, 2, 3], @user.with_marshaling
end
def test_should_use_custom_encryptor_and_crypt_method_names_and_arguments
assert_equal LegacySillyEncryptor.silly_encrypt(:value => 'testing', :some_arg => 'test'), LegacyUser.encrypt_credit_card('testing')
end
def test_should_evaluate_a_key_passed_as_a_symbol
@user = LegacyUser.new
assert_nil @user.ssn_encrypted
@user.ssn = 'testing'
refute_nil @user.ssn_encrypted
assert_equal Encryptor.encrypt(:value => 'testing', :key => @user.salt), @user.ssn_encrypted
end
def test_should_evaluate_a_key_passed_as_a_proc
@user = LegacyUser.new
assert_nil @user.crypted_password_test
@user.password = 'testing'
refute_nil @user.crypted_password_test
assert_equal Encryptor.encrypt(:value => 'testing', :key => 'LegacyUser'), @user.crypted_password_test
end
def test_should_use_options_found_in_the_attr_encrypted_options_attribute
@user = LegacyUser.new
assert_nil @user.crypted_password_test
@user.password = 'testing'
refute_nil @user.crypted_password_test
assert_equal Encryptor.encrypt(:value => 'testing', :key => 'LegacyUser'), @user.crypted_password_test
end
def test_should_inherit_encrypted_attributes
assert_equal [LegacyUser.encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, LegacyAdmin.encrypted_attributes.keys.collect { |key| key.to_s }.sort
end
def test_should_inherit_attr_encrypted_options
assert !LegacyUser.attr_encrypted_options.empty?
assert_equal LegacyUser.attr_encrypted_options, LegacyAdmin.attr_encrypted_options
end
def test_should_not_inherit_unrelated_attributes
assert LegacySomeOtherClass.attr_encrypted_options.empty?
assert LegacySomeOtherClass.encrypted_attributes.empty?
end
def test_should_evaluate_a_symbol_option
assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, :class)
end
def test_should_evaluate_a_proc_option
assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, proc { |object| object.class })
end
def test_should_evaluate_a_lambda_option
assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, lambda { |object| object.class })
end
def test_should_evaluate_a_method_option
assert_equal Object, Object.new.send(:evaluate_attr_encrypted_option, LegacySomeOtherClass.method(:call))
end
def test_should_return_a_string_option
assert_equal 'Object', Object.new.send(:evaluate_attr_encrypted_option, 'Object')
end
def test_should_encrypt_with_true_if
@user = LegacyUser.new
assert_nil @user.encrypted_with_true_if
@user.with_true_if = 'testing'
refute_nil @user.encrypted_with_true_if
assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key'), @user.encrypted_with_true_if
end
def test_should_not_encrypt_with_false_if
@user = LegacyUser.new
assert_nil @user.encrypted_with_false_if
@user.with_false_if = 'testing'
refute_nil @user.encrypted_with_false_if
assert_equal 'testing', @user.encrypted_with_false_if
end
def test_should_encrypt_with_false_unless
@user = LegacyUser.new
assert_nil @user.encrypted_with_false_unless
@user.with_false_unless = 'testing'
refute_nil @user.encrypted_with_false_unless
assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key'), @user.encrypted_with_false_unless
end
def test_should_not_encrypt_with_true_unless
@user = LegacyUser.new
assert_nil @user.encrypted_with_true_unless
@user.with_true_unless = 'testing'
refute_nil @user.encrypted_with_true_unless
assert_equal 'testing', @user.encrypted_with_true_unless
end
def test_should_work_with_aliased_attr_encryptor
assert LegacyUser.encrypted_attributes.include?(:aliased)
end
def test_should_always_reset_options
@user = LegacyUser.new
@user.with_if_changed = "encrypt_stuff"
@user.stubs(:instance_variable_get).returns(nil)
@user.stubs(:instance_variable_set).raises("BadStuff")
assert_raises RuntimeError do
@user.with_if_changed
end
@user = LegacyUser.new
@user.should_encrypt = false
@user.with_if_changed = "not_encrypted_stuff"
assert_equal "not_encrypted_stuff", @user.with_if_changed
assert_equal "not_encrypted_stuff", @user.encrypted_with_if_changed
end
def test_should_cast_values_as_strings_before_encrypting
string_encrypted_email = LegacyUser.encrypt_email('3')
assert_equal string_encrypted_email, LegacyUser.encrypt_email(3)
assert_equal '3', LegacyUser.decrypt_email(string_encrypted_email)
end
def test_should_create_query_accessor
@user = LegacyUser.new
assert !@user.email?
@user.email = ''
assert !@user.email?
@user.email = 'test@example.com'
assert @user.email?
end
end
attr-encrypted-1.3.4/test/compatibility_test.rb 0000644 0000764 0000764 00000011552 12544776574 020765 0 ustar pravi pravi # -*- encoding: utf-8 -*-
require File.expand_path('../test_helper', __FILE__)
# Test to ensure that existing representations in database do not break on
# migrating to new versions of this gem. This ensures that future versions of
# this gem will retain backwards compatibility with data generated by earlier
# versions.
class CompatibilityTest < Minitest::Test
class NonmarshallingPet < ActiveRecord::Base
PET_NICKNAME_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-nickname-salt')
PET_NICKNAME_KEY = 'my-really-really-secret-pet-nickname-key'
PET_BIRTHDATE_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-birthdate-salt')
PET_BIRTHDATE_KEY = 'my-really-really-secret-pet-birthdate-key'
self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
attr_encrypted :nickname,
:key => proc { Encryptor.encrypt(:value => PET_NICKNAME_SALT, :key => PET_NICKNAME_KEY) }
attr_encrypted :birthdate,
:key => proc { Encryptor.encrypt(:value => PET_BIRTHDATE_SALT, :key => PET_BIRTHDATE_KEY) }
end
class MarshallingPet < ActiveRecord::Base
PET_NICKNAME_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-nickname-salt')
PET_NICKNAME_KEY = 'my-really-really-secret-pet-nickname-key'
PET_BIRTHDATE_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-birthdate-salt')
PET_BIRTHDATE_KEY = 'my-really-really-secret-pet-birthdate-key'
self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
attr_encrypted :nickname,
:key => proc { Encryptor.encrypt(:value => PET_NICKNAME_SALT, :key => PET_NICKNAME_KEY) },
:marshal => true
attr_encrypted :birthdate,
:key => proc { Encryptor.encrypt(:value => PET_BIRTHDATE_SALT, :key => PET_BIRTHDATE_KEY) },
:marshal => true
end
def setup
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
create_tables
end
def test_nonmarshalling_backwards_compatibility
pet = NonmarshallingPet.create!(
:name => 'Fido',
:encrypted_nickname => 'E4lJTxFG/EfkfPg5MpnriQ==',
:encrypted_nickname_iv => 'z4Q8deE4h7f6S8NNZcbPNg==',
:encrypted_nickname_salt => 'adcd833001a873db',
:encrypted_birthdate => '6uKEAiFVdJw+N5El+U6Gow==',
:encrypted_birthdate_iv => 'zxtc1XPssL4s2HwA69nORQ==',
:encrypted_birthdate_salt => '4f879270045eaad7'
)
assert_equal 'Fido', pet.name
assert_equal 'Fido the Dog', pet.nickname
assert_equal '2011-07-09', pet.birthdate
end
def test_marshalling_backwards_compatibility
# Marshalling formats changed significantly from Ruby 1.8.7 to 1.9.3.
# Also, Date class did not correctly support marshalling pre-1.9.3, so here
# we just marshal it as a string in the Ruby 1.8.7 case.
if RUBY_VERSION < '1.9.3'
pet = MarshallingPet.create!(
:name => 'Fido',
:encrypted_nickname => 'NhpLBIp3aKRzNZrUgUfVuceYi4x+8lE3wUsVCSI9BcU=',
:encrypted_nickname_iv => 'wpQqrj3KN16fN6PsAerUTA==',
:encrypted_nickname_salt => '8f1a62d274ca8a3a',
:encrypted_birthdate => '4nbCEzcj6CjLd3B9liKm9Q==',
:encrypted_birthdate_iv => 'Vt10PQZMrbamh/gmjSLdkQ==',
:encrypted_birthdate_salt => 'cfb245a3df76404f'
)
else
pet = MarshallingPet.create!(
:name => 'Fido',
:encrypted_nickname => 'EsQScJYkPw80vVGvKWkE37Px99HHpXPFjoEPTNa4rbs=',
:encrypted_nickname_iv => 'fNq1OZcGvty4KfcvGTcFSw==',
:encrypted_nickname_salt => '733b459b7d34c217',
:encrypted_birthdate => '+VUlKQGfNWkOgCwI4hv+3qlGIwh9h6cJ/ranJlaxvU+xxQdL3H3cOzTcI2rkYkdR',
:encrypted_birthdate_iv => 'Ka+zF/SwEYZKwVa24lvFfA==',
:encrypted_birthdate_salt => 'd5e892d5bbd81566'
)
end
assert_equal 'Fido', pet.name
assert_equal 'Mummy\'s little helper', pet.nickname
# See earlier comment.
if RUBY_VERSION < '1.9.3'
assert_equal '2011-07-09', pet.birthdate
else
assert_equal Date.new(2011, 7, 9), pet.birthdate
end
end
private
def create_tables
silence_stream(STDOUT) do
ActiveRecord::Schema.define(:version => 1) do
create_table :nonmarshalling_pets do |t|
t.string :name
t.string :encrypted_nickname
t.string :encrypted_nickname_iv
t.string :encrypted_nickname_salt
t.string :encrypted_birthdate
t.string :encrypted_birthdate_iv
t.string :encrypted_birthdate_salt
end
create_table :marshalling_pets do |t|
t.string :name
t.string :encrypted_nickname
t.string :encrypted_nickname_iv
t.string :encrypted_nickname_salt
t.string :encrypted_birthdate
t.string :encrypted_birthdate_iv
t.string :encrypted_birthdate_salt
end
end
end
end
end
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
attr-encrypted-1.3.4/test/run.sh 0000755 0000764 0000764 00000001372 12544776574 015672 0 ustar pravi pravi #!/bin/sh
set -e
export RBENV_VERSION=ree-1.8.7-2012.02
rbenv version
export ACTIVERECORD=2.3.8
bundle
bundle exec rake
export ACTIVERECORD=3.0.0
bundle
bundle exec rake
export ACTIVERECORD=3.1.0
bundle
bundle exec rake
export ACTIVERECORD=3.2.0
bundle
bundle exec rake
export RBENV_VERSION=1.9.3-p484
rbenv version
export ACTIVERECORD=3.0.0
bundle
bundle exec rake
export ACTIVERECORD=3.1.0
bundle
bundle exec rake
export ACTIVERECORD=3.2.0
bundle
bundle exec rake
export ACTIVERECORD=4.0.0
bundle
bundle exec rake
export RBENV_VERSION=2.0.0-p353
rbenv version
export ACTIVERECORD=3.2.0
bundle
bundle exec rake
export ACTIVERECORD=4.0.0
bundle
bundle exec rake
export RBENV_VERSION=2.1.0
rbenv version
export ACTIVERECORD=4.0.0
bundle
bundle exec rake
attr-encrypted-1.3.4/test/legacy_active_record_test.rb 0000644 0000764 0000764 00000010031 12544776574 022240 0 ustar pravi pravi # -*- encoding: utf-8 -*-
require File.expand_path('../test_helper', __FILE__)
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
def create_people_table
silence_stream(STDOUT) do
ActiveRecord::Schema.define(:version => 1) do
create_table :legacy_people do |t|
t.string :encrypted_email
t.string :password
t.string :encrypted_credentials
t.string :salt
end
end
end
end
# The table needs to exist before defining the class
create_people_table
ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
class LegacyPerson < ActiveRecord::Base
attr_encrypted :email, :key => 'a secret key'
attr_encrypted :credentials, :key => Proc.new { |user| Encryptor.encrypt(:value => user.salt, :key => 'some private key') }, :marshal => true
ActiveSupport::Deprecation.silenced = true
def after_initialize; end
ActiveSupport::Deprecation.silenced = false
after_initialize :initialize_salt_and_credentials
protected
def initialize_salt_and_credentials
self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(5)).to_s)
self.credentials ||= { :username => 'example', :password => 'test' }
rescue ActiveRecord::MissingAttributeError
end
end
class LegacyPersonWithValidation < LegacyPerson
validates_presence_of :email
validates_uniqueness_of :encrypted_email
end
class LegacyActiveRecordTest < Minitest::Test
def setup
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
create_people_table
end
def test_should_decrypt_with_correct_encoding
if defined?(Encoding)
@person = LegacyPerson.create :email => 'test@example.com'
assert_equal 'UTF-8', LegacyPerson.find(:first).email.encoding.name
end
end
def test_should_encrypt_email
@person = LegacyPerson.create :email => 'test@example.com'
refute_nil @person.encrypted_email
refute_equal @person.email, @person.encrypted_email
assert_equal @person.email, LegacyPerson.find(:first).email
end
def test_should_marshal_and_encrypt_credentials
@person = LegacyPerson.create
refute_nil @person.encrypted_credentials
refute_equal @person.credentials, @person.encrypted_credentials
assert_equal @person.credentials, LegacyPerson.find(:first).credentials
end
def test_should_find_by_email
@person = LegacyPerson.create(:email => 'test@example.com')
assert_equal @person, LegacyPerson.find_by_email('test@example.com')
end
def test_should_find_by_email_and_password
LegacyPerson.create(:email => 'test@example.com', :password => 'invalid')
@person = LegacyPerson.create(:email => 'test@example.com', :password => 'test')
assert_equal @person, LegacyPerson.find_by_email_and_password('test@example.com', 'test')
end
def test_should_scope_by_email
@person = LegacyPerson.create(:email => 'test@example.com')
assert_equal @person, LegacyPerson.scoped_by_email('test@example.com').find(:first) rescue NoMethodError
end
def test_should_scope_by_email_and_password
LegacyPerson.create(:email => 'test@example.com', :password => 'invalid')
@person = LegacyPerson.create(:email => 'test@example.com', :password => 'test')
assert_equal @person, LegacyPerson.scoped_by_email_and_password('test@example.com', 'test').find(:first) rescue NoMethodError
end
def test_should_encode_by_default
assert LegacyPerson.attr_encrypted_options[:encode]
end
def test_should_validate_presence_of_email
@person = LegacyPersonWithValidation.new
assert !@person.valid?
assert !@person.errors[:email].empty? || @person.errors.on(:email)
end
def test_should_validate_uniqueness_of_email
@person = LegacyPersonWithValidation.new :email => 'test@example.com'
assert @person.save
@person2 = LegacyPersonWithValidation.new :email => @person.email
assert !@person2.valid?
assert !@person2.errors[:encrypted_email].empty? || @person2.errors.on(:encrypted_email)
end
end
attr-encrypted-1.3.4/test/legacy_data_mapper_test.rb 0000644 0000764 0000764 00000002772 12544776574 021721 0 ustar pravi pravi require File.expand_path('../test_helper', __FILE__)
DataMapper.setup(:default, 'sqlite3::memory:')
class LegacyClient
include DataMapper::Resource
property :id, Serial
property :encrypted_email, String
property :encrypted_credentials, Text
property :salt, String
attr_encrypted :email, :key => 'a secret key'
attr_encrypted :credentials, :key => Proc.new { |client| Encryptor.encrypt(:value => client.salt, :key => 'some private key') }, :marshal => true
def initialize(attrs = {})
super attrs
self.salt ||= Digest::SHA1.hexdigest((Time.now.to_i * rand(5)).to_s)
self.credentials ||= { :username => 'example', :password => 'test' }
end
end
DataMapper.auto_migrate!
class LegacyDataMapperTest < Minitest::Test
def setup
LegacyClient.all.each(&:destroy)
end
def test_should_encrypt_email
@client = LegacyClient.new :email => 'test@example.com'
assert @client.save
refute_nil @client.encrypted_email
refute_equal @client.email, @client.encrypted_email
assert_equal @client.email, LegacyClient.first.email
end
def test_should_marshal_and_encrypt_credentials
@client = LegacyClient.new
assert @client.save
refute_nil @client.encrypted_credentials
refute_equal @client.credentials, @client.encrypted_credentials
assert_equal @client.credentials, LegacyClient.first.credentials
assert LegacyClient.first.credentials.is_a?(Hash)
end
def test_should_encode_by_default
assert LegacyClient.attr_encrypted_options[:encode]
end
end
attr-encrypted-1.3.4/metadata.yml 0000644 0000764 0000764 00000014705 12544776574 016057 0 ustar pravi pravi --- !ruby/object:Gem::Specification
name: attr_encrypted
version: !ruby/object:Gem::Version
version: 1.3.4
prerelease:
platform: ruby
authors:
- Sean Huber
- S. Brent Faulkner
- William Monk
- Stephen Aghaulor
autorequire:
bindir: bin
cert_chain: []
date: 2015-05-11 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: encryptor
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: 1.3.0
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: 1.3.0
- !ruby/object:Gem::Dependency
name: activerecord
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: 2.0.0
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: 2.0.0
- !ruby/object:Gem::Dependency
name: minitest
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: datamapper
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: mocha
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ~>
- !ruby/object:Gem::Version
version: 1.0.0
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ~>
- !ruby/object:Gem::Version
version: 1.0.0
- !ruby/object:Gem::Dependency
name: sequel
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: sqlite3
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: dm-sqlite-adapter
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: rake
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - '='
- !ruby/object:Gem::Version
version: 0.9.2.2
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - '='
- !ruby/object:Gem::Version
version: 0.9.2.2
- !ruby/object:Gem::Dependency
name: simplecov
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: simplecov-rcov
requirement: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
description: Generates attr_accessors that encrypt and decrypt attributes transparently
email:
- shuber@huberry.com
- sbfaulkner@gmail.com
- billy.monk@gmail.com
- saghaulor@gmail.com
executables: []
extensions: []
extra_rdoc_files: []
files:
- lib/attr_encrypted/adapters/active_record.rb
- lib/attr_encrypted/adapters/data_mapper.rb
- lib/attr_encrypted/adapters/sequel.rb
- lib/attr_encrypted/version.rb
- lib/attr_encrypted.rb
- MIT-LICENSE
- Rakefile
- README.rdoc
- test/active_record_test.rb
- test/attr_encrypted_test.rb
- test/compatibility_test.rb
- test/data_mapper_test.rb
- test/legacy_active_record_test.rb
- test/legacy_attr_encrypted_test.rb
- test/legacy_compatibility_test.rb
- test/legacy_data_mapper_test.rb
- test/legacy_sequel_test.rb
- test/run.sh
- test/sequel_test.rb
- test/test_helper.rb
homepage: http://github.com/attr-encrypted/attr_encrypted
licenses: []
post_install_message:
rdoc_options:
- --line-numbers
- --inline-source
- --main
- README.rdoc
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
segments:
- 0
hash: -1997953751590493696
required_rubygems_version: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
segments:
- 0
hash: -1997953751590493696
requirements: []
rubyforge_project:
rubygems_version: 1.8.23.2
signing_key:
specification_version: 3
summary: Encrypt and decrypt attributes
test_files:
- test/active_record_test.rb
- test/attr_encrypted_test.rb
- test/compatibility_test.rb
- test/data_mapper_test.rb
- test/legacy_active_record_test.rb
- test/legacy_attr_encrypted_test.rb
- test/legacy_compatibility_test.rb
- test/legacy_data_mapper_test.rb
- test/legacy_sequel_test.rb
- test/run.sh
- test/sequel_test.rb
- test/test_helper.rb
attr-encrypted-1.3.4/lib/ 0000755 0000764 0000764 00000000000 12544776574 014313 5 ustar pravi pravi attr-encrypted-1.3.4/lib/attr_encrypted/ 0000755 0000764 0000764 00000000000 12544776574 017342 5 ustar pravi pravi attr-encrypted-1.3.4/lib/attr_encrypted/adapters/ 0000755 0000764 0000764 00000000000 12544776574 021145 5 ustar pravi pravi attr-encrypted-1.3.4/lib/attr_encrypted/adapters/data_mapper.rb 0000644 0000764 0000764 00000001076 12544776574 023753 0 ustar pravi pravi if defined?(DataMapper)
module AttrEncrypted
module Adapters
module DataMapper
def self.extended(base) # :nodoc:
class << base
alias_method :included_without_attr_encrypted, :included
alias_method :included, :included_with_attr_encrypted
end
end
def included_with_attr_encrypted(base)
included_without_attr_encrypted(base)
base.attr_encrypted_options[:encode] = true
end
end
end
end
DataMapper::Resource.extend AttrEncrypted::Adapters::DataMapper
end attr-encrypted-1.3.4/lib/attr_encrypted/adapters/sequel.rb 0000644 0000764 0000764 00000000422 12544776574 022766 0 ustar pravi pravi if defined?(Sequel)
module AttrEncrypted
module Adapters
module Sequel
def self.extended(base) # :nodoc:
base.attr_encrypted_options[:encode] = true
end
end
end
end
Sequel::Model.extend AttrEncrypted::Adapters::Sequel
end attr-encrypted-1.3.4/lib/attr_encrypted/adapters/active_record.rb 0000644 0000764 0000764 00000010543 12544776574 024306 0 ustar pravi pravi if defined?(ActiveRecord::Base)
module AttrEncrypted
module Adapters
module ActiveRecord
def self.extended(base) # :nodoc:
base.class_eval do
# https://github.com/attr-encrypted/attr_encrypted/issues/68
def reload_with_attr_encrypted(*args, &block)
result = reload_without_attr_encrypted(*args, &block)
self.class.encrypted_attributes.keys.each do |attribute_name|
instance_variable_set("@#{attribute_name}", nil)
end
result
end
alias_method_chain :reload, :attr_encrypted
attr_encrypted_options[:encode] = true
class << self
alias_method :attr_encryptor, :attr_encrypted
alias_method_chain :method_missing, :attr_encrypted
alias_method :undefine_attribute_methods, :reset_column_information if ::ActiveRecord::VERSION::STRING < "3"
end
def perform_attribute_assignment(method, new_attributes, *args)
return if new_attributes.blank?
send method, new_attributes.reject { |k, _| self.class.encrypted_attributes.key?(k.to_sym) }, *args
send method, new_attributes.reject { |k, _| !self.class.encrypted_attributes.key?(k.to_sym) }, *args
end
private :perform_attribute_assignment
if ::ActiveRecord::VERSION::STRING < "3.0" || ::ActiveRecord::VERSION::STRING > "3.1"
def assign_attributes_with_attr_encrypted(*args)
perform_attribute_assignment :assign_attributes_without_attr_encrypted, *args
end
alias_method_chain :assign_attributes, :attr_encrypted
end
def attributes_with_attr_encrypted=(*args)
perform_attribute_assignment :attributes_without_attr_encrypted=, *args
end
alias_method_chain :attributes=, :attr_encrypted
end
end
protected
# attr_encrypted method
def attr_encrypted(*attrs)
super
attrs.reject { |attr| attr.is_a?(Hash) }.each { |attr| alias_method "#{attr}_before_type_cast", attr }
end
def attribute_instance_methods_as_symbols
# We add accessor methods of the db columns to the list of instance
# methods returned to let ActiveRecord define the accessor methods
# for the db columns
if table_exists?
columns_hash.keys.inject(super) {|instance_methods, column_name| instance_methods.concat [column_name.to_sym, :"#{column_name}="]}
else
super
end
end
# Allows you to use dynamic methods like find_by_email or scoped_by_email for
# encrypted attributes
#
# NOTE: This only works when the :key option is specified as a string (see the README)
#
# This is useful for encrypting fields like email addresses. Your user's email addresses
# are encrypted in the database, but you can still look up a user by email for logging in
#
# Example
#
# class User < ActiveRecord::Base
# attr_encrypted :email, :key => 'secret key'
# end
#
# User.find_by_email_and_password('test@example.com', 'testing')
# # results in a call to
# User.find_by_encrypted_email_and_password('the_encrypted_version_of_test@example.com', 'testing')
def method_missing_with_attr_encrypted(method, *args, &block)
if match = /^(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)$/.match(method.to_s)
attribute_names = match.captures.last.split('_and_')
attribute_names.each_with_index do |attribute, index|
if attr_encrypted?(attribute)
args[index] = send("encrypt_#{attribute}", args[index])
attribute_names[index] = encrypted_attributes[attribute.to_sym][:attribute]
end
end
method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
end
method_missing_without_attr_encrypted(method, *args, &block)
end
end
end
end
ActiveRecord::Base.extend AttrEncrypted::Adapters::ActiveRecord
end
attr-encrypted-1.3.4/lib/attr_encrypted/version.rb 0000644 0000764 0000764 00000000571 12544776574 021357 0 ustar pravi pravi module AttrEncrypted
# Contains information about this gem's version
module Version
MAJOR = 1
MINOR = 3
PATCH = 4
# Returns a version string by joining MAJOR, MINOR, and PATCH with '.'
#
# Example
#
# Version.string # '1.0.2'
def self.string
[MAJOR, MINOR, PATCH].join('.')
end
end
end
attr-encrypted-1.3.4/lib/attr_encrypted.rb 0000644 0000764 0000764 00000036066 12544776574 017702 0 ustar pravi pravi require 'encryptor'
# Adds attr_accessors that encrypt and decrypt an object's attributes
module AttrEncrypted
autoload :Version, 'attr_encrypted/version'
def self.extended(base) # :nodoc:
base.class_eval do
include InstanceMethods
attr_writer :attr_encrypted_options
@attr_encrypted_options, @encrypted_attributes = {}, {}
end
end
# 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
# attr_accessor :email, :attribute => :ee 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 attr_accessor :email, :password, :prefix => 'crypted_' 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 attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'
# 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, DataMapper, or Sequel.
#
# :default_encoding => Defaults to 'm' (base64).
#
# :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.
#
# :marshaler => The object to use for marshaling. Defaults to Marshal.
#
# :dump_method => The dump method name to call on the :marshaler object to. Defaults to 'dump'.
#
# :load_method => The load method name to call on the :marshaler object. Defaults to 'load'.
#
# :encryptor => The object to use for encrypting. Defaults to Encryptor.
#
# :encrypt_method => The encrypt method name to call on the :encryptor object. Defaults to 'encrypt'.
#
# :decrypt_method => The decrypt method name to call on the :encryptor 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.
#
# :mode => Selects encryption mode for attribute: choose :single_iv_and_salt for compatibility
# with the old attr_encrypted API: the default IV and salt of the underlying encryptor object
# is used; :per_attribute_iv_and_salt uses a per-attribute IV and salt attribute and
# is the recommended mode for new deployments.
# Defaults to :single_iv_and_salt.
#
# 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, :key => 'my secret key'
# 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 # nil
# @user.email? # false
# @user.email = 'test@example.com'
# @user.email? # true
# @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(*attributes)
options = {
:prefix => 'encrypted_',
:suffix => '',
:if => true,
:unless => false,
:encode => false,
:default_encoding => 'm',
:marshal => false,
:marshaler => Marshal,
:dump_method => 'dump',
:load_method => 'load',
:encryptor => Encryptor,
:encrypt_method => 'encrypt',
:decrypt_method => 'decrypt',
:mode => :single_iv_and_salt
}.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})
options[:encode] = options[:default_encoding] if options[:encode] == true
attributes.each do |attribute|
encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
iv_name = "#{encrypted_attribute_name}_iv".to_sym
salt_name = "#{encrypted_attribute_name}_salt".to_sym
instance_methods_as_symbols = attribute_instance_methods_as_symbols
attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
if options[:mode] == :per_attribute_iv_and_salt
attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")
attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name)
attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")
end
define_method(attribute) do
instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
end
define_method("#{attribute}=") do |value|
send("#{encrypted_attribute_name}=", encrypt(attribute, value))
instance_variable_set("@#{attribute}", value)
end
define_method("#{attribute}?") do
value = send(attribute)
value.respond_to?(:empty?) ? !value.empty? : !!value
end
encrypted_attributes[attribute.to_sym] = options.merge(:attribute => encrypted_attribute_name)
end
end
alias_method :attr_encryptor, :attr_encrypted
# Default options to use with calls to attr_encrypted
#
# It will inherit existing options from its superclass
def attr_encrypted_options
@attr_encrypted_options ||= superclass.attr_encrypted_options.dup
end
# Checks if an attribute is configured with attr_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.has_key?(attribute.to_sym)
end
# Decrypts a value for the attribute specified
#
# Example
#
# class User
# attr_encrypted :email
# end
#
# email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
def decrypt(attribute, encrypted_value, options = {})
options = encrypted_attributes[attribute.to_sym].merge(options)
if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
value = options[:encryptor].send(options[:decrypt_method], options.merge!(:value => encrypted_value))
if options[:marshal]
value = options[:marshaler].send(options[:load_method], value)
elsif defined?(Encoding)
encoding = Encoding.default_internal || Encoding.default_external
value = value.force_encoding(encoding.name)
end
value
else
encrypted_value
end
end
# Encrypts a value for the attribute specified
#
# Example
#
# class User
# attr_encrypted :email
# end
#
# encrypted_email = User.encrypt(:email, 'test@example.com')
def encrypt(attribute, value, options = {})
options = encrypted_attributes[attribute.to_sym].merge(options)
if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(:value => value))
encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
encrypted_value
else
value
end
end
# Contains a hash of encrypted attributes with virtual attribute names as keys
# and their corresponding options as values
#
# Example
#
# class User
# attr_encrypted :email, :key => 'my secret key'
# end
#
# User.encrypted_attributes # { :email => { :attribute => 'encrypted_email', :key => 'my secret key' } }
def encrypted_attributes
@encrypted_attributes ||= superclass.encrypted_attributes.dup
end
# Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
# if attribute was configured with attr_encrypted
#
# Example
#
# class User
# attr_encrypted :email, :key => 'my secret key'
# end
#
# User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
def method_missing(method, *arguments, &block)
if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
send($1, $3, *arguments)
else
super
end
end
module InstanceMethods
# Decrypts a value for the attribute specified using options evaluated in the current object's scope
#
# Example
#
# class User
# attr_accessor :secret_key
# attr_encrypted :email, :key => :secret_key
#
# def initialize(secret_key)
# self.secret_key = secret_key
# end
# end
#
# @user = User.new('some-secret-key')
# @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
def decrypt(attribute, encrypted_value)
self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
end
# Encrypts a value for the attribute specified using options evaluated in the current object's scope
#
# Example
#
# class User
# attr_accessor :secret_key
# attr_encrypted :email, :key => :secret_key
#
# def initialize(secret_key)
# self.secret_key = secret_key
# end
# end
#
# @user = User.new('some-secret-key')
# @user.encrypt(:email, 'test@example.com')
def encrypt(attribute, value)
self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
end
protected
# Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
def evaluated_attr_encrypted_options_for(attribute)
if evaluate_attr_encrypted_option(self.class.encrypted_attributes[attribute.to_sym][:mode]) == :per_attribute_iv_and_salt
load_iv_for_attribute(attribute, self.class.encrypted_attributes[attribute.to_sym][:algorithm])
load_salt_for_attribute(attribute)
end
self.class.encrypted_attributes[attribute.to_sym].inject({}) { |hash, (option, value)| hash[option] = evaluate_attr_encrypted_option(value); hash }
end
# Evaluates symbol (method reference) or proc (responds to call) options
#
# If the option is not a symbol or proc then the original option is returned
def evaluate_attr_encrypted_option(option)
if option.is_a?(Symbol) && respond_to?(option)
send(option)
elsif option.respond_to?(:call)
option.call(self)
else
option
end
end
def load_iv_for_attribute(attribute, algorithm)
encrypted_attribute_name = self.class.encrypted_attributes[attribute.to_sym][:attribute]
iv = send("#{encrypted_attribute_name}_iv")
if(iv == nil)
begin
algorithm = algorithm || "aes-256-cbc"
algo = OpenSSL::Cipher::Cipher.new(algorithm)
iv = [algo.random_iv].pack("m")
send("#{encrypted_attribute_name}_iv=", iv)
rescue RuntimeError
end
end
self.class.encrypted_attributes[attribute.to_sym] = self.class.encrypted_attributes[attribute.to_sym].merge(:iv => iv.unpack("m").first) if (iv && !iv.empty?)
end
def load_salt_for_attribute(attribute)
encrypted_attribute_name = self.class.encrypted_attributes[attribute.to_sym][:attribute]
salt = send("#{encrypted_attribute_name}_salt") || send("#{encrypted_attribute_name}_salt=", Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15])
self.class.encrypted_attributes[attribute.to_sym] = self.class.encrypted_attributes[attribute.to_sym].merge(:salt => salt)
end
end
protected
def attribute_instance_methods_as_symbols
instance_methods.collect { |method| method.to_sym }
end
end
Object.extend AttrEncrypted
Dir[File.join(File.dirname(__FILE__), 'attr_encrypted', 'adapters', '*.rb')].each { |adapter| require adapter }
attr-encrypted-1.3.4/Rakefile 0000644 0000764 0000764 00000001511 12544776574 015210 0 ustar pravi pravi require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require "bundler/gem_tasks"
desc 'Test the attr_encrypted gem.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
desc 'Generate documentation for the attr_encrypted gem.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'attr_encrypted'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
if RUBY_VERSION < '1.9.3'
require 'rcov/rcovtask'
task :rcov do
sh "rcov -o coverage/rcov --exclude '^(?!lib)' " + FileList[ 'test/**/*_test.rb' ].join(' ')
end
desc 'Default: run unit tests under rcov.'
task :default => :rcov
else
desc 'Default: run unit tests.'
task :default => :test
end
attr-encrypted-1.3.4/MIT-LICENSE 0000644 0000764 0000764 00000002062 12544776574 015201 0 ustar pravi pravi Copyright (c) 2008 Sean Huber - shuber@huberry.com
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.