attr-encrypted-1.3.4/0000755000076400007640000000000012544776574013545 5ustar pravipraviattr-encrypted-1.3.4/README.rdoc0000644000076400007640000002777212544776574015372 0ustar pravipravi= 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/0000755000076400007640000000000012544776574014524 5ustar pravipraviattr-encrypted-1.3.4/test/sequel_test.rb0000644000076400007640000000277312544776574017417 0ustar pravipravirequire 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.rb0000644000076400007640000002165412544776574020731 0ustar pravipravirequire 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.rb0000644000076400007640000000744612544776574022320 0ustar pravipravi# -*- 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.rb0000644000076400007640000000226612544776574017375 0ustar pravipraviif 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.rb0000644000076400007640000000271212544776574020734 0ustar pravipravirequire 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.rb0000644000076400007640000002642212544776574021145 0ustar pravipravi# 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.rb0000644000076400007640000000277212544776574020375 0ustar pravipravirequire 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.rb0000644000076400007640000002507612544776574022475 0ustar pravipravi# -*- 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.rb0000644000076400007640000001155212544776574020765 0ustar pravipravi# -*- 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.sh0000755000076400007640000000137212544776574015672 0ustar pravipravi#!/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.rb0000644000076400007640000001003112544776574022240 0ustar pravipravi# -*- 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.rb0000644000076400007640000000277212544776574021721 0ustar pravipravirequire 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.yml0000644000076400007640000001470512544776574016057 0ustar pravipravi--- !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/0000755000076400007640000000000012544776574014313 5ustar pravipraviattr-encrypted-1.3.4/lib/attr_encrypted/0000755000076400007640000000000012544776574017342 5ustar pravipraviattr-encrypted-1.3.4/lib/attr_encrypted/adapters/0000755000076400007640000000000012544776574021145 5ustar pravipraviattr-encrypted-1.3.4/lib/attr_encrypted/adapters/data_mapper.rb0000644000076400007640000000107612544776574023753 0ustar pravipraviif 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 endattr-encrypted-1.3.4/lib/attr_encrypted/adapters/sequel.rb0000644000076400007640000000042212544776574022766 0ustar pravipraviif 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 endattr-encrypted-1.3.4/lib/attr_encrypted/adapters/active_record.rb0000644000076400007640000001054312544776574024306 0ustar pravipraviif 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.rb0000644000076400007640000000057112544776574021357 0ustar pravipravimodule 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.rb0000644000076400007640000003606612544776574017702 0ustar pravipravirequire '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/Rakefile0000644000076400007640000000151112544776574015210 0ustar pravipravirequire '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-LICENSE0000644000076400007640000000206212544776574015201 0ustar pravipraviCopyright (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.