Skip to content

Commit

Permalink
Fix encryption of column default values
Browse files Browse the repository at this point in the history
Prior to this commit, encrypted attributes that used column default
values appeared to be encrypted on create, but were not:

  ```ruby
  Book.encrypts :name

  book = Book.create!
  book.name
  # => "<untitled>"
  book.name_before_type_cast
  # => "{\"p\":\"abc..."
  book.reload.name_before_type_cast
  # => "<untitled>"
  ```

This commit ensures attributes with column default values are encrypted:

  ```ruby
  Book.encrypts :name

  book = Book.create!
  book.name
  # => "<untitled>"
  book.name_before_type_cast
  # => "{\"p\":\"abc..."
  book.reload.name_before_type_cast
  # => "{\"p\":\"abc..."
  ```

The existing "support encrypted attributes defined on columns with
default values" test in `encryptable_record_test.rb` shows the intended
behavior, but it was not failing without a `reload`.
  • Loading branch information
jonathanhefner committed Oct 20, 2022
1 parent 0eb42ca commit c48a830
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 4 deletions.
33 changes: 33 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,36 @@
* Fix encryption of column default values.

Previously, encrypted attributes that used column default values appeared to
be encrypted on create, but were not:

```ruby
Book.encrypts :name

book = Book.create!
book.name
# => "<untitled>"
book.name_before_type_cast
# => "{\"p\":\"abc..."
book.reload.name_before_type_cast
# => "<untitled>"
```

Now, attributes with column default values are encrypted:

```ruby
Book.encrypts :name

book = Book.create!
book.name
# => "<untitled>"
book.name_before_type_cast
# => "{\"p\":\"abc..."
book.reload.name_before_type_cast
# => "{\"p\":\"abc..."
```

*Jonathan Hefner*

* Deprecate delegation from `Base` to `connection_handler`.

Calling `Base.clear_all_connections!`, `Base.clear_active_connections!`, `Base.clear_reloadable_connections!` and `Base.flush_idle_connections!` is deprecated. Please call these methods on the connection handler directly. In future Rails versions, the delegation from `Base` to the `connection_handler` will be removed.
Expand Down
2 changes: 1 addition & 1 deletion activerecord/lib/active_record/base.rb
Expand Up @@ -311,6 +311,7 @@ class Base
include Attributes
include Locking::Optimistic
include Locking::Pessimistic
include Encryption::EncryptableRecord
include AttributeMethods
include Callbacks
include Timestamp
Expand All @@ -328,7 +329,6 @@ class Base
include TokenFor
include SignedId
include Suppressor
include Encryption::EncryptableRecord
end

ActiveSupport.run_load_hooks(:active_record, Base)
Expand Down
Expand Up @@ -161,6 +161,15 @@ def decrypt
private
ORIGINAL_ATTRIBUTE_PREFIX = "original_"

def _create_record(attribute_names = self.attribute_names)
if has_encrypted_attributes?
# Always persist encrypted attributes, because an attribute might be
# encrypting a column default value.
attribute_names |= self.class.encrypted_attributes.map(&:to_s)
end
super
end

def encrypt_attributes
validate_encryption_allowed

Expand Down
9 changes: 6 additions & 3 deletions activerecord/test/cases/encryption/helper.rb
Expand Up @@ -10,10 +10,13 @@ class ActiveRecord::Fixture
module ActiveRecord::Encryption
module EncryptionHelpers
def assert_encrypted_attribute(model, attribute_name, expected_value)
encrypted_content = model.ciphertext_for(attribute_name)
assert_not_equal expected_value, encrypted_content
assert_not_equal expected_value, model.ciphertext_for(attribute_name)
assert_equal expected_value, model.public_send(attribute_name)
assert_equal expected_value, model.reload.public_send(attribute_name) unless model.new_record?
unless model.new_record?
model.reload
assert_not_equal expected_value, model.ciphertext_for(attribute_name)
assert_equal expected_value, model.public_send(attribute_name)
end
end

def assert_invalid_key_cant_read_attribute(model, attribute_name)
Expand Down

0 comments on commit c48a830

Please sign in to comment.