powerhome-attr_encrypted 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +67 -0
- data/CHANGELOG.md +98 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +465 -0
- data/Rakefile +25 -0
- data/attr_encrypted.gemspec +63 -0
- data/certs/saghaulor.pem +21 -0
- data/checksum/attr_encrypted-3.0.0.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.0.gem.sha512 +1 -0
- data/checksum/attr_encrypted-3.0.1.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.1.gem.sha512 +1 -0
- data/checksum/attr_encrypted-3.0.2.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.2.gem.sha512 +1 -0
- data/checksum/attr_encrypted-3.0.3.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.3.gem.sha512 +1 -0
- data/checksum/attr_encrypted-3.1.0.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.1.0.gem.sha512 +1 -0
- data/lib/attr_encrypted.rb +473 -0
- data/lib/attr_encrypted/adapters/active_record.rb +157 -0
- data/lib/attr_encrypted/adapters/data_mapper.rb +24 -0
- data/lib/attr_encrypted/adapters/sequel.rb +16 -0
- data/lib/attr_encrypted/version.rb +19 -0
- data/test/active_record_test.rb +365 -0
- data/test/attr_encrypted_test.rb +490 -0
- data/test/compatibility_test.rb +109 -0
- data/test/data_mapper_test.rb +59 -0
- data/test/legacy_active_record_test.rb +120 -0
- data/test/legacy_attr_encrypted_test.rb +300 -0
- data/test/legacy_compatibility_test.rb +95 -0
- data/test/legacy_data_mapper_test.rb +57 -0
- data/test/legacy_sequel_test.rb +54 -0
- data/test/run.sh +12 -0
- data/test/sequel_test.rb +55 -0
- data/test/test_helper.rb +61 -0
- metadata +294 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if defined?(ActiveRecord::Base)
|
4
|
+
module AttrEncrypted
|
5
|
+
module Adapters
|
6
|
+
module ActiveRecord
|
7
|
+
def self.extended(base) # :nodoc:
|
8
|
+
base.class_eval do
|
9
|
+
|
10
|
+
# https://github.com/attr-encrypted/attr_encrypted/issues/68
|
11
|
+
alias_method :reload_without_attr_encrypted, :reload
|
12
|
+
def reload(*args, &block)
|
13
|
+
result = reload_without_attr_encrypted(*args, &block)
|
14
|
+
self.class.encrypted_attributes.keys.each do |attribute_name|
|
15
|
+
instance_variable_set("@#{attribute_name}", nil)
|
16
|
+
end
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_encrypted_options[:encode] = true
|
21
|
+
|
22
|
+
class << self
|
23
|
+
alias_method :method_missing_without_attr_encrypted, :method_missing
|
24
|
+
alias_method :method_missing, :method_missing_with_attr_encrypted
|
25
|
+
end
|
26
|
+
|
27
|
+
def perform_attribute_assignment(method, new_attributes, *args)
|
28
|
+
return if new_attributes.blank?
|
29
|
+
|
30
|
+
send method, new_attributes.reject { |k, _| self.class.encrypted_attributes.key?(k.to_sym) }, *args
|
31
|
+
send method, new_attributes.reject { |k, _| !self.class.encrypted_attributes.key?(k.to_sym) }, *args
|
32
|
+
end
|
33
|
+
private :perform_attribute_assignment
|
34
|
+
|
35
|
+
if ::ActiveRecord::VERSION::STRING > "3.1"
|
36
|
+
alias_method :assign_attributes_without_attr_encrypted, :assign_attributes
|
37
|
+
def assign_attributes(*args)
|
38
|
+
perform_attribute_assignment :assign_attributes_without_attr_encrypted, *args
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
alias_method :attributes_without_attr_encrypted=, :attributes=
|
43
|
+
def attributes=(*args)
|
44
|
+
perform_attribute_assignment :attributes_without_attr_encrypted=, *args
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
# <tt>attr_encrypted</tt> method
|
52
|
+
def attr_encrypted(*attrs)
|
53
|
+
super
|
54
|
+
options = attrs.extract_options!
|
55
|
+
attr = attrs.pop
|
56
|
+
attribute attr if ::ActiveRecord::VERSION::STRING >= "5.1.0"
|
57
|
+
options.merge! encrypted_attributes[attr]
|
58
|
+
|
59
|
+
define_method("#{attr}_was") do
|
60
|
+
attribute_was(attr)
|
61
|
+
end
|
62
|
+
|
63
|
+
if ::ActiveRecord::VERSION::STRING >= "4.1"
|
64
|
+
define_method("#{attr}_changed?") do |options = {}|
|
65
|
+
attribute_changed?(attr, options)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
define_method("#{attr}_changed?") do
|
69
|
+
attribute_changed?(attr)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
define_method("#{attr}_change") do
|
74
|
+
attribute_change(attr)
|
75
|
+
end
|
76
|
+
|
77
|
+
define_method("#{attr}_with_dirtiness=") do |value|
|
78
|
+
##
|
79
|
+
# In ActiveRecord 5.2+, due to changes to the way virtual
|
80
|
+
# attributes are handled, @attributes[attr].value is nil which
|
81
|
+
# breaks attribute_was. Setting it here returns us to the expected
|
82
|
+
# behavior.
|
83
|
+
if ::ActiveRecord::VERSION::STRING >= "5.2"
|
84
|
+
# This is needed support attribute_was before a record has
|
85
|
+
# been saved
|
86
|
+
set_attribute_was(attr, __send__(attr)) if value != __send__(attr)
|
87
|
+
# This is needed to support attribute_was after a record has
|
88
|
+
# been saved
|
89
|
+
@attributes.write_from_user(attr.to_s, value) if value != __send__(attr)
|
90
|
+
end
|
91
|
+
##
|
92
|
+
attribute_will_change!(attr) if value != __send__(attr)
|
93
|
+
__send__("#{attr}_without_dirtiness=", value)
|
94
|
+
end
|
95
|
+
|
96
|
+
alias_method "#{attr}_without_dirtiness=", "#{attr}="
|
97
|
+
alias_method "#{attr}=", "#{attr}_with_dirtiness="
|
98
|
+
|
99
|
+
alias_method "#{attr}_before_type_cast", attr
|
100
|
+
end
|
101
|
+
|
102
|
+
def attribute_instance_methods_as_symbols
|
103
|
+
# We add accessor methods of the db columns to the list of instance
|
104
|
+
# methods returned to let ActiveRecord define the accessor methods
|
105
|
+
# for the db columns
|
106
|
+
|
107
|
+
if connected? && table_exists?
|
108
|
+
columns_hash.keys.inject(super) {|instance_methods, column_name| instance_methods.concat [column_name.to_sym, :"#{column_name}="]}
|
109
|
+
else
|
110
|
+
super
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def attribute_instance_methods_as_symbols_available?
|
115
|
+
connected? && table_exists?
|
116
|
+
end
|
117
|
+
|
118
|
+
# Allows you to use dynamic methods like <tt>find_by_email</tt> or <tt>scoped_by_email</tt> for
|
119
|
+
# encrypted attributes
|
120
|
+
#
|
121
|
+
# NOTE: This only works when the <tt>:key</tt> option is specified as a string (see the README)
|
122
|
+
#
|
123
|
+
# This is useful for encrypting fields like email addresses. Your user's email addresses
|
124
|
+
# are encrypted in the database, but you can still look up a user by email for logging in
|
125
|
+
#
|
126
|
+
# Example
|
127
|
+
#
|
128
|
+
# class User < ActiveRecord::Base
|
129
|
+
# attr_encrypted :email, key: 'secret key'
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# User.find_by_email_and_password('test@example.com', 'testing')
|
133
|
+
# # results in a call to
|
134
|
+
# User.find_by_encrypted_email_and_password('the_encrypted_version_of_test@example.com', 'testing')
|
135
|
+
def method_missing_with_attr_encrypted(method, *args, &block)
|
136
|
+
if match = /^(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)$/.match(method.to_s)
|
137
|
+
attribute_names = match.captures.last.split('_and_')
|
138
|
+
attribute_names.each_with_index do |attribute, index|
|
139
|
+
if attr_encrypted?(attribute) && encrypted_attributes[attribute.to_sym][:mode] == :single_iv_and_salt
|
140
|
+
args[index] = send("encrypt_#{attribute}", args[index])
|
141
|
+
warn "DEPRECATION WARNING: This feature will be removed in the next major release."
|
142
|
+
attribute_names[index] = encrypted_attributes[attribute.to_sym][:attribute]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
|
146
|
+
end
|
147
|
+
method_missing_without_attr_encrypted(method, *args, &block)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
ActiveSupport.on_load(:active_record) do
|
154
|
+
extend AttrEncrypted
|
155
|
+
extend AttrEncrypted::Adapters::ActiveRecord
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if defined?(DataMapper)
|
4
|
+
module AttrEncrypted
|
5
|
+
module Adapters
|
6
|
+
module DataMapper
|
7
|
+
def self.extended(base) # :nodoc:
|
8
|
+
class << base
|
9
|
+
alias_method :included_without_attr_encrypted, :included
|
10
|
+
alias_method :included, :included_with_attr_encrypted
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def included_with_attr_encrypted(base)
|
15
|
+
included_without_attr_encrypted(base)
|
16
|
+
base.extend AttrEncrypted
|
17
|
+
base.attr_encrypted_options[:encode] = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
DataMapper::Resource.extend AttrEncrypted::Adapters::DataMapper
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if defined?(Sequel)
|
4
|
+
module AttrEncrypted
|
5
|
+
module Adapters
|
6
|
+
module Sequel
|
7
|
+
def self.extended(base) # :nodoc:
|
8
|
+
base.attr_encrypted_options[:encode] = true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Sequel::Model.extend AttrEncrypted
|
15
|
+
Sequel::Model.extend AttrEncrypted::Adapters::Sequel
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AttrEncrypted
|
4
|
+
# Contains information about this gem's version
|
5
|
+
module Version
|
6
|
+
MAJOR = 1
|
7
|
+
MINOR = 0
|
8
|
+
PATCH = 1
|
9
|
+
|
10
|
+
# Returns a version string by joining <tt>MAJOR</tt>, <tt>MINOR</tt>, and <tt>PATCH</tt> with <tt>'.'</tt>
|
11
|
+
#
|
12
|
+
# Example
|
13
|
+
#
|
14
|
+
# Version.string # '1.0.2'
|
15
|
+
def self.string
|
16
|
+
[MAJOR, MINOR, PATCH].join('.')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,365 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'test_helper'
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
6
|
+
|
7
|
+
def create_tables
|
8
|
+
ActiveRecord::Schema.define(version: 1) do
|
9
|
+
self.verbose = false
|
10
|
+
create_table :people do |t|
|
11
|
+
t.string :encrypted_email
|
12
|
+
t.string :password
|
13
|
+
t.string :encrypted_credentials
|
14
|
+
t.binary :salt
|
15
|
+
t.binary :key_iv
|
16
|
+
t.string :encrypted_email_salt
|
17
|
+
t.string :encrypted_credentials_salt
|
18
|
+
t.string :encrypted_email_iv
|
19
|
+
t.string :encrypted_credentials_iv
|
20
|
+
end
|
21
|
+
create_table :accounts do |t|
|
22
|
+
t.string :encrypted_password
|
23
|
+
t.string :encrypted_password_iv
|
24
|
+
t.string :encrypted_password_salt
|
25
|
+
t.string :key
|
26
|
+
end
|
27
|
+
create_table :users do |t|
|
28
|
+
t.string :login
|
29
|
+
t.string :encrypted_password
|
30
|
+
t.string :encrypted_password_iv
|
31
|
+
t.boolean :is_admin
|
32
|
+
end
|
33
|
+
create_table :prime_ministers do |t|
|
34
|
+
t.string :encrypted_name
|
35
|
+
t.string :encrypted_name_iv
|
36
|
+
end
|
37
|
+
create_table :addresses do |t|
|
38
|
+
t.binary :encrypted_street
|
39
|
+
t.binary :encrypted_street_iv
|
40
|
+
t.binary :encrypted_zipcode
|
41
|
+
t.string :mode
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
|
47
|
+
|
48
|
+
if ::ActiveRecord::VERSION::STRING > "4.0"
|
49
|
+
module Rack
|
50
|
+
module Test
|
51
|
+
class UploadedFile; end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
require 'action_controller/metal/strong_parameters'
|
56
|
+
end
|
57
|
+
|
58
|
+
class Person < ActiveRecord::Base
|
59
|
+
self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
|
60
|
+
attr_encrypted :email, key: SECRET_KEY
|
61
|
+
attr_encrypted :credentials, key: Proc.new { |user| Encryptor.encrypt(value: user.salt, key: SECRET_KEY, iv: user.key_iv) }, marshal: true
|
62
|
+
|
63
|
+
after_initialize :initialize_salt_and_credentials
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def initialize_salt_and_credentials
|
68
|
+
self.key_iv ||= SecureRandom.random_bytes(12)
|
69
|
+
self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15]
|
70
|
+
self.credentials ||= { username: 'example', password: 'test' }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class PersonWithValidation < Person
|
75
|
+
validates_presence_of :email
|
76
|
+
end
|
77
|
+
|
78
|
+
class PersonWithProcMode < Person
|
79
|
+
attr_encrypted :email, key: SECRET_KEY, mode: Proc.new { :per_attribute_iv_and_salt }
|
80
|
+
attr_encrypted :credentials, key: SECRET_KEY, mode: Proc.new { :single_iv_and_salt }, insecure_mode: true
|
81
|
+
end
|
82
|
+
|
83
|
+
class Account < ActiveRecord::Base
|
84
|
+
ACCOUNT_ENCRYPTION_KEY = SecureRandom.urlsafe_base64(24)
|
85
|
+
attr_encrypted :password, key: :password_encryption_key
|
86
|
+
|
87
|
+
def encrypting?(attr)
|
88
|
+
encrypted_attributes[attr][:operation] == :encrypting
|
89
|
+
end
|
90
|
+
|
91
|
+
def password_encryption_key
|
92
|
+
if encrypting?(:password)
|
93
|
+
self.key = ACCOUNT_ENCRYPTION_KEY
|
94
|
+
else
|
95
|
+
self.key
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class PersonWithSerialization < ActiveRecord::Base
|
101
|
+
self.table_name = 'people'
|
102
|
+
attr_encrypted :email, key: SECRET_KEY
|
103
|
+
serialize :password
|
104
|
+
end
|
105
|
+
|
106
|
+
class UserWithProtectedAttribute < ActiveRecord::Base
|
107
|
+
self.table_name = 'users'
|
108
|
+
attr_encrypted :password, key: SECRET_KEY
|
109
|
+
attr_protected :is_admin if ::ActiveRecord::VERSION::STRING < "4.0"
|
110
|
+
end
|
111
|
+
|
112
|
+
class PersonUsingAlias < ActiveRecord::Base
|
113
|
+
self.table_name = 'people'
|
114
|
+
attr_encryptor :email, key: SECRET_KEY
|
115
|
+
end
|
116
|
+
|
117
|
+
class PrimeMinister < ActiveRecord::Base
|
118
|
+
attr_encrypted :name, marshal: true, key: SECRET_KEY
|
119
|
+
end
|
120
|
+
|
121
|
+
class Address < ActiveRecord::Base
|
122
|
+
self.attr_encrypted_options[:marshal] = false
|
123
|
+
self.attr_encrypted_options[:encode] = false
|
124
|
+
attr_encrypted :street, encode_iv: false, key: SECRET_KEY
|
125
|
+
attr_encrypted :zipcode, key: SECRET_KEY, mode: Proc.new { |address| address.mode.to_sym }, insecure_mode: true
|
126
|
+
end
|
127
|
+
|
128
|
+
class ActiveRecordTest < Minitest::Test
|
129
|
+
def setup
|
130
|
+
drop_all_tables
|
131
|
+
create_tables
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_should_encrypt_email
|
135
|
+
@person = Person.create(email: 'test@example.com')
|
136
|
+
refute_nil @person.encrypted_email
|
137
|
+
refute_equal @person.email, @person.encrypted_email
|
138
|
+
assert_equal @person.email, Person.first.email
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_should_marshal_and_encrypt_credentials
|
142
|
+
@person = Person.create
|
143
|
+
refute_nil @person.encrypted_credentials
|
144
|
+
refute_equal @person.credentials, @person.encrypted_credentials
|
145
|
+
assert_equal @person.credentials, Person.first.credentials
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_should_encode_by_default
|
149
|
+
assert Person.attr_encrypted_options[:encode]
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_should_validate_presence_of_email
|
153
|
+
@person = PersonWithValidation.new
|
154
|
+
assert !@person.valid?
|
155
|
+
assert !@person.errors[:email].empty? || @person.errors.on(:email)
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_should_encrypt_decrypt_with_iv
|
159
|
+
@person = Person.create(email: 'test@example.com')
|
160
|
+
@person2 = Person.find(@person.id)
|
161
|
+
refute_nil @person2.encrypted_email_iv
|
162
|
+
assert_equal 'test@example.com', @person2.email
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_should_ensure_attributes_can_be_deserialized
|
166
|
+
@person = PersonWithSerialization.new(email: 'test@example.com', password: %w(an array of strings))
|
167
|
+
@person.save
|
168
|
+
assert_equal @person.password, %w(an array of strings)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_should_create_an_account_regardless_of_arguments_order
|
172
|
+
Account.create!(key: SECRET_KEY, password: "password")
|
173
|
+
Account.create!(password: "password" , key: SECRET_KEY)
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_should_set_attributes_regardless_of_arguments_order
|
177
|
+
# minitest does not implement `assert_nothing_raised` https://github.com/seattlerb/minitest/issues/112
|
178
|
+
Account.new.attributes = { password: "password", key: SECRET_KEY }
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_should_create_changed_predicate
|
182
|
+
person = Person.create!(email: 'test@example.com')
|
183
|
+
refute person.email_changed?
|
184
|
+
person.email = 'test@example.com'
|
185
|
+
refute person.email_changed?
|
186
|
+
person.email = nil
|
187
|
+
assert person.email_changed?
|
188
|
+
person.email = 'test2@example.com'
|
189
|
+
assert person.email_changed?
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_should_create_was_predicate
|
193
|
+
original_email = 'test@example.com'
|
194
|
+
person = Person.create!(email: original_email)
|
195
|
+
assert_equal original_email, person.email_was
|
196
|
+
person.email = 'test2@example.com'
|
197
|
+
assert_equal original_email, person.email_was
|
198
|
+
old_pm_name = "Winston Churchill"
|
199
|
+
pm = PrimeMinister.create!(name: old_pm_name)
|
200
|
+
assert_equal old_pm_name, pm.name_was
|
201
|
+
old_zipcode = "90210"
|
202
|
+
address = Address.create!(zipcode: old_zipcode, mode: "single_iv_and_salt")
|
203
|
+
assert_equal old_zipcode, address.zipcode_was
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_attribute_was_works_when_options_for_old_encrypted_value_are_different_than_options_for_new_encrypted_value
|
207
|
+
pw = 'password'
|
208
|
+
crypto_key = SecureRandom.urlsafe_base64(24)
|
209
|
+
old_iv = SecureRandom.random_bytes(12)
|
210
|
+
account = Account.create
|
211
|
+
encrypted_value = Encryptor.encrypt(value: pw, iv: old_iv, key: crypto_key)
|
212
|
+
Account.where(id: account.id).update_all(key: crypto_key, encrypted_password_iv: [old_iv].pack('m'), encrypted_password: [encrypted_value].pack('m'))
|
213
|
+
account = Account.find(account.id)
|
214
|
+
assert_equal pw, account.password
|
215
|
+
account.password = pw.reverse
|
216
|
+
assert_equal pw, account.password_was
|
217
|
+
account.save
|
218
|
+
account.reload
|
219
|
+
assert_equal Account::ACCOUNT_ENCRYPTION_KEY, account.key
|
220
|
+
assert_equal pw.reverse, account.password
|
221
|
+
end
|
222
|
+
|
223
|
+
# ActiveRecord 5.2 specific methods
|
224
|
+
if ::ActiveRecord::VERSION::STRING >= "5.2"
|
225
|
+
def test_should_create_will_save_change_to_predicate
|
226
|
+
person = Person.create!(email: 'test@example.com')
|
227
|
+
refute person.will_save_change_to_email?
|
228
|
+
person.email = 'test@example.com'
|
229
|
+
refute person.will_save_change_to_email?
|
230
|
+
person.email = 'test2@example.com'
|
231
|
+
assert person.will_save_change_to_email?
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_should_create_saved_change_to_predicate
|
235
|
+
person = Person.create!(email: 'test@example.com')
|
236
|
+
assert person.saved_change_to_email?
|
237
|
+
person.reload
|
238
|
+
person.email = 'test@example.com'
|
239
|
+
refute person.saved_change_to_email?
|
240
|
+
person.email = nil
|
241
|
+
refute person.saved_change_to_email?
|
242
|
+
person.email = 'test2@example.com'
|
243
|
+
refute person.saved_change_to_email?
|
244
|
+
person.save
|
245
|
+
assert person.saved_change_to_email?
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
if ::ActiveRecord::VERSION::STRING > "4.0"
|
250
|
+
def test_should_assign_attributes
|
251
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
252
|
+
@user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
|
253
|
+
assert_equal 'modified', @user.login
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_should_not_assign_protected_attributes
|
257
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
258
|
+
@user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
|
259
|
+
assert !@user.is_admin?
|
260
|
+
end
|
261
|
+
|
262
|
+
def test_should_raise_exception_if_not_permitted
|
263
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
264
|
+
assert_raises ActiveModel::ForbiddenAttributesError do
|
265
|
+
@user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_should_raise_exception_on_init_if_not_permitted
|
270
|
+
assert_raises ActiveModel::ForbiddenAttributesError do
|
271
|
+
@user = UserWithProtectedAttribute.new ActionController::Parameters.new(login: 'modified', is_admin: true)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
else
|
275
|
+
def test_should_assign_attributes
|
276
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
277
|
+
@user.attributes = { login: 'modified', is_admin: true }
|
278
|
+
assert_equal 'modified', @user.login
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_should_not_assign_protected_attributes
|
282
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
283
|
+
@user.attributes = { login: 'modified', is_admin: true }
|
284
|
+
assert !@user.is_admin?
|
285
|
+
end
|
286
|
+
|
287
|
+
def test_should_assign_protected_attributes
|
288
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
289
|
+
if ::ActiveRecord::VERSION::STRING > "3.1"
|
290
|
+
@user.send(:assign_attributes, { login: 'modified', is_admin: true }, without_protection: true)
|
291
|
+
else
|
292
|
+
@user.send(:attributes=, { login: 'modified', is_admin: true }, false)
|
293
|
+
end
|
294
|
+
assert @user.is_admin?
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def test_should_allow_assignment_of_nil_attributes
|
299
|
+
@person = Person.new
|
300
|
+
assert_nil(@person.attributes = nil)
|
301
|
+
end
|
302
|
+
|
303
|
+
def test_should_allow_proc_based_mode
|
304
|
+
@person = PersonWithProcMode.create(email: 'test@example.com', credentials: 'password123')
|
305
|
+
|
306
|
+
# Email is :per_attribute_iv_and_salt
|
307
|
+
assert_equal @person.class.encrypted_attributes[:email][:mode].class, Proc
|
308
|
+
assert_equal @person.class.encrypted_attributes[:email][:mode].call, :per_attribute_iv_and_salt
|
309
|
+
refute_nil @person.encrypted_email_salt
|
310
|
+
refute_nil @person.encrypted_email_iv
|
311
|
+
|
312
|
+
# Credentials is :single_iv_and_salt
|
313
|
+
assert_equal @person.class.encrypted_attributes[:credentials][:mode].class, Proc
|
314
|
+
assert_equal @person.class.encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt
|
315
|
+
assert_nil @person.encrypted_credentials_salt
|
316
|
+
assert_nil @person.encrypted_credentials_iv
|
317
|
+
end
|
318
|
+
|
319
|
+
if ::ActiveRecord::VERSION::STRING > "3.1"
|
320
|
+
def test_should_allow_assign_attributes_with_nil
|
321
|
+
@person = Person.new
|
322
|
+
assert_nil(@person.assign_attributes nil)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def test_that_alias_encrypts_column
|
327
|
+
user = PersonUsingAlias.new
|
328
|
+
user.email = 'test@example.com'
|
329
|
+
user.save
|
330
|
+
|
331
|
+
refute_nil user.encrypted_email
|
332
|
+
refute_equal user.email, user.encrypted_email
|
333
|
+
assert_equal user.email, PersonUsingAlias.first.email
|
334
|
+
end
|
335
|
+
|
336
|
+
# See https://github.com/attr-encrypted/attr_encrypted/issues/68
|
337
|
+
def test_should_invalidate_virtual_attributes_on_reload
|
338
|
+
old_pm_name = 'Winston Churchill'
|
339
|
+
new_pm_name = 'Neville Chamberlain'
|
340
|
+
pm = PrimeMinister.create!(name: old_pm_name)
|
341
|
+
assert_equal old_pm_name, pm.name
|
342
|
+
pm.name = new_pm_name
|
343
|
+
assert_equal new_pm_name, pm.name
|
344
|
+
|
345
|
+
result = pm.reload
|
346
|
+
assert_equal pm, result
|
347
|
+
assert_equal old_pm_name, pm.name
|
348
|
+
end
|
349
|
+
|
350
|
+
def test_should_save_encrypted_data_as_binary
|
351
|
+
street = '123 Elm'
|
352
|
+
address = Address.create!(street: street)
|
353
|
+
refute_equal address.encrypted_street, street
|
354
|
+
assert_equal Address.first.street, street
|
355
|
+
end
|
356
|
+
|
357
|
+
def test_should_evaluate_proc_based_mode
|
358
|
+
street = '123 Elm'
|
359
|
+
zipcode = '12345'
|
360
|
+
address = Address.create(street: street, zipcode: zipcode, mode: :single_iv_and_salt)
|
361
|
+
address.reload
|
362
|
+
refute_equal address.encrypted_zipcode, zipcode
|
363
|
+
assert_equal address.zipcode, zipcode
|
364
|
+
end
|
365
|
+
end
|