mirror of https://github.com/rails/rails
Merge pull request #52729 from djmb/binary-decryption
Fix binary decryption on Postgres
This commit is contained in:
commit
bac5556758
|
@ -1,3 +1,9 @@
|
|||
* Deserialize binary data before decrypting
|
||||
|
||||
This ensures that we call `PG::Connection.unescape_bytea` on PostgreSQL before decryption.
|
||||
|
||||
*Donal McBreen*
|
||||
|
||||
* Ensure `ActiveRecord::Encryption.config` is always ready before access.
|
||||
|
||||
Previously, `ActiveRecord::Encryption` configuration was deferred until `ActiveRecord::Base`
|
||||
|
|
|
@ -100,7 +100,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def decrypt(value)
|
||||
text_to_database_type decrypt_as_text(value)
|
||||
text_to_database_type decrypt_as_text(database_type_to_text(value))
|
||||
end
|
||||
|
||||
def try_to_deserialize_with_previous_encrypted_types(value)
|
||||
|
@ -170,6 +170,15 @@ module ActiveRecord
|
|||
value
|
||||
end
|
||||
end
|
||||
|
||||
def database_type_to_text(value)
|
||||
if value && cast_type.binary?
|
||||
binary_cast_type = cast_type.serialized? ? cast_type.subtype : cast_type
|
||||
binary_cast_type.deserialize(value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,19 +5,22 @@ require "models/author_encrypted"
|
|||
require "models/book_encrypted"
|
||||
require "active_record/encryption/message_pack_message_serializer"
|
||||
|
||||
class ActiveRecord::Encryption::EncryptableRecordTest < ActiveRecord::EncryptionTestCase
|
||||
class ActiveRecord::Encryption::EncryptableRecordMessagePackSerializedTest < ActiveRecord::EncryptionTestCase
|
||||
fixtures :encrypted_books
|
||||
|
||||
test "binary data can be serialized with message pack" do
|
||||
all_bytes = (0..255).map(&:chr).join
|
||||
assert_equal all_bytes, EncryptedBookWithBinaryMessagePackSerialized.create!(logo: all_bytes).logo
|
||||
book = EncryptedBookWithBinaryMessagePackSerialized.create!(logo: all_bytes)
|
||||
assert_encrypted_attribute(book, :logo, all_bytes)
|
||||
end
|
||||
|
||||
test "binary data can be encrypted uncompressed and serialized with message pack" do
|
||||
# Strings below 140 bytes are not compressed
|
||||
low_bytes = (0..127).map(&:chr).join
|
||||
high_bytes = (128..255).map(&:chr).join
|
||||
assert_equal low_bytes, EncryptedBookWithBinaryMessagePackSerialized.create!(logo: low_bytes).logo
|
||||
assert_equal high_bytes, EncryptedBookWithBinaryMessagePackSerialized.create!(logo: high_bytes).logo
|
||||
|
||||
assert_encrypted_attribute(EncryptedBookWithBinaryMessagePackSerialized.create!(logo: low_bytes), :logo, low_bytes)
|
||||
assert_encrypted_attribute(EncryptedBookWithBinaryMessagePackSerialized.create!(logo: high_bytes), :logo, high_bytes)
|
||||
end
|
||||
|
||||
test "text columns cannot be serialized with message pack" do
|
||||
|
|
|
@ -92,6 +92,12 @@ class ActiveRecord::Encryption::EncryptableRecordTest < ActiveRecord::Encryption
|
|||
assert_encrypted_attribute(traffic_light, :state, states)
|
||||
end
|
||||
|
||||
test "encrypts serialized attributes where encrypts is declared first" do
|
||||
states = ["green", "red"]
|
||||
traffic_light = EncryptedFirstTrafficLight.create!(state: states, long_state: states)
|
||||
assert_encrypted_attribute(traffic_light, :state, states)
|
||||
end
|
||||
|
||||
test "encrypts store attributes with accessors" do
|
||||
traffic_light = EncryptedTrafficLightWithStoreState.create!(color: "red", long_state: ["green", "red"])
|
||||
assert_equal "red", traffic_light.color
|
||||
|
@ -404,13 +410,14 @@ class ActiveRecord::Encryption::EncryptableRecordTest < ActiveRecord::Encryption
|
|||
test "binary data can be encrypted uncompressed" do
|
||||
low_bytes = (0..127).map(&:chr).join
|
||||
high_bytes = (128..255).map(&:chr).join
|
||||
assert_equal low_bytes, EncryptedBookWithBinary.create!(logo: low_bytes).logo
|
||||
assert_equal high_bytes, EncryptedBookWithBinary.create!(logo: high_bytes).logo
|
||||
assert_encrypted_attribute EncryptedBookWithBinary.create!(logo: low_bytes), :logo, low_bytes
|
||||
assert_encrypted_attribute EncryptedBookWithBinary.create!(logo: high_bytes), :logo, high_bytes
|
||||
end
|
||||
|
||||
test "serialized binary data can be encrypted" do
|
||||
json_bytes = (32..127).map(&:chr)
|
||||
assert_equal json_bytes, EncryptedBookWithSerializedBinary.create!(logo: json_bytes).logo
|
||||
assert_encrypted_attribute EncryptedBookWithSerializedFirstBinary.create!(logo: json_bytes), :logo, json_bytes
|
||||
assert_encrypted_attribute EncryptedBookWithSerializedSecondBinary.create!(logo: json_bytes), :logo, json_bytes
|
||||
end
|
||||
|
||||
test "can compress data with custom compressor" do
|
||||
|
@ -423,6 +430,17 @@ class ActiveRecord::Encryption::EncryptableRecordTest < ActiveRecord::Encryption
|
|||
assert_equal :text, EncryptedPost.type_for_attribute(:body).type
|
||||
end
|
||||
|
||||
test "encrypts normalized data" do
|
||||
assert_encrypted_attribute EncryptedBookNormalizedFirst.create!(name: "Book"), :name, "book"
|
||||
assert_encrypted_attribute EncryptedBookNormalizedSecond.create!(name: "Book"), :name, "book"
|
||||
assert_encrypted_attribute EncryptedBookNormalizedFirst.create!(logo: "Book"), :logo, "book"
|
||||
assert_encrypted_attribute EncryptedBookNormalizedSecond.create!(logo: "Book"), :logo, "book"
|
||||
end
|
||||
|
||||
test "encrypts attribute data" do
|
||||
assert_encrypted_attribute EncryptedBookAttribute.create!(name: "2024-01-01"), :name, Date.new(2024, 1, 1)
|
||||
end
|
||||
|
||||
private
|
||||
def build_derived_key_provider_with(hash_digest_class)
|
||||
ActiveRecord::Encryption.with_encryption_context(key_generator: ActiveRecord::Encryption::KeyGenerator.new(hash_digest_class: hash_digest_class)) do
|
||||
|
|
|
@ -24,6 +24,31 @@ class EncryptedBookWithDowncaseName < ActiveRecord::Base
|
|||
encrypts :name, deterministic: true, downcase: true
|
||||
end
|
||||
|
||||
class EncryptedBookNormalizedFirst < ActiveRecord::Base
|
||||
self.table_name = "encrypted_books"
|
||||
|
||||
normalizes :name, with: ->(value) { value.to_s.downcase }
|
||||
encrypts :name
|
||||
normalizes :logo, with: ->(value) { value.to_s.downcase }
|
||||
encrypts :logo
|
||||
end
|
||||
|
||||
class EncryptedBookNormalizedSecond < ActiveRecord::Base
|
||||
self.table_name = "encrypted_books"
|
||||
|
||||
encrypts :name
|
||||
normalizes :name, with: ->(value) { value.to_s.downcase }
|
||||
encrypts :logo
|
||||
normalizes :logo, with: ->(value) { value.to_s.downcase }
|
||||
end
|
||||
|
||||
class EncryptedBookAttribute < ActiveRecord::Base
|
||||
self.table_name = "encrypted_books"
|
||||
|
||||
attribute :name, :date
|
||||
encrypts :name
|
||||
end
|
||||
|
||||
class EncryptedBookThatIgnoresCase < ActiveRecord::Base
|
||||
self.table_name = "encrypted_books"
|
||||
|
||||
|
@ -50,13 +75,20 @@ class EncryptedBookWithBinary < ActiveRecord::Base
|
|||
encrypts :logo
|
||||
end
|
||||
|
||||
class EncryptedBookWithSerializedBinary < ActiveRecord::Base
|
||||
class EncryptedBookWithSerializedFirstBinary < ActiveRecord::Base
|
||||
self.table_name = "encrypted_books"
|
||||
|
||||
serialize :logo, coder: JSON
|
||||
encrypts :logo
|
||||
end
|
||||
|
||||
class EncryptedBookWithSerializedSecondBinary < ActiveRecord::Base
|
||||
self.table_name = "encrypted_books"
|
||||
|
||||
encrypts :logo
|
||||
serialize :logo, coder: JSON
|
||||
end
|
||||
|
||||
class EncryptedBookWithCustomCompressor < ActiveRecord::Base
|
||||
module CustomCompressor
|
||||
def self.deflate(value)
|
||||
|
|
|
@ -7,6 +7,14 @@ class EncryptedTrafficLight < TrafficLight
|
|||
encrypts :state
|
||||
end
|
||||
|
||||
class EncryptedFirstTrafficLight < ActiveRecord::Base
|
||||
self.table_name = "traffic_lights"
|
||||
|
||||
encrypts :state
|
||||
serialize :state, type: Array
|
||||
serialize :long_state, type: Array
|
||||
end
|
||||
|
||||
class EncryptedTrafficLightWithStoreState < TrafficLight
|
||||
store :state, accessors: %i[ color ], coder: ActiveRecord::Coders::JSON
|
||||
encrypts :state
|
||||
|
|
Loading…
Reference in New Issue