diff --git a/.evergreen/config.yml b/.evergreen/config.yml index e7a8e93ae0..5f67bd4f04 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -707,6 +707,10 @@ axes: - id: "mongodb-version" display_name: MongoDB Version values: + - id: "latest" + display_name: "Latest" + variables: + MONGODB_VERSION: "latest" - id: "rapid" display_name: "Rapid" variables: @@ -1471,17 +1475,20 @@ buildvariants: tasks: - name: "test-fle" -# - matrix_name: "fle-latest" -# matrix_spec: -# auth-and-ssl: "noauth-and-nossl" -# ruby: -# topology: [replica-set, sharded-cluster] -# mongodb-version: [ 'latest' ] -# os: ubuntu2204 -# fle: helper -# display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" -# tasks: -# - name: "test-fle" + # Runs FLE specs against the latest (currently 9.0) server so that QE text + # query tests (which require server 9.0+) get real coverage on PRs. + - matrix_name: "fle-latest" + matrix_spec: + auth-and-ssl: "noauth-and-nossl" + ruby: "ruby-4.0" + topology: [replica-set, sharded-cluster] + mongodb-version: [ 'latest' ] + os: ubuntu2204 + fle: helper + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" + tags: ["pr"] + tasks: + - name: "test-fle" # Runs specs against the latest server. Not run on PRs to avoid blocking # merges when an unreleased MongoDB build breaks something. diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index 9b305d1570..60b852ee52 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -2,6 +2,10 @@ axes: - id: "mongodb-version" display_name: MongoDB Version values: + - id: "latest" + display_name: "Latest" + variables: + MONGODB_VERSION: "latest" - id: "rapid" display_name: "Rapid" variables: diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index ea1c3d019c..12f3b47008 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -364,17 +364,20 @@ buildvariants: tasks: - name: "test-fle" -# - matrix_name: "fle-latest" -# matrix_spec: -# auth-and-ssl: "noauth-and-nossl" -# ruby: <%#= latest_ruby %> -# topology: [replica-set, sharded-cluster] -# mongodb-version: [ 'latest' ] -# os: ubuntu2204 -# fle: helper -# display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" -# tasks: -# - name: "test-fle" + # Runs FLE specs against the latest (currently 9.0) server so that QE text + # query tests (which require server 9.0+) get real coverage on PRs. + - matrix_name: "fle-latest" + matrix_spec: + auth-and-ssl: "noauth-and-nossl" + ruby: <%= latest_ruby %> + topology: [replica-set, sharded-cluster] + mongodb-version: [ 'latest' ] + os: ubuntu2204 + fle: helper + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" + tags: ["pr"] + tasks: + - name: "test-fle" # Runs specs against the latest server. Not run on PRs to avoid blocking # merges when an unreleased MongoDB build breaks something. diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index 95b67a0d19..513b50371a 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -68,6 +68,6 @@ def standard_dependencies gem 'ruby-lsp', platforms: :mri end - gem 'libmongocrypt-helper', '~> 1.19.0' if %w[helper mongocryptd].include?(ENV['FLE']) + gem 'libmongocrypt-helper', '~> 1.20.1' if %w[helper mongocryptd].include?(ENV['FLE']) end # rubocop:enable Metrics/MethodLength diff --git a/lib/mongo/client_encryption.rb b/lib/mongo/client_encryption.rb index 105154b05e..ac9cb8c541 100644 --- a/lib/mongo/client_encryption.rb +++ b/lib/mongo/client_encryption.rb @@ -100,15 +100,22 @@ def create_data_key(kms_provider, options = {}) # encryption key. # @option options [ String ] :algorithm The algorithm used to encrypt the value. # Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", - # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed". + # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "String". # @option options [ Integer | nil ] :contention_factor Contention factor - # to be applied if encryption algorithm is set to "Indexed". If not - # provided, it defaults to a value of 0. Contention factor should be set - # only if encryption algorithm is set to "Indexed". + # to be applied if encryption algorithm is set to "Indexed" or "String". + # If not provided, it defaults to a value of 0. Contention factor should be + # set only if encryption algorithm is set to "Indexed" or "String". # @option options [ String | nil ] query_type Query type to be applied - # if encryption algorithm is set to "Indexed". Query type should be set - # only if encryption algorithm is set to "Indexed". The only allowed - # value is "equality". + # if encryption algorithm is set to "Indexed" or "String". Allowed values + # are "equality" (for "Indexed") and "prefix", "suffix", "substring" + # (for "String"). + # @option options [ Hash | nil ] :string_opts Specifies index options for a + # Queryable Encryption field supporting "prefix", "suffix", or "substring" + # queries. Required when algorithm is "String". Allowed options are + # :case_sensitive, :diacritic_sensitive, :prefix, :suffix, :substring. + # + # @note The "substring" query type is unstable and subject to backwards + # breaking changes. # # @note The :key_id and :key_alt_name options are mutually exclusive. Only # one is required to perform explicit encryption. diff --git a/lib/mongo/crypt/binding.rb b/lib/mongo/crypt/binding.rb index 8c8eef6965..38276e56d2 100644 --- a/lib/mongo/crypt/binding.rb +++ b/lib/mongo/crypt/binding.rb @@ -1873,6 +1873,46 @@ def self.ctx_setopt_algorithm_range(context, opts) end end + # @!method self.mongocrypt_ctx_setopt_algorithm_text(ctx, opts) + # @api private + # + # Set options for explicit encryption with the "String" algorithm. + # + # @note The libmongocrypt C function is named `..._algorithm_text` (the + # original "text" name), even though the algorithm string passed to + # mongocrypt_ctx_setopt_algorithm is "String" (renamed for GA). There is + # no `..._algorithm_string` symbol; `..._algorithm_text` is the correct + # and only setter for these options. + # + # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object. + # @param [ FFI::Pointer ] opts A pointer to a string options document. + # + # @return [ Boolean ] Whether setting this option succeeded. + attach_function( + :mongocrypt_ctx_setopt_algorithm_text, + %i[ + pointer + pointer + ], + :bool + ) + + # Set options for explicit encryption with the "String" algorithm. + # + # @param [ Mongo::Crypt::Context ] context + # @param [ Hash ] opts options + # + # @raise [ Mongo::Error::CryptError ] If the operation failed + def self.ctx_setopt_algorithm_text(context, opts) + validate_document(opts) + data = opts.to_bson.to_s + Binary.wrap_string(data) do |data_p| + check_ctx_status(context) do + mongocrypt_ctx_setopt_algorithm_text(context.ctx_p, data_p) + end + end + end + # Raise a Mongo::Error::CryptError based on the status of the underlying # mongocrypt_t object. # diff --git a/lib/mongo/crypt/explicit_encryption_context.rb b/lib/mongo/crypt/explicit_encryption_context.rb index 79acd25c3a..d835495a79 100644 --- a/lib/mongo/crypt/explicit_encryption_context.rb +++ b/lib/mongo/crypt/explicit_encryption_context.rb @@ -36,14 +36,16 @@ class ExplicitEncryptionContext < Context # that will be used to encrypt the value. # @option options [ String ] :algorithm The algorithm used to encrypt the # value. Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", - # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range". + # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range", + # "String". # @option options [ Integer | nil ] :contention_factor Contention factor - # to be applied if encryption algorithm is set to "Indexed". If not - # provided, it defaults to a value of 0. Contention factor should be set - # only if encryption algorithm is set to "Indexed". + # to be applied if encryption algorithm is set to "Indexed", "Range", or "String". + # If not provided, it defaults to a value of 0. Contention factor should be set + # only if encryption algorithm is set to "Indexed", "Range", or "String". # @option options [ String | nil ] query_type Query type to be applied - # if encryption algorithm is set to "Indexed" or "Range". - # Allowed values are "equality" and "range". + # if encryption algorithm is set to "Indexed", "Range", or "String". + # Allowed values are "equality", "range", "prefix", "suffix", and + # "substring". # @option options [ Hash | nil ] :range_opts Specifies index options for # a Queryable Encryption field supporting "range" queries. # Allowed options are: @@ -56,9 +58,22 @@ class ExplicitEncryptionContext < Context # the encryptedFields of the destination collection. # For double and decimal128, min/max/precision must all be set, # or all be unset. + # @option options [ Hash | nil ] :string_opts Specifies index options for + # a Queryable Encryption field supporting "prefix", "suffix", or + # "substring" queries (algorithm "String"). Allowed options are: + # - :case_sensitive + # - :diacritic_sensitive + # - :prefix (Hash with :str_min_query_length, :str_max_query_length) + # - :suffix (Hash with :str_min_query_length, :str_max_query_length) + # - :substring (Hash with :str_max_length, :str_min_query_length, + # :str_max_query_length) + # The options must match the values set in the encryptedFields of the + # destination collection. # # @note The Range algorithm is experimental only. It is not intended for # public use. + # @note The "substring" query type is unstable and subject to backwards + # breaking changes. # # @raise [ ArgumentError|Mongo::Error::CryptError ] If invalid options are provided def initialize(mongocrypt, io, doc, options = {}) @@ -114,20 +129,22 @@ def set_key_alt_name(key_alt_name) def set_algorithm_opts(options) Binding.ctx_setopt_algorithm(self, options[:algorithm]) - if %w[Indexed Range].include?(options[:algorithm]) + if %w[Indexed Range String].include?(options[:algorithm]) Binding.ctx_setopt_contention_factor(self, options[:contention_factor]) if options[:contention_factor] Binding.ctx_setopt_query_type(self, options[:query_type]) if options[:query_type] else if options[:contention_factor] - raise ArgumentError.new(':contention_factor is allowed only for "Indexed" or "Range" algorithms') + raise ArgumentError.new(':contention_factor is allowed only for "Indexed", "Range", or "String" algorithms') end if options[:query_type] - raise ArgumentError.new(':query_type is allowed only for "Indexed" or "Range" algorithms') + raise ArgumentError.new(':query_type is allowed only for "Indexed", "Range", or "String" algorithms') end end - return unless options[:algorithm] == 'Range' - - Binding.ctx_setopt_algorithm_range(self, convert_range_opts(options[:range_opts])) + if options[:algorithm] == 'Range' + Binding.ctx_setopt_algorithm_range(self, convert_range_opts(options[:range_opts])) + elsif options[:algorithm] == 'String' + Binding.ctx_setopt_algorithm_text(self, convert_string_opts(options[:string_opts])) + end end def convert_range_opts(range_opts) @@ -136,6 +153,26 @@ def convert_range_opts(range_opts) opts[:trimFactor] = opts.delete(:trim_factor) if opts[:trim_factor] end end + + def convert_string_opts(string_opts) + raise ArgumentError.new(':string_opts is required for the "String" algorithm') if string_opts.nil? + + string_opts.dup.tap do |opts| + opts[:caseSensitive] = opts.delete(:case_sensitive) if opts.key?(:case_sensitive) + opts[:diacriticSensitive] = opts.delete(:diacritic_sensitive) if opts.key?(:diacritic_sensitive) + %i[substring prefix suffix].each do |query_type| + opts[query_type] = convert_string_query_opts(opts[query_type]) if opts[query_type] + end + end + end + + def convert_string_query_opts(query_opts) + query_opts.dup.tap do |opts| + opts[:strMaxLength] = opts.delete(:str_max_length) if opts.key?(:str_max_length) + opts[:strMinQueryLength] = opts.delete(:str_min_query_length) if opts.key?(:str_min_query_length) + opts[:strMaxQueryLength] = opts.delete(:str_max_query_length) if opts.key?(:str_max_query_length) + end + end end end end diff --git a/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb b/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb index b49f41590c..20e8bad95d 100644 --- a/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb +++ b/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb @@ -19,6 +19,17 @@ 'explicit_encryption' end + # Collection configured with contention 10, used by the non-zero contention + # case. Server 9.0+ requires the payload's contention to not exceed the + # collection's configured contention. + let(:encrypted_coll_c10) do + 'explicit_encryption_c10' + end + + let(:encrypted_fields_c10) do + BSON::ExtJSON.parse(File.read('spec/support/crypt/encrypted_fields/encryptedFields-c10.json')) + end + let(:value) do 'encrypted indexed value' end @@ -61,12 +72,15 @@ before do authorized_client[encrypted_coll].drop(encrypted_fields: encrypted_fields) authorized_client[encrypted_coll].create(encrypted_fields: encrypted_fields) + authorized_client[encrypted_coll_c10].drop(encrypted_fields: encrypted_fields_c10) + authorized_client[encrypted_coll_c10].create(encrypted_fields: encrypted_fields_c10) authorized_client.use(key_vault_db)[key_vault_coll].drop authorized_client.use(key_vault_db)[key_vault_coll, write_concern: { w: :majority }].insert_one(key1_document) end after do authorized_client[encrypted_coll].drop(encrypted_fields: encrypted_fields) + authorized_client[encrypted_coll_c10].drop(encrypted_fields: encrypted_fields_c10) authorized_client.use(key_vault_db)[key_vault_coll].drop end @@ -92,30 +106,20 @@ insert_payload = client_encryption.encrypt( value, key_id: key1_id, algorithm: 'Indexed', contention_factor: 10 ) - encrypted_client[encrypted_coll].insert_one( + encrypted_client[encrypted_coll_c10].insert_one( 'encryptedIndexed' => insert_payload ) end find_payload = client_encryption.encrypt( - value, key_id: key1_id, algorithm: 'Indexed', query_type: 'equality', contention_factor: 0 + value, key_id: key1_id, algorithm: 'Indexed', query_type: 'equality', contention_factor: 10 ) - find_results = encrypted_client[encrypted_coll] + find_results = encrypted_client[encrypted_coll_c10] .find('encryptedIndexed' => find_payload) .to_a - expect(find_results.size).to be < 10 + expect(find_results.size).to eq(10) find_results.each do |doc| expect(doc['encryptedIndexed']).to eq(value) end - find_payload2 = client_encryption.encrypt( - value, key_id: key1_id, algorithm: 'Indexed', query_type: 'equality', contention_factor: 10 - ) - find_results2 = encrypted_client[encrypted_coll] - .find('encryptedIndexed' => find_payload2) - .to_a - expect(find_results2.size).to eq(10) - find_results2.each do |doc| - expect(doc['encryptedIndexed']).to eq(value) - end end it 'can insert encrypted unindexed' do diff --git a/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb b/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb index ef7c63933d..39ff93cd0b 100644 --- a/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb +++ b/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb @@ -232,7 +232,8 @@ { min: BSON::Int32.new(0), max: BSON::Int32.new(200), - sparsity: 1 + sparsity: 1, + trim_factor: 1 } end @@ -278,7 +279,8 @@ { min: BSON::Int64.new(0), max: BSON::Int64.new(200), - sparsity: 1 + sparsity: 1, + trim_factor: 1 } end @@ -325,6 +327,7 @@ min: 0.0, max: 200.0, sparsity: 1, + trim_factor: 1, precision: 2 } end @@ -369,7 +372,8 @@ let(:range_opts) do { - sparsity: 1 + sparsity: 1, + trim_factor: 1 } end @@ -397,12 +401,14 @@ 'Date' end + # The Date encryptedFields uses indexMin/indexMax of 0ms and 200ms since the + # Unix epoch, so map the integer test values to milliseconds after the epoch. let(:value_converter) do proc do |value| if value.is_a?(Array) - value.map { |i| Time.new(i) } + value.map { |i| Time.at(0, i * 1000) } else - Time.new(value) + Time.at(0, value * 1000) end end end @@ -413,16 +419,17 @@ let(:range_opts) do { - min: Time.new(0), - max: Time.new(200), - sparsity: 1 + min: Time.at(0, 0), + max: Time.at(0, 200 * 1000), + sparsity: 1, + trim_factor: 1 } end before do [ 0, 6, 30, 200 ].each_with_index do |num, idx| insert_payload = client_encryption.encrypt( - Time.new(num), + value_converter.call(num), key_id: key1_id, algorithm: 'Range', contention_factor: 0, @@ -464,6 +471,7 @@ min: BSON::Decimal128.new('0.0'), max: BSON::Decimal128.new('200.0'), sparsity: 1, + trim_factor: 1, precision: 2 } end @@ -510,7 +518,8 @@ let(:range_opts) do { - sparsity: 1 + sparsity: 1, + trim_factor: 1 } end diff --git a/spec/integration/client_side_encryption/string_explicit_encryption_prose_spec.rb b/spec/integration/client_side_encryption/string_explicit_encryption_prose_spec.rb new file mode 100644 index 0000000000..280c05bbcc --- /dev/null +++ b/spec/integration/client_side_encryption/string_explicit_encryption_prose_spec.rb @@ -0,0 +1,304 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Prose test 27 "String Explicit Encryption" from the client-side-encryption +# specification. Only the GA parameter sets (server 9.0.0+) are exercised here; +# the "*Preview" query types are not implemented in the driver. +describe 'String Explicit Encryption' do + min_server_version '9.0.0-rc0' + + require_libmongocrypt + include_context 'define shared FLE helpers' + include_context 'with local kms_providers' + + let(:key1_id) { key1_document['_id'] } + + let(:prefix_suffix_fields) do + BSON::ExtJSON.parse(File.read('spec/support/crypt/encrypted_fields/encryptedFields-prefix-suffix.json')) + end + + let(:prefix_suffix_ci_di_fields) do + BSON::ExtJSON.parse(File.read('spec/support/crypt/encrypted_fields/encryptedFields-prefix-suffix-ci-di.json')) + end + + let(:substring_fields) do + BSON::ExtJSON.parse(File.read('spec/support/crypt/encrypted_fields/encryptedFields-substring.json')) + end + + let(:substring_ci_di_fields) do + BSON::ExtJSON.parse(File.read('spec/support/crypt/encrypted_fields/encryptedFields-substring-ci-di.json')) + end + + let(:key_vault_client) do + ClientRegistry.instance.new_local_client(SpecConfig.instance.addresses) + end + + let(:client_encryption) do + Mongo::ClientEncryption.new( + key_vault_client, + kms_tls_options: kms_tls_options, + key_vault_namespace: key_vault_namespace, + kms_providers: local_kms_providers + ) + end + + # Client with auto encryption but query analysis bypassed, used to insert + # explicitly encrypted payloads and to run explicitly encrypted queries. + let(:explicit_encrypted_client) do + ClientRegistry.instance.new_local_client( + SpecConfig.instance.addresses, + auto_encryption_options: { + key_vault_namespace: key_vault_namespace, + kms_providers: local_kms_providers, + bypass_query_analysis: true + }, + database: SpecConfig.instance.test_db + ) + end + + # Client with full auto encryption (query analysis enabled), used to insert + # documents that must be auto-encrypted. + let(:auto_encrypted_client) do + ClientRegistry.instance.new_local_client( + SpecConfig.instance.addresses, + auto_encryption_options: { + key_vault_namespace: key_vault_namespace, + kms_providers: local_kms_providers + }, + database: SpecConfig.instance.test_db + ) + end + + def create_collection(name, fields) + authorized_client[name].drop(encrypted_fields: fields) + authorized_client[name, write_concern: { w: :majority }].create(encrypted_fields: fields) + end + + before do + create_collection('prefix-suffix', prefix_suffix_fields) + create_collection('prefix-suffix-ci-di', prefix_suffix_ci_di_fields) + create_collection('substring', substring_fields) + create_collection('substring-ci-di', substring_ci_di_fields) + + authorized_client.use(key_vault_db)[key_vault_coll].drop + authorized_client.use(key_vault_db)[key_vault_coll, write_concern: { w: :majority }] + .insert_one(key1_document) + + # Insert a document with prefix+suffix indexes. + prefix_suffix_insert = client_encryption.encrypt( + 'foobarbaz', + key_id: key1_id, + algorithm: 'String', + contention_factor: 0, + string_opts: { + case_sensitive: true, + diacritic_sensitive: true, + prefix: { str_max_query_length: 10, str_min_query_length: 2 }, + suffix: { str_max_query_length: 10, str_min_query_length: 2 } + } + ) + explicit_encrypted_client['prefix-suffix', write_concern: { w: :majority }] + .insert_one(_id: 0, encryptedText: prefix_suffix_insert) + + # Insert a document with a substring index. + substring_insert = client_encryption.encrypt( + 'foobarbaz', + key_id: key1_id, + algorithm: 'String', + contention_factor: 0, + string_opts: { + case_sensitive: true, + diacritic_sensitive: true, + substring: { str_max_length: 10, str_max_query_length: 6, str_min_query_length: 2 } + } + ) + explicit_encrypted_client['substring', write_concern: { w: :majority }] + .insert_one(_id: 0, encryptedText: substring_insert) + end + + # Encrypts +value+ for a prefix/suffix query. + def encrypt_prefix_suffix(value, query_type) + client_encryption.encrypt( + value, + key_id: key1_id, + algorithm: 'String', + query_type: query_type, + contention_factor: 0, + string_opts: { + case_sensitive: true, + diacritic_sensitive: true, + query_type.to_sym => { str_max_query_length: 10, str_min_query_length: 2 } + } + ) + end + + def encrypt_substring(value, query_type) + client_encryption.encrypt( + value, + key_id: key1_id, + algorithm: 'String', + query_type: query_type, + contention_factor: 0, + string_opts: { + case_sensitive: true, + diacritic_sensitive: true, + substring: { str_max_length: 10, str_max_query_length: 6, str_min_query_length: 2 } + } + ) + end + + # Encrypts for a case/diacritic-insensitive prefix/suffix query. + def encrypt_prefix_suffix_ci_di(value, query_type) + client_encryption.encrypt( + value, + key_id: key1_id, + algorithm: 'String', + query_type: query_type, + contention_factor: 0, + string_opts: { + case_sensitive: false, + diacritic_sensitive: false, + query_type.to_sym => { str_max_query_length: 10, str_min_query_length: 2 } + } + ) + end + + def encrypt_substring_ci_di(value, query_type) + client_encryption.encrypt( + value, + key_id: key1_id, + algorithm: 'String', + query_type: query_type, + contention_factor: 0, + string_opts: { + case_sensitive: false, + diacritic_sensitive: false, + substring: { str_max_length: 10, str_max_query_length: 6, str_min_query_length: 2 } + } + ) + end + + it 'Case 1: can find a document by prefix' do + encrypted = encrypt_prefix_suffix('foo', 'prefix') + result = explicit_encrypted_client['prefix-suffix'].find( + '$expr' => { '$encStrStartsWith' => { input: '$encryptedText', prefix: encrypted } } + ).to_a + expect(result.map { |doc| doc.slice('_id', 'encryptedText') }) + .to eq([ { '_id' => 0, 'encryptedText' => 'foobarbaz' } ]) + end + + it 'Case 2: can find a document by suffix' do + encrypted = encrypt_prefix_suffix('baz', 'suffix') + result = explicit_encrypted_client['prefix-suffix'].find( + '$expr' => { '$encStrEndsWith' => { input: '$encryptedText', suffix: encrypted } } + ).to_a + expect(result.map { |doc| doc.slice('_id', 'encryptedText') }) + .to eq([ { '_id' => 0, 'encryptedText' => 'foobarbaz' } ]) + end + + it 'Case 3: assert no document found by prefix' do + encrypted = encrypt_prefix_suffix('baz', 'prefix') + result = explicit_encrypted_client['prefix-suffix'].find( + '$expr' => { '$encStrStartsWith' => { input: '$encryptedText', prefix: encrypted } } + ).to_a + expect(result).to be_empty + end + + it 'Case 4: assert no document found by suffix' do + encrypted = encrypt_prefix_suffix('foo', 'suffix') + result = explicit_encrypted_client['prefix-suffix'].find( + '$expr' => { '$encStrEndsWith' => { input: '$encryptedText', suffix: encrypted } } + ).to_a + expect(result).to be_empty + end + + it 'Case 5: can find a document by substring' do + encrypted = encrypt_substring('bar', 'substring') + result = explicit_encrypted_client['substring'].find( + '$expr' => { '$encStrContains' => { input: '$encryptedText', substring: encrypted } } + ).to_a + expect(result.map { |doc| doc.slice('_id', 'encryptedText') }) + .to eq([ { '_id' => 0, 'encryptedText' => 'foobarbaz' } ]) + end + + it 'Case 6: assert no document found by substring' do + encrypted = encrypt_substring('qux', 'substring') + result = explicit_encrypted_client['substring'].find( + '$expr' => { '$encStrContains' => { input: '$encryptedText', substring: encrypted } } + ).to_a + expect(result).to be_empty + end + + it 'Case 7: assert contentionFactor is required' do + expect do + client_encryption.encrypt( + 'foo', + key_id: key1_id, + algorithm: 'String', + query_type: 'prefix', + string_opts: { + case_sensitive: true, + diacritic_sensitive: true, + prefix: { str_max_query_length: 10, str_min_query_length: 2 } + } + ) + end.to raise_error(Mongo::Error::CryptError, /contention factor is required for string algorithm/) + end + + it 'Case 8: can find an auto-encrypted case-insensitively indexed document by prefix and suffix' do + auto_encrypted_client['prefix-suffix-ci-di', write_concern: { w: :majority }] + .insert_one(encryptedText: 'BingQiLin') + + prefix = encrypt_prefix_suffix_ci_di('bing', 'prefix') + result = explicit_encrypted_client['prefix-suffix-ci-di'].find( + '$expr' => { '$encStrStartsWith' => { input: '$encryptedText', prefix: prefix } } + ).to_a + expect(result.map { |doc| doc['encryptedText'] }).to eq([ 'BingQiLin' ]) + + suffix = encrypt_prefix_suffix_ci_di('lin', 'suffix') + result = explicit_encrypted_client['prefix-suffix-ci-di'].find( + '$expr' => { '$encStrEndsWith' => { input: '$encryptedText', suffix: suffix } } + ).to_a + expect(result.map { |doc| doc['encryptedText'] }).to eq([ 'BingQiLin' ]) + end + + it 'Case 9: can find an auto-encrypted diacritic-insensitively indexed document by prefix and suffix' do + auto_encrypted_client['prefix-suffix-ci-di', write_concern: { w: :majority }] + .insert_one(encryptedText: 'cafébarbäz') + + prefix = encrypt_prefix_suffix_ci_di('cafe', 'prefix') + result = explicit_encrypted_client['prefix-suffix-ci-di'].find( + '$expr' => { '$encStrStartsWith' => { input: '$encryptedText', prefix: prefix } } + ).to_a + expect(result.map { |doc| doc['encryptedText'] }).to eq([ 'cafébarbäz' ]) + + suffix = encrypt_prefix_suffix_ci_di('baz', 'suffix') + result = explicit_encrypted_client['prefix-suffix-ci-di'].find( + '$expr' => { '$encStrEndsWith' => { input: '$encryptedText', suffix: suffix } } + ).to_a + expect(result.map { |doc| doc['encryptedText'] }).to eq([ 'cafébarbäz' ]) + end + + it 'Case 10: can find an auto-encrypted case-insensitively indexed document by substring' do + auto_encrypted_client['substring-ci-di', write_concern: { w: :majority }] + .insert_one(encryptedText: 'FooBarBaz') + + substring = encrypt_substring_ci_di('bar', 'substring') + result = explicit_encrypted_client['substring-ci-di'].find( + '$expr' => { '$encStrContains' => { input: '$encryptedText', substring: substring } } + ).to_a + expect(result.map { |doc| doc['encryptedText'] }).to eq([ 'FooBarBaz' ]) + end + + it 'Case 11: can find an auto-encrypted diacritic-insensitively indexed document by substring' do + auto_encrypted_client['substring-ci-di', write_concern: { w: :majority }] + .insert_one(encryptedText: 'foocafébaz') + + substring = encrypt_substring_ci_di('cafe', 'substring') + result = explicit_encrypted_client['substring-ci-di'].find( + '$expr' => { '$encStrContains' => { input: '$encryptedText', substring: substring } } + ).to_a + expect(result.map { |doc| doc['encryptedText'] }).to eq([ 'foocafébaz' ]) + end +end diff --git a/spec/mongo/crypt/explicit_encryption_context_spec.rb b/spec/mongo/crypt/explicit_encryption_context_spec.rb index 46e3d0d8f8..bd21641c87 100644 --- a/spec/mongo/crypt/explicit_encryption_context_spec.rb +++ b/spec/mongo/crypt/explicit_encryption_context_spec.rb @@ -138,7 +138,7 @@ value, options.merge(query_type: 'equality') ) - end.to raise_error(ArgumentError, /query_type is allowed only for "Indexed" or "Range" algorithm/) + end.to raise_error(ArgumentError, /query_type is allowed only for "Indexed", "Range", or "String" algorithms/) end end @@ -153,7 +153,7 @@ value, options.merge(contention_factor: 10) ) - end.to raise_error(ArgumentError, /contention_factor is allowed only for "Indexed" or "Range" algorithm/) + end.to raise_error(ArgumentError, /contention_factor is allowed only for "Indexed", "Range", or "String" algorithms/) end end @@ -203,6 +203,87 @@ end end end + + context 'with String algorithm' do + let(:algorithm) { 'String' } + let(:key_alt_name) { nil } + + let(:string_opts) do + { + case_sensitive: true, + diacritic_sensitive: true, + prefix: { str_min_query_length: 2, str_max_query_length: 10 }, + } + end + + context 'with prefix query_type and string_opts' do + it 'initializes context' do + expect do + described_class.new( + mongocrypt, + io, + value, + options.merge(query_type: 'prefix', contention_factor: 0, string_opts: string_opts) + ) + end.not_to raise_error + end + end + + context 'with suffix query_type and string_opts' do + let(:string_opts) do + { + case_sensitive: false, + diacritic_sensitive: false, + suffix: { str_min_query_length: 2, str_max_query_length: 10 }, + } + end + + it 'initializes context' do + expect do + described_class.new( + mongocrypt, + io, + value, + options.merge(query_type: 'suffix', contention_factor: 0, string_opts: string_opts) + ) + end.not_to raise_error + end + end + + context 'with substring query_type and string_opts' do + let(:string_opts) do + { + case_sensitive: true, + diacritic_sensitive: true, + substring: { str_max_length: 10, str_min_query_length: 2, str_max_query_length: 10 }, + } + end + + it 'initializes context' do + expect do + described_class.new( + mongocrypt, + io, + value, + options.merge(query_type: 'substring', contention_factor: 0, string_opts: string_opts) + ) + end.not_to raise_error + end + end + + context 'without string_opts' do + it 'raises an exception' do + expect do + described_class.new( + mongocrypt, + io, + value, + options.merge(query_type: 'prefix', contention_factor: 0) + ) + end.to raise_error(ArgumentError, /:string_opts is required for the "String" algorithm/) + end + end + end end context 'when mongocrypt is initialized with AWS KMS provider options' do diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index 7860f5a636..c2cf47fcd8 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -375,9 +375,17 @@ def build_session_options(spec) def set_initial_data @spec['initialData']&.each do |entity_spec| spec = UsingHash[entity_spec] - collection = root_authorized_client.with(write_concern: { w: :majority }) - .use(spec.use!('databaseName'))[spec.use!('collectionName')] + database = root_authorized_client.with(write_concern: { w: :majority }) + .use(spec.use!('databaseName')) + collection_name = spec.use!('collectionName') + collection = database[collection_name] collection.drop + # Queryable Encryption stores tag counts in the metadata collections + # enxcol_..esc/.ecoc. They must be dropped alongside the data + # collection so that __safeContent__ is reproducible for exact matching. + # See the unified test format spec, initialData setup. + database["enxcol_.#{collection_name}.esc"].drop + database["enxcol_.#{collection_name}.ecoc"].drop create_options = spec.use('createOptions') || {} docs = spec.use!('documents') begin diff --git a/spec/spec_tests/data/client_side_encryption/unified/QE-Text-cleanupStructuredEncryptionData.yml b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-cleanupStructuredEncryptionData.yml new file mode 100644 index 0000000000..69243bfa4f --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-cleanupStructuredEncryptionData.yml @@ -0,0 +1,128 @@ +description: QE-Text-cleanupStructuredEncryptionData +schemaVersion: "1.25" +runOnRequirements: + - minServerVersion: "9.0.0" # Server 9.0.0 adds stable support for QE text prefix and suffix queries. + topologies: ["replicaset", "sharded", "load-balanced"] # QE does not support standalone. + csfle: + minLibmongocryptVersion: 1.19.0 # For MONGOCRYPT-870. +createEntities: + - client: + id: &client "client" + autoEncryptOpts: + keyVaultNamespace: keyvault.datakeys + kmsProviders: + local: + key: Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk + observeEvents: + - commandStartedEvent + - database: + id: &db "db" + client: *client + databaseName: *db + - collection: + id: &coll "coll" + database: *db + collectionName: *coll +initialData: + # Insert data encryption key: + - databaseName: keyvault + collectionName: datakeys + documents: + [ + { + "_id": &keyid { "$binary": { "base64": "q83vqxI0mHYSNBI0VniQEg==", "subType": "04" } }, + "keyMaterial": + { + "$binary": + { + "base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==", + "subType": "00", + }, + }, + "creationDate": { "$date": { "$numberLong": "1648914851981" } }, + "updateDate": { "$date": { "$numberLong": "1648914851981" } }, + "status": { "$numberInt": "0" }, + "masterKey": { "provider": "local" }, + }, + ] + # Create encrypted collection: + - databaseName: *db + collectionName: *coll + documents: [] + createOptions: + encryptedFields: + { + "fields": + [ + { + "keyId": *keyid, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "queryType": "suffix", + "contention": { "$numberLong": "0" }, + "strMinQueryLength": { "$numberLong": "3" }, + "strMaxQueryLength": { "$numberLong": "30" }, + "caseSensitive": true, + "diacriticSensitive": true, + }, + ], + }, + ], + } +tests: + - description: "QE Text cleanupStructuredEncryptionData works" + operations: + - name: runCommand + object: *db + arguments: + command: + cleanupStructuredEncryptionData: *coll + commandName: cleanupStructuredEncryptionData + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + listCollections: 1 + filter: + name: *coll + commandName: listCollections + - commandStartedEvent: + command: + find: datakeys + filter: + { + "$or": + [ + "_id": { "$in": [ *keyid ] }, + "keyAltNames": { "$in": [] }, + ], + } + $db: keyvault + readConcern: { level: "majority" } + commandName: find + - commandStartedEvent: + command: + { + "cleanupStructuredEncryptionData": *coll, + "cleanupTokens": { + "encryptedText": { + "ecoc": { + "$binary": { + "base64": "SWO8WEoZ2r2Kx/muQKb7+COizy85nIIUFiHh4K9kcvA=", + "subType": "00" + } + }, + "anchorPaddingToken": { + "$binary": { + "base64": "YAiF7Iwhqq1UyfxPvm70xfQJtrIRPrjfD2yRLG1+saQ=", + "subType": "00" + } + } + } + } + } + commandName: cleanupStructuredEncryptionData diff --git a/spec/spec_tests/data/client_side_encryption/unified/QE-Text-compactStructuredEncryptionData.yml b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-compactStructuredEncryptionData.yml new file mode 100644 index 0000000000..4bdc55ee88 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-compactStructuredEncryptionData.yml @@ -0,0 +1,137 @@ +description: QE-Text-compactStructuredEncryptionData +schemaVersion: "1.25" +runOnRequirements: + - minServerVersion: "9.0.0" # Server 9.0.0 adds stable support for QE text prefix and suffix queries. + topologies: ["replicaset", "sharded", "load-balanced"] # QE does not support standalone. + csfle: + minLibmongocryptVersion: 1.19.0 # For MONGOCRYPT-870. +createEntities: + - client: + id: &client "client" + autoEncryptOpts: + keyVaultNamespace: keyvault.datakeys + kmsProviders: + local: + key: Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk + observeEvents: + - commandStartedEvent + - database: + id: &db "db" + client: *client + databaseName: *db + - collection: + id: &coll "coll" + database: *db + collectionName: *coll +initialData: + # Insert data encryption key: + - databaseName: keyvault + collectionName: datakeys + documents: + [ + { + "_id": &keyid { "$binary": { "base64": "q83vqxI0mHYSNBI0VniQEg==", "subType": "04" } }, + "keyMaterial": + { + "$binary": + { + "base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==", + "subType": "00", + }, + }, + "creationDate": { "$date": { "$numberLong": "1648914851981" } }, + "updateDate": { "$date": { "$numberLong": "1648914851981" } }, + "status": { "$numberInt": "0" }, + "masterKey": { "provider": "local" }, + }, + ] + # Create encrypted collection: + - databaseName: *db + collectionName: *coll + documents: [] + createOptions: + encryptedFields: &encryptedFields + { + "fields": + [ + { + "keyId": *keyid, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "queryType": "suffix", + "contention": { "$numberLong": "0" }, + "strMinQueryLength": { "$numberLong": "3" }, + "strMaxQueryLength": { "$numberLong": "30" }, + "caseSensitive": true, + "diacriticSensitive": true, + }, + ], + }, + ], + } +tests: + - description: "QE Text compactStructuredEncryptionData works" + operations: + - name: runCommand + object: *db + arguments: + command: + compactStructuredEncryptionData: *coll + commandName: compactStructuredEncryptionData + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + listCollections: 1 + filter: + name: *coll + commandName: listCollections + - commandStartedEvent: + command: + find: datakeys + filter: + { + "$or": + [ + "_id": { "$in": [ *keyid ] }, + "keyAltNames": { "$in": [] }, + ], + } + $db: keyvault + readConcern: { level: "majority" } + commandName: find + - commandStartedEvent: + command: + compactStructuredEncryptionData: *coll + encryptionInformation: + type: { "$numberInt": "1" } + schema: + db.coll: + <<: *encryptedFields + # libmongocrypt applies strEncodeVersion, escCollection, and ecocCollection: + strEncodeVersion: { "$numberInt": "1" } + escCollection: "enxcol_.coll.esc" + ecocCollection: "enxcol_.coll.ecoc" + compactionTokens: + encryptedText: + ecoc: + { + "$binary": + { + "base64": "SWO8WEoZ2r2Kx/muQKb7+COizy85nIIUFiHh4K9kcvA=", + "subType": "00", + }, + } + anchorPaddingToken: + { + "$binary": + { + "base64": "YAiF7Iwhqq1UyfxPvm70xfQJtrIRPrjfD2yRLG1+saQ=", + "subType": "00", + }, + } + commandName: compactStructuredEncryptionData diff --git a/spec/spec_tests/data/client_side_encryption/unified/QE-Text-prefix.yml b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-prefix.yml new file mode 100644 index 0000000000..7cdc4e45f7 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-prefix.yml @@ -0,0 +1,225 @@ +description: QE-Text-prefix +schemaVersion: "1.25" +runOnRequirements: + - minServerVersion: "9.0.0" # Server 9.0.0 adds stable support for QE text prefix and suffix queries. + topologies: ["replicaset", "sharded", "load-balanced"] # QE does not support standalone. + csfle: + minLibmongocryptVersion: 1.19.0 # For MONGOCRYPT-870. +createEntities: + - client: + id: &client "client" + autoEncryptOpts: + keyVaultNamespace: keyvault.datakeys + kmsProviders: + local: + key: Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk + observeEvents: + - commandStartedEvent + - database: + id: &db "db" + client: *client + databaseName: *db + - collection: + id: &coll "coll" + database: *db + collectionName: *coll +initialData: + # Insert data encryption key: + - databaseName: keyvault + collectionName: datakeys + documents: + [ + { + "_id": &keyid { "$binary": { "base64": "q83vqxI0mHYSNBI0VniQEg==", "subType": "04" } }, + "keyMaterial": + { + "$binary": + { + "base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==", + "subType": "00", + }, + }, + "creationDate": { "$date": { "$numberLong": "1648914851981" } }, + "updateDate": { "$date": { "$numberLong": "1648914851981" } }, + "status": { "$numberInt": "0" }, + "masterKey": { "provider": "local" }, + }, + ] + # Create encrypted collection: + - databaseName: *db + collectionName: *coll + documents: [] + createOptions: + encryptedFields: + { + "fields": + [ + { + "keyId": *keyid, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + # Use zero contention for deterministic __safeContent__: + { + "queryType": "prefix", + "contention": { "$numberLong": "0" }, + "strMinQueryLength": { "$numberLong": "3" }, + "strMaxQueryLength": { "$numberLong": "30" }, + "caseSensitive": true, + "diacriticSensitive": true, + }, + ], + }, + ], + } +tests: + - description: "Insert QE prefix" + operations: + - name: insertOne + arguments: + document: { _id: 1, encryptedText: "foobar" } + object: *coll + expectEvents: + - client: "client" + events: + - commandStartedEvent: + command: + listCollections: 1 + filter: + name: *coll + commandName: listCollections + - commandStartedEvent: + command: + find: datakeys + filter: + { + "$or": + [ + "_id": { "$in": [ *keyid ] }, + "keyAltNames": { "$in": [] }, + ], + } + $db: keyvault + readConcern: { level: "majority" } + commandName: find + - commandStartedEvent: + command: + insert: *coll + documents: + - { "_id": 1, "encryptedText": { $$type: "binData" } } # Sends encrypted payload + ordered: true + commandName: insert + - description: "Query with matching $encStrStartsWith" + operations: + - name: insertOne + arguments: + document: { _id: 1, encryptedText: "foobar" } + object: *coll + - name: find + arguments: + filter: + { + $expr: + { + $encStrStartsWith: { input: "$encryptedText", prefix: "foo" }, + }, + } + object: *coll + expectResult: + [ + { + "_id": { "$numberInt": "1" }, + "encryptedText": "foobar", + "__safeContent__": + [ + { + "$binary": + { + "base64": "wpaMBVDjL4bHf9EtSP52PJFzyNn1R19+iNI/hWtvzdk=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "fmUMXTMV/XRiN0IL3VXxSEn6SQG9E6Po30kJKB8JJlQ=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "vZIDMiFDgjmLNYVrrbnq1zT4hg7sGpe/PMtighSsnRc=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "26Z5G+sHTzV3D7F8Y0m08389USZ2afinyFV3ez9UEBQ=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "q/JEq8of7bE0QE5Id0XuOsNQ4qVpANYymcPQDUL2Ywk=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "Uvvv46LkfbgLoPqZ6xTBzpgoYRTM6FUgRdqZ9eaVojI=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "nMxdq2lladuBJA3lv3JC2MumIUtRJBNJVLp3PVE6nQk=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "hS3V0qq5CF/SkTl3ZWWWgXcAJ8G5yGtkY2RwcHNc5Oc=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "McgwYUxfKj5+4D0vskZymy4KA82s71MR25iV/Enutww=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "Ciqdk1b+t+Vrr6oIlFFk0Zdym5BPmwN3glQ0/VcsVdM=", + "subType": "00", + }, + }, + ], + }, + ] + + - description: "Query with non-matching $encStrStartsWith" + operations: + - name: insertOne + arguments: + document: { _id: 1, encryptedText: "foobar" } + object: *coll + - name: find + arguments: + filter: + { + $expr: + { + $encStrStartsWith: { input: "$encryptedText", prefix: "bar" }, + }, + } + object: *coll + expectResult: [] diff --git a/spec/spec_tests/data/client_side_encryption/unified/QE-Text-substring.yml b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-substring.yml new file mode 100644 index 0000000000..b034c50dd6 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-substring.yml @@ -0,0 +1,374 @@ +description: QE-Text-substring +schemaVersion: "1.25" +runOnRequirements: + - minServerVersion: "9.0.0" # Server 9.0.0 adds stable support for QE text substring queries. + topologies: ["replicaset", "sharded", "load-balanced"] # QE does not support standalone. + csfle: + minLibmongocryptVersion: 1.20.0 # For MONGOCRYPT-936. +createEntities: + - client: + id: &client "client" + autoEncryptOpts: + keyVaultNamespace: keyvault.datakeys + kmsProviders: + local: + key: Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk + observeEvents: + - commandStartedEvent + - database: + id: &db "db" + client: *client + databaseName: *db + - collection: + id: &coll "coll" + database: *db + collectionName: *coll +initialData: + # Insert data encryption key: + - databaseName: keyvault + collectionName: datakeys + documents: + [ + { + "_id": &keyid { "$binary": { "base64": "q83vqxI0mHYSNBI0VniQEg==", "subType": "04" } }, + "keyMaterial": + { + "$binary": + { + "base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==", + "subType": "00", + }, + }, + "creationDate": { "$date": { "$numberLong": "1648914851981" } }, + "updateDate": { "$date": { "$numberLong": "1648914851981" } }, + "status": { "$numberInt": "0" }, + "masterKey": { "provider": "local" }, + }, + ] + # Create encrypted collection: + - databaseName: *db + collectionName: *coll + documents: [] + createOptions: + encryptedFields: + { + "fields": + [ + { + "keyId": *keyid, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + # Use zero contention for deterministic __safeContent__: + { + "queryType": "substring", + "contention": { "$numberLong": "0" }, + "strMinQueryLength": { "$numberLong": "3" }, + "strMaxQueryLength": { "$numberLong": "6" }, + "strMaxLength": { "$numberLong": "20" }, + "caseSensitive": true, + "diacriticSensitive": true, + }, + ], + }, + ], + } +tests: + - description: "Insert QE substring" + operations: + - name: insertOne + arguments: + document: { _id: 1, encryptedText: "foobar" } + object: *coll + expectEvents: + - client: "client" + events: + - commandStartedEvent: + command: + listCollections: 1 + filter: + name: *coll + commandName: listCollections + - commandStartedEvent: + command: + find: datakeys + filter: + { + "$or": + [ + "_id": { "$in": [ *keyid ] }, + "keyAltNames": { "$in": [] }, + ], + } + $db: keyvault + readConcern: { level: "majority" } + commandName: find + - commandStartedEvent: + command: + insert: *coll + documents: + - { "_id": 1, "encryptedText": { $$type: "binData" } } # Sends encrypted payload + ordered: true + commandName: insert + - description: "Query with matching $encStrContains" + operations: + - name: insertOne + arguments: + document: { _id: 1, encryptedText: "foobar" } + object: *coll + - name: find + arguments: + filter: + { + $expr: + { + $encStrContains: + { input: "$encryptedText", substring: "oba" }, + }, + } + object: *coll + expectResult: + [ + { + "_id": { "$numberInt": "1" }, + "encryptedText": "foobar", + "__safeContent__": + [ + { + "$binary": + { + "base64": "wpaMBVDjL4bHf9EtSP52PJFzyNn1R19+iNI/hWtvzdk=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "IpY3x/jjm8j/74jAdUhgxdM5hk68zR0zv/lTKm/72Vg=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "G+ky260C6QiOfIxKz14FmaMbAxvui1BKJO/TnLOHlGk=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "7dv3gAKe9vwJMZmpB40pRCwRTmc7ds9UkGhxH8j084E=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "o0V+Efn6x8XQdE80F1tztNaT3qxHjcsd9DOQ47BtmQk=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "sJvrCjyVot7PIZFsdRehWFANKAj6fmBaj3FLbz/dZLE=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "e98auxFmu02h5MfBIARk29MI7hSmvN3F9DaQ0xjqoEM=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "US83krGNov/ezL6IhsY5eEOCxv1xUPDIEL/nmY0IKi0=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "P2Aq5+OHZPG0CWIdmZvWq9c/18ZKVYW3vbxd+WU/TXU=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "8AdPRPnSzcd5uhq4TZfNvNeF0XjLNVwAsJJMTtktw84=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "9O6u/G51I4ZHFLhL4ZLuudbr0s202A2QnPfThmOXPhI=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "N7AjYVyVlv6+lVSTM+cIxRL3SMgs3G5LgxSs+jrgDkI=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "RbGF7dQbPGYQFd9DDO1hPz1UlLOJ77FAC6NsjGwJeos=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "m7srHMgKm6kZwsNx8rc45pmw0/9Qro6xuQ8lZS3+RYk=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "K75CNU3JyKFqZWPiIsVi4+n7DhYmcPl/nEhQ3d88mVI=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "c7bwGpUZc/7JzEnMS7qQ/TPuXZyrmMihFaAV6zIqbZc=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "rDvEdUgEk8u4Srt3ETokWs2FXcnyJaRGQ+NbkFwi2rQ=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "VcdZj9zfveRBRlpCR2OYWau2+GokOFb73TE3gpElNiU=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "eOa9o2xfA6OgkbYUxd6wQJicaeN6guhy2V66W3ALsaA=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "1xGkJh+um70XiRd8lKLDtyHgDqrf7/59Mg7X0+KZh8k=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "OSvllqHxycbcZN4phR6NDujY3ttA59o7nQJ6V9eJpX0=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "ZTX1pyk8Vdw0BSbJx7GeJNcQf3tGKxbrrNSTqBqUWkg=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "cn7V05zb5iXwYrePGMHztC+GRq+Tj8IMpRDraauPhSE=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "E9bV9KyrZxHJSUmMg0HrDK4gGN+75ruelAnrM6hXQgY=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "WrssTNmdgXoTGpbaF0JLRCGH6cDQuz1XEFNTy98nrb0=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "jZmyOJP35dsxQ/OY5U4ISpVRIYr8iedNfcwZiKt29Qc=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "d2mocORMbX9MX+/itAW8r1kxVw2/uii4vzXtc+2CIRQ=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "JBnJy58eRPhDo3DuZvsHbvQDiHXxdtAx1Eif66k5SfA=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "OjbDulC8s62v0pgweBSsQqtJjJBwH5JinfJpj7nVr+A=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "85i7KT2GP9nSda3Gsil5LKubhq0LDtc22pxBxHpR+nE=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "u9Fvsclwrs9lwIcMPV/fMZD7L3d5anSfJQVjQb9mgLg=", + "subType": "00", + }, + }, + ], + }, + ] + + - description: "Query with non-matching $encStrContains" + operations: + - name: insertOne + arguments: + document: { _id: 1, encryptedText: "foobar" } + object: *coll + - name: find + arguments: + filter: + { + $expr: + { + $encStrContains: { input: "$encryptedText", substring: "blah" }, + }, + } + object: *coll + expectResult: [] diff --git a/spec/spec_tests/data/client_side_encryption/unified/QE-Text-suffix.yml b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-suffix.yml new file mode 100644 index 0000000000..c98cb5e9dc --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/unified/QE-Text-suffix.yml @@ -0,0 +1,221 @@ +description: QE-Text-suffix +schemaVersion: "1.25" +runOnRequirements: + - minServerVersion: "9.0.0" # Server 9.0.0 adds stable support for QE text prefix and suffix queries. + topologies: ["replicaset", "sharded", "load-balanced"] # QE does not support standalone. + csfle: + minLibmongocryptVersion: 1.19.0 # For MONGOCRYPT-870. +createEntities: + - client: + id: &client "client" + autoEncryptOpts: + keyVaultNamespace: keyvault.datakeys + kmsProviders: + local: + key: Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk + observeEvents: + - commandStartedEvent + - database: + id: &db "db" + client: *client + databaseName: *db + - collection: + id: &coll "coll" + database: *db + collectionName: *coll +initialData: + # Insert data encryption key: + - databaseName: keyvault + collectionName: datakeys + documents: + [ + { + "_id": &keyid { "$binary": { "base64": "q83vqxI0mHYSNBI0VniQEg==", "subType": "04" } }, + "keyMaterial": + { + "$binary": + { + "base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==", + "subType": "00", + }, + }, + "creationDate": { "$date": { "$numberLong": "1648914851981" } }, + "updateDate": { "$date": { "$numberLong": "1648914851981" } }, + "status": { "$numberInt": "0" }, + "masterKey": { "provider": "local" }, + }, + ] + # Create encrypted collection: + - databaseName: *db + collectionName: *coll + documents: [] + createOptions: + encryptedFields: + { + "fields": + [ + { + "keyId": *keyid, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + # Use zero contention for deterministic __safeContent__: + { + "queryType": "suffix", + "contention": { "$numberLong": "0" }, + "strMinQueryLength": { "$numberLong": "3" }, + "strMaxQueryLength": { "$numberLong": "30" }, + "caseSensitive": true, + "diacriticSensitive": true, + }, + ], + }, + ], + } +tests: + - description: "Insert QE suffix" + operations: + - name: insertOne + arguments: + document: { _id: 1, encryptedText: "foobar" } + object: *coll + expectEvents: + - client: "client" + events: + - commandStartedEvent: + command: + listCollections: 1 + filter: + name: *coll + commandName: listCollections + - commandStartedEvent: + command: + find: datakeys + filter: + { + "$or": + [ + "_id": { "$in": [ *keyid ] }, + "keyAltNames": { "$in": [] }, + ], + } + $db: keyvault + readConcern: { level: "majority" } + commandName: find + - commandStartedEvent: + command: + insert: *coll + documents: + - { "_id": 1, "encryptedText": { $$type: "binData" } } # Sends encrypted payload + ordered: true + commandName: insert + - description: "Query with matching $encStrEndsWith" + operations: + - name: insertOne + arguments: + document: { _id: 1, encryptedText: "foobar" } + object: *coll + - name: find + arguments: + filter: + { + $expr: + { $encStrEndsWith: { input: "$encryptedText", suffix: "bar" } }, + } + object: *coll + expectResult: + [ + { + "_id": { "$numberInt": "1" }, + "encryptedText": "foobar", + "__safeContent__": + [ + { + "$binary": + { + "base64": "wpaMBVDjL4bHf9EtSP52PJFzyNn1R19+iNI/hWtvzdk=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "uDCWsucUsJemUP7pmeb+Kd8B9qupVzI8wnLFqX1rkiU=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "W3E1x4bHZ8SEHFz4zwXM0G5Z5WSwBhnxE8x5/qdP6JM=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "6g/TXVDDf6z+ntResIvTKWdmIy4ajQ1rhwdNZIiEG7A=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "hU+u/T3D6dHDpT3d/v5AlgtRoAufCXCAyO2jQlgsnCw=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "vrPnq0AtBIURNgNGA6HJL+5/p5SBWe+qz8505TRo/dE=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "W5pylBxdv2soY2NcBfPiHDVLTS6tx+0ULkI8gysBeFY=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "oWO3xX3x0bYUJGK2S1aPAmlU3Xtfsgb9lTZ6flGAlsg=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "SjZGucTEUbdpd86O8yj1pyMyBOOKxvAQ9C8ngZ9C5UE=", + "subType": "00", + }, + }, + { + "$binary": + { + "base64": "CEaMZkxVDVbnXr+To0DOyvsva04UQkIYP3KtgYVVwf8=", + "subType": "00", + }, + }, + ], + }, + ] + + - description: "Query with non-matching $encStrEndsWith" + operations: + - name: insertOne + arguments: + document: { _id: 1, encryptedText: "foobar" } + object: *coll + - name: find + arguments: + filter: + { + $expr: + { $encStrEndsWith: { input: "$encryptedText", suffix: "foo" } }, + } + object: *coll + expectResult: [] diff --git a/spec/support/crypt/encrypted_fields/encryptedFields-c10.json b/spec/support/crypt/encrypted_fields/encryptedFields-c10.json new file mode 100644 index 0000000000..e9ed08bb39 --- /dev/null +++ b/spec/support/crypt/encrypted_fields/encryptedFields-c10.json @@ -0,0 +1,30 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "10" + } + } + }, + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedUnindexed", + "bsonType": "string" + } + ] +} diff --git a/spec/support/crypt/encrypted_fields/encryptedFields-prefix-suffix-ci-di.json b/spec/support/crypt/encrypted_fields/encryptedFields-prefix-suffix-ci-di.json new file mode 100644 index 0000000000..3002c642b2 --- /dev/null +++ b/spec/support/crypt/encrypted_fields/encryptedFields-prefix-suffix-ci-di.json @@ -0,0 +1,40 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "queryType": "prefix", + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "contention": 0, + "caseSensitive": false, + "diacriticSensitive": false + }, + { + "queryType": "suffix", + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "contention": 0, + "caseSensitive": false, + "diacriticSensitive": false + } + ] + } + ] +} diff --git a/spec/support/crypt/encrypted_fields/encryptedFields-prefix-suffix.json b/spec/support/crypt/encrypted_fields/encryptedFields-prefix-suffix.json new file mode 100644 index 0000000000..a96e616723 --- /dev/null +++ b/spec/support/crypt/encrypted_fields/encryptedFields-prefix-suffix.json @@ -0,0 +1,44 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "queryType": "prefix", + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "contention": { + "$numberLong": "0" + }, + "caseSensitive": true, + "diacriticSensitive": true + }, + { + "queryType": "suffix", + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "contention": { + "$numberLong": "0" + }, + "caseSensitive": true, + "diacriticSensitive": true + } + ] + } + ] +} diff --git a/spec/support/crypt/encrypted_fields/encryptedFields-substring-ci-di.json b/spec/support/crypt/encrypted_fields/encryptedFields-substring-ci-di.json new file mode 100644 index 0000000000..657faa5343 --- /dev/null +++ b/spec/support/crypt/encrypted_fields/encryptedFields-substring-ci-di.json @@ -0,0 +1,31 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "queryType": "substring", + "strMaxLength": { + "$numberInt": "10" + }, + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "6" + }, + "contention": 0, + "caseSensitive": false, + "diacriticSensitive": false + } + ] + } + ] +} diff --git a/spec/support/crypt/encrypted_fields/encryptedFields-substring.json b/spec/support/crypt/encrypted_fields/encryptedFields-substring.json new file mode 100644 index 0000000000..5e80f4fc95 --- /dev/null +++ b/spec/support/crypt/encrypted_fields/encryptedFields-substring.json @@ -0,0 +1,33 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "queryType": "substring", + "strMaxLength": { + "$numberInt": "10" + }, + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "6" + }, + "contention": { + "$numberLong": "0" + }, + "caseSensitive": true, + "diacriticSensitive": true + } + ] + } + ] +} diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json index 2966548ef9..defa6e37ff 100644 --- a/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json @@ -1,30 +1,36 @@ { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "range", - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" } + }, + "max": { + "$date": { + "$numberLong": "200" } } } - ] -} + } + ] +} \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json index a061165aeb..dbe28e9c10 100644 --- a/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json @@ -1,21 +1,26 @@ { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "range", - "sparsity": { - "$numberInt": "1" - } + "sparsity": { + "$numberLong": "1" } } - ] - } - \ No newline at end of file + } + ] +} \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json index 6081b3d740..538ab20f0e 100644 --- a/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json @@ -11,9 +11,15 @@ "bsonType": "decimal", "queries": { "queryType": "range", - "sparsity": { + "contention": { + "$numberLong": "0" + }, + "trimFactor": { "$numberInt": "1" }, + "sparsity": { + "$numberLong": "1" + }, "min": { "$numberDecimal": "0.0" }, @@ -26,4 +32,4 @@ } } ] -} +} \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json index b922e1c11b..fb4f46d375 100644 --- a/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json @@ -1,21 +1,26 @@ { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "range", - "sparsity": { - "$numberLong": "1" - } + "sparsity": { + "$numberLong": "1" } } - ] - } - \ No newline at end of file + } + ] +} \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json index fbd984b7eb..07d1c84d6f 100644 --- a/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json @@ -1,30 +1,35 @@ { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "range", - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } + "precision": { + "$numberInt": "2" } } - ] - } - \ No newline at end of file + } + ] +} \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json index a5ba8e93a7..4f0b4854e4 100644 --- a/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json @@ -1,27 +1,32 @@ { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "range", - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } + "max": { + "$numberInt": "200" } } - ] - } - \ No newline at end of file + } + ] +} \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json index e1ac5e03ff..32fe1ea15d 100644 --- a/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json @@ -1,27 +1,32 @@ { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "range", - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } + "max": { + "$numberLong": "200" } } - ] - } - \ No newline at end of file + } + ] +} \ No newline at end of file