1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
|
# frozen_string_literal: true
require_relative 'test_helper'
RAILS_VERSION = Gem::Version.new(::ActiveRecord::VERSION::STRING).freeze
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
def create_tables
ActiveRecord::Schema.define(version: 1) do
self.verbose = false
create_table :people do |t|
t.string :encrypted_email
t.string :password
t.string :encrypted_credentials
t.binary :salt
t.binary :key_iv
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
t.string :key
end
create_table :users do |t|
t.string :login
t.string :encrypted_password
t.string :encrypted_password_iv
t.boolean :is_admin
end
create_table :prime_ministers do |t|
t.string :encrypted_name
t.string :encrypted_name_iv
end
create_table :addresses do |t|
t.binary :encrypted_street
t.binary :encrypted_street_iv
t.binary :encrypted_zipcode
t.string :mode
end
end
end
ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
module Rack
module Test
class UploadedFile; end
end
end
require 'action_controller/metal/strong_parameters'
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, iv: user.key_iv) }, marshal: true
after_initialize :initialize_salt_and_credentials
protected
def initialize_salt_and_credentials
self.key_iv ||= SecureRandom.random_bytes(12)
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 }, insecure_mode: true
end
class Account < ActiveRecord::Base
ACCOUNT_ENCRYPTION_KEY = SecureRandom.urlsafe_base64(24)
attr_encrypted :password, key: :password_encryption_key
def encrypting?(attr)
attr_encrypted_encrypted_attributes[attr][:operation] == :encrypting
end
def password_encryption_key
if encrypting?(:password)
self.key = ACCOUNT_ENCRYPTION_KEY
else
self.key
end
end
end
class PersonWithSerialization < ActiveRecord::Base
self.table_name = 'people'
attr_encrypted :email, key: SECRET_KEY
serialize :password
end
class UserWithProtectedAttribute < ActiveRecord::Base
self.table_name = 'users'
attr_encrypted :password, key: SECRET_KEY
end
class PersonUsingAlias < ActiveRecord::Base
self.table_name = 'people'
attr_encryptor :email, key: SECRET_KEY
end
class PrimeMinister < ActiveRecord::Base
attr_encrypted :name, marshal: true, key: SECRET_KEY
end
class Address < ActiveRecord::Base
self.attr_encrypted_options[:marshal] = false
self.attr_encrypted_options[:encode] = false
attr_encrypted :street, encode_iv: false, key: SECRET_KEY
attr_encrypted :zipcode, key: SECRET_KEY, mode: Proc.new { |address| address.mode.to_sym }, insecure_mode: true
end
class ActiveRecordTest < Minitest::Test
def setup
drop_all_tables
create_tables
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.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.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
# minitest does not implement `assert_nothing_raised` https://github.com/seattlerb/minitest/issues/112
Account.new.attributes = { password: "password", key: SECRET_KEY }
end
def test_should_create_changed_predicate
person = Person.create!(email: 'test@example.com')
refute person.email_changed?
person.email = 'test@example.com'
refute person.email_changed?
person.email = nil
assert person.email_changed?
person.email = 'test2@example.com'
assert person.email_changed?
end
def test_should_create_was_predicate
original_email = 'test@example.com'
person = Person.create!(email: original_email)
assert_equal original_email, person.email_was
person.email = 'test2@example.com'
assert_equal original_email, person.email_was
old_pm_name = "Winston Churchill"
pm = PrimeMinister.create!(name: old_pm_name)
assert_equal old_pm_name, pm.name_was
old_zipcode = "90210"
address = Address.create!(zipcode: old_zipcode, mode: "single_iv_and_salt")
assert_equal old_zipcode, address.zipcode_was
end
def test_attribute_was_works_when_options_for_old_encrypted_value_are_different_than_options_for_new_encrypted_value
pw = 'password'
crypto_key = SecureRandom.urlsafe_base64(24)
old_iv = SecureRandom.random_bytes(12)
account = Account.create
encrypted_value = Encryptor.encrypt(value: pw, iv: old_iv, key: crypto_key)
Account.where(id: account.id).update_all(key: crypto_key, encrypted_password_iv: [old_iv].pack('m'), encrypted_password: [encrypted_value].pack('m'))
account = Account.find(account.id)
assert_equal pw, account.password
account.password = pw.reverse
assert_equal pw, account.password_was
account.save
account.reload
assert_equal Account::ACCOUNT_ENCRYPTION_KEY, account.key
assert_equal pw.reverse, account.password
end
if Gem::Requirement.new('>= 5.2').satisfied_by?(RAILS_VERSION)
def test_should_create_will_save_change_to_predicate
person = Person.create!(email: 'test@example.com')
refute person.will_save_change_to_email?
person.email = 'test@example.com'
refute person.will_save_change_to_email?
person.email = 'test2@example.com'
assert person.will_save_change_to_email?
end
def test_should_create_saved_change_to_predicate
person = Person.create!(email: 'test@example.com')
assert person.saved_change_to_email?
person.reload
person.email = 'test@example.com'
refute person.saved_change_to_email?
person.email = nil
refute person.saved_change_to_email?
person.email = 'test2@example.com'
refute person.saved_change_to_email?
person.save
assert person.saved_change_to_email?
end
end
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
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.attr_encrypted_encrypted_attributes[:email][:mode].class, Proc
assert_equal @person.class.attr_encrypted_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.attr_encrypted_encrypted_attributes[:credentials][:mode].class, Proc
assert_equal @person.class.attr_encrypted_encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt
assert_nil @person.encrypted_credentials_salt
assert_nil @person.encrypted_credentials_iv
end
def test_should_allow_assign_attributes_with_nil
@person = Person.new
assert_nil(@person.assign_attributes nil)
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.first.email
end
# See https://github.com/attr-encrypted/attr_encrypted/issues/68
def test_should_invalidate_virtual_attributes_on_reload
old_pm_name = 'Winston Churchill'
new_pm_name = 'Neville Chamberlain'
pm = PrimeMinister.create!(name: old_pm_name)
assert_equal old_pm_name, pm.name
pm.name = new_pm_name
assert_equal new_pm_name, pm.name
result = pm.reload
assert_equal pm, result
assert_equal old_pm_name, pm.name
end
def test_should_save_encrypted_data_as_binary
street = '123 Elm'
address = Address.create!(street: street)
refute_equal address.encrypted_street, street
assert_equal Address.first.street, street
end
def test_should_evaluate_proc_based_mode
street = '123 Elm'
zipcode = '12345'
address = Address.create(street: street, zipcode: zipcode, mode: :single_iv_and_salt)
address.reload
refute_equal address.encrypted_zipcode, zipcode
assert_equal address.zipcode, zipcode
end
end
|