From 1e4d9163ee28d636ffa49b76cb0d1c2a5d7d1ad4 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 26 Nov 2025 15:48:58 +0100 Subject: [PATCH 1/2] Add config option for fle types --- lib/mongo/config.rb | 4 ++++ lib/mongo/crypt/auto_decryption_context.rb | 9 +++++++++ lib/mongo/crypt/binding.rb | 2 +- lib/mongo/crypt/context.rb | 10 ++++++++++ lib/mongo/crypt/explicit_decryption_context.rb | 9 +++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/mongo/config.rb b/lib/mongo/config.rb index 73aaedbe25..77c02dca0f 100644 --- a/lib/mongo/config.rb +++ b/lib/mongo/config.rb @@ -27,6 +27,10 @@ module Config # validate the parameters and raise an error if they are invalid. option :validate_update_replace, default: false + # When this flag is set to true, the CSFLE will use Ruby types for + # decryption instead of BSON types. + option :fle_use_ruby_types, default: false + # Set the configuration options. # # @example Set the options. diff --git a/lib/mongo/crypt/auto_decryption_context.rb b/lib/mongo/crypt/auto_decryption_context.rb index 4de9103107..a244eaede3 100644 --- a/lib/mongo/crypt/auto_decryption_context.rb +++ b/lib/mongo/crypt/auto_decryption_context.rb @@ -38,6 +38,15 @@ def initialize(mongocrypt, io, command) Binding.ctx_decrypt_init(self, @command) end + + # Which BSON mode to use when creating documents from the outcome of + # the state machine. The returned value is based on the + # +Mongo::Config.fle_use_ruby_types+ option. + # + # @return [ Symbol, nil ] The BSON mode. + def bson_mode + Mongo::Config.fle_use_ruby_types ? nil : :bson + end end end end diff --git a/lib/mongo/crypt/binding.rb b/lib/mongo/crypt/binding.rb index e729802a8b..027d1f4d06 100644 --- a/lib/mongo/crypt/binding.rb +++ b/lib/mongo/crypt/binding.rb @@ -1216,7 +1216,7 @@ def self.ctx_finalize(context) # TODO since the binary references a C pointer, and ByteBuffer is # written in C in MRI, we could omit a copy of the data by making # ByteBuffer reference the string that is owned by libmongocrypt. - BSON::Document.from_bson(BSON::ByteBuffer.new(binary.to_s), mode: :bson) + BSON::Document.from_bson(BSON::ByteBuffer.new(binary.to_s), mode: context.bson_mode) end # @!method self.mongocrypt_ctx_destroy(ctx) diff --git a/lib/mongo/crypt/context.rb b/lib/mongo/crypt/context.rb index 625c89dc16..999e01a601 100644 --- a/lib/mongo/crypt/context.rb +++ b/lib/mongo/crypt/context.rb @@ -109,6 +109,16 @@ def run_state_machine(timeout_holder) end end + # Which BSON mode to use when creating documents from the outcome of + # the state machine. The default is :bson, which creates BSON::Document + # with BSON types. Subclasses may override this method to + # change this behavior. + # + # @return [ Symbol, nil ] The BSON mode. + def bson_mode + :bson + end + private def provide_markings(timeout_ms) diff --git a/lib/mongo/crypt/explicit_decryption_context.rb b/lib/mongo/crypt/explicit_decryption_context.rb index c1d4bc4ca9..67003a199f 100644 --- a/lib/mongo/crypt/explicit_decryption_context.rb +++ b/lib/mongo/crypt/explicit_decryption_context.rb @@ -38,6 +38,15 @@ def initialize(mongocrypt, io, doc) # explicit decryption Binding.ctx_explicit_decrypt_init(self, doc) end + + # Which BSON mode to use when creating documents from the outcome of + # the state machine. The returned value is based on the + # +Mongo::Config.fle_use_ruby_types+ option. + # + # @return [ Symbol, nil ] The BSON mode. + def bson_mode + Mongo::Config.fle_use_ruby_types ? nil : :bson + end end end end From 7a4c1720091b74571b59b90f86b9fc73af2a9673 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 27 Nov 2025 11:00:29 +0100 Subject: [PATCH 2/2] Add tests --- lib/mongo.rb | 1 + lib/mongo/config.rb | 2 +- lib/mongo/crypt/auto_decryption_context.rb | 4 +- .../crypt/explicit_decryption_context.rb | 4 +- .../auto_encryption_type_conversion_spec.rb | 69 +++++++++++++++++++ .../explicit_encryption_spec.rb | 44 ++++++++++-- 6 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 spec/integration/client_side_encryption/auto_encryption_type_conversion_spec.rb diff --git a/lib/mongo.rb b/lib/mongo.rb index c866ad1a9e..0bf2634b3f 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -100,6 +100,7 @@ def self.delegate_option(obj, opt) delegate_option Config, :broken_view_aggregate delegate_option Config, :broken_view_options delegate_option Config, :validate_update_replace + delegate_option Config, :csfle_convert_to_ruby_types end # Clears the driver's OCSP response cache. diff --git a/lib/mongo/config.rb b/lib/mongo/config.rb index 77c02dca0f..7dd0f9248e 100644 --- a/lib/mongo/config.rb +++ b/lib/mongo/config.rb @@ -29,7 +29,7 @@ module Config # When this flag is set to true, the CSFLE will use Ruby types for # decryption instead of BSON types. - option :fle_use_ruby_types, default: false + option :csfle_convert_to_ruby_types, default: false # Set the configuration options. # diff --git a/lib/mongo/crypt/auto_decryption_context.rb b/lib/mongo/crypt/auto_decryption_context.rb index a244eaede3..7a527153cf 100644 --- a/lib/mongo/crypt/auto_decryption_context.rb +++ b/lib/mongo/crypt/auto_decryption_context.rb @@ -41,11 +41,11 @@ def initialize(mongocrypt, io, command) # Which BSON mode to use when creating documents from the outcome of # the state machine. The returned value is based on the - # +Mongo::Config.fle_use_ruby_types+ option. + # +Mongo::Config.csfle_convert_to_ruby_types+ option. # # @return [ Symbol, nil ] The BSON mode. def bson_mode - Mongo::Config.fle_use_ruby_types ? nil : :bson + Mongo::Config.csfle_convert_to_ruby_types ? nil : :bson end end end diff --git a/lib/mongo/crypt/explicit_decryption_context.rb b/lib/mongo/crypt/explicit_decryption_context.rb index 67003a199f..6b704d1ec2 100644 --- a/lib/mongo/crypt/explicit_decryption_context.rb +++ b/lib/mongo/crypt/explicit_decryption_context.rb @@ -41,11 +41,11 @@ def initialize(mongocrypt, io, doc) # Which BSON mode to use when creating documents from the outcome of # the state machine. The returned value is based on the - # +Mongo::Config.fle_use_ruby_types+ option. + # +Mongo::Config.csfle_convert_to_ruby_types+ option. # # @return [ Symbol, nil ] The BSON mode. def bson_mode - Mongo::Config.fle_use_ruby_types ? nil : :bson + Mongo::Config.csfle_convert_to_ruby_types ? nil : :bson end end end diff --git a/spec/integration/client_side_encryption/auto_encryption_type_conversion_spec.rb b/spec/integration/client_side_encryption/auto_encryption_type_conversion_spec.rb new file mode 100644 index 0000000000..296e785c4b --- /dev/null +++ b/spec/integration/client_side_encryption/auto_encryption_type_conversion_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Auto Encryption Type Conversion' do + require_libmongocrypt + require_enterprise + min_server_fcv '4.2' + + include_context 'define shared FLE helpers' + include_context 'with local kms_providers' + + let(:client) do + new_local_client( + SpecConfig.instance.addresses, + SpecConfig.instance.test_options.merge( + auto_encryption_options: { + kms_providers: kms_providers, + key_vault_namespace: key_vault_namespace, + schema_map: { 'auto_encryption.users' => schema_map }, + # Spawn mongocryptd on non-default port for sharded cluster tests + extra_options: extra_options, + }, + database: 'auto_encryption' + ) + ) + end + + let(:large_number) { 2**40 } + + let(:ssn) { '123-45-6789' } + + let(:decrypted_doc) do + client['users'].find(_id: 1).first + end + + before do + authorized_client.use('auto_encryption')['users'].drop + + key_vault_collection.drop + key_vault_collection.insert_one(data_key) + + client['users'].insert_one( + _id: 1, + large_number: large_number, + ssn: ssn + ) + end + + context 'when csfle_convert_to_ruby_types is true' do + config_override :csfle_convert_to_ruby_types, true + + it 'returns Int64 as Integer' do + expect(decrypted_doc['ssn']).to eq(ssn) # To check that decryption works + expect(decrypted_doc['large_number']).to be_a(Integer) + expect(decrypted_doc['large_number']).to eq(large_number) + end + end + + context 'when csfle_convert_to_ruby_types is false' do + config_override :csfle_convert_to_ruby_types, false + + it 'returns Int64 as BSON::Int64' do + expect(decrypted_doc['ssn']).to eq(ssn) # To check that decryption works + expect(decrypted_doc['large_number']).to be_a(BSON::Int64) + expect(decrypted_doc['large_number'].value).to eq(large_number) + end + end +end diff --git a/spec/integration/client_side_encryption/explicit_encryption_spec.rb b/spec/integration/client_side_encryption/explicit_encryption_spec.rb index 3d97641786..71bf34c4a2 100644 --- a/spec/integration/client_side_encryption/explicit_encryption_spec.rb +++ b/spec/integration/client_side_encryption/explicit_encryption_spec.rb @@ -28,7 +28,7 @@ client.use(key_vault_db)[key_vault_coll].drop end - shared_examples 'an explicit encrypter' do + shared_examples 'an explicit encrypter' do |decrypted_value| it 'encrypts and decrypts the value using key_id' do data_key_id = client_encryption.create_data_key( kms_provider_name, @@ -44,8 +44,13 @@ ) decrypted = client_encryption.decrypt(encrypted) - expect(decrypted).to eq(value) - expect(decrypted).to be_a_kind_of(value.class) + if decrypted_value.nil? + expect(decrypted).to eq(value) + expect(decrypted).to be_a_kind_of(value.class) + else + expect(decrypted).to eq(decrypted_value) + expect(decrypted).to be_a_kind_of(decrypted_value.class) + end end it 'encrypts and decrypts the value using key_alt_name' do @@ -63,8 +68,13 @@ ) decrypted = client_encryption.decrypt(encrypted) - expect(decrypted).to eq(value) - expect(decrypted).to be_a_kind_of(value.class) + if decrypted_value.nil? + expect(decrypted).to eq(value) + expect(decrypted).to be_a_kind_of(value.class) + else + expect(decrypted).to eq(decrypted_value) + expect(decrypted).to be_a_kind_of(decrypted_value.class) + end end end @@ -140,6 +150,30 @@ end end + context 'values is a large integer that requires BSON::Int64' do + let(:value) { 2**40 } + + context 'when csfle_convert_to_ruby_types is true' do + config_override :csfle_convert_to_ruby_types, true + + context 'with local KMS provider' do + include_context 'with local kms_providers' + + it_behaves_like 'an explicit encrypter' + end + end + + context 'when csfle_convert_to_ruby_types is false' do + config_override :csfle_convert_to_ruby_types, false + + context 'with local KMS provider' do + include_context 'with local kms_providers' + + it_behaves_like 'an explicit encrypter', BSON::Int64.new(2**40) + end + end + end + context 'value is an symbol' do let(:value) { BSON::Symbol::Raw.new(:hello_world) }