diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.27.text_queries.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.27.text_queries.test.ts index b307bb489c..9404e54acd 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.27.text_queries.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.27.text_queries.test.ts @@ -9,6 +9,7 @@ import * as semver from 'semver'; import { getCSFLEKMSProviders } from '../../csfle-kms-providers'; import { ClientEncryption, type MongoClient, MongoDBCollectionNamespace } from '../../mongodb'; +import { getEncryptExtraOptions } from '../../tools/utils'; // Cases 1-4: prefix/suffix GA requires server 9.0+ (SERVER-123416) and libmongocrypt 1.19.0+ (MONGOCRYPT-870). const metadata: MongoDBMetadataUI = { @@ -55,6 +56,7 @@ describe('27. String Explicit Encryption', function () { let keyVaultClient: MongoClient; let clientEncryption: ClientEncryption; let explicitEncryptedClient: MongoClient; + let autoEncryptedClient: MongoClient; beforeEach(async function () { utilClient = this.configuration.newClient(); @@ -76,6 +78,8 @@ describe('27. String Explicit Encryption', function () { // - `db.prefix-suffix` using the `encryptedFields` option set to the contents of // encryptedFields-prefix-suffix.json. This step requires server 9.0.0+. + // - `db.prefix-suffix-ci-di` using the `encryptedFields` option set to the contents of + // encryptedFields-prefix-suffix-ci-di.json. This step requires server 9.0.0+. // - `db.prefix-suffix-preview` using the `encryptedFields` option set to the contents of // encryptedFields-prefix-suffix-preview.json. This step requires server pre-9.0.0. if (isServer9OrAbove) { @@ -83,6 +87,10 @@ describe('27. String Explicit Encryption', function () { 'db.prefix-suffix', await loadFLEDataFile('encryptedFields-prefix-suffix.json') ); + await dropAndCreateCollection( + 'db.prefix-suffix-ci-di', + await loadFLEDataFile('encryptedFields-prefix-suffix-ci-di.json') + ); } else { await dropAndCreateCollection( 'db.prefix-suffix-preview', @@ -92,10 +100,16 @@ describe('27. String Explicit Encryption', function () { // - `db.substring` using the `encryptedFields` option set to the contents of // encryptedFields-substring.json + // - `db.substring-ci-di` using the `encryptedFields` option set to the contents of + // encryptedFields-substring-ci-di.json await dropAndCreateCollection( 'db.substring', await loadFLEDataFile('encryptedFields-substring.json') ); + await dropAndCreateCollection( + 'db.substring-ci-di', + await loadFLEDataFile('encryptedFields-substring-ci-di.json') + ); // Load the file key1-document.json as `key1Document`. keyDocument1 = await loadFLEDataFile('keys/key1-document.json'); @@ -143,6 +157,22 @@ describe('27. String Explicit Encryption', function () { } ); + // Create a MongoClient named `autoEncryptedClient` with these `AutoEncryptionOpts`: + // class AutoEncryptionOpts { + // keyVaultNamespace: "keyvault.datakeys", + // kmsProviders: { "local": { "key": } }, + // } + autoEncryptedClient = this.configuration.newClient( + {}, + { + autoEncryption: { + keyVaultNamespace: 'keyvault.datakeys', + kmsProviders: { local: getCSFLEKMSProviders().local }, + extraOptions: getEncryptExtraOptions() + } + } + ); + // Use `clientEncryption` to encrypt the string `"foobarbaz"` with the following `EncryptOpts`: // class EncryptOpts { // keyId : , @@ -229,6 +259,7 @@ describe('27. String Explicit Encryption', function () { await Promise.allSettled([ utilClient.close(), explicitEncryptedClient.close(), + autoEncryptedClient.close(), keyVaultClient.close() ]); }); @@ -741,4 +772,326 @@ describe('27. String Explicit Encryption', function () { expect(error).to.match(/contention factor is required for string algorithm/); }); + + it( + 'Case 8: can find an auto-encrypted case-insensitively indexed document by prefix and suffix', + metadata, + async function () { + // Use `autoEncryptedClient` to insert the following document into `db.prefix-suffix-ci-di` + // with majority write concern: + // { "encryptedText": "BingQiLin" } + await autoEncryptedClient + .db('db') + .collection('prefix-suffix-ci-di') + .insertOne({ encryptedText: 'BingQiLin' }, { writeConcern: { w: 'majority' } }); + + // Use `clientEncryption.encrypt()` to encrypt the string `"bing"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "prefix", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: false, + // diacriticSensitive: false, + // prefix: PrefixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + const encryptedBing = await clientEncryption.encrypt('bing', { + keyId: keyId1, + algorithm: 'String', + queryType: 'prefix', + contentionFactor: 0, + stringOptions: { + caseSensitive: false, + diacriticSensitive: false, + prefix: { strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); + + // Use `explicitEncryptedClient` to run a "find" operation on the `db.prefix-suffix-ci-di` + // collection with the following filter: + // { $expr: { $encStrStartsWith: {input: '$encryptedText', prefix: } } } + // Assert the following document is returned: + // { "encryptedText": "BingQiLin" } + const { + _id: _id1, + __safeContent__: _sc1, + ...prefixResult + } = await explicitEncryptedClient + .db('db') + .collection<{ _id: unknown; encryptedText: Binary; __safeContent__: unknown }>( + 'prefix-suffix-ci-di' + ) + .findOne({ + $expr: { $encStrStartsWith: { input: '$encryptedText', prefix: encryptedBing } } + }); + expect(prefixResult).to.deep.equal({ encryptedText: 'BingQiLin' }); + + // Use `clientEncryption.encrypt()` to encrypt the string `"lin"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "suffix", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: false, + // diacriticSensitive: false, + // suffix: SuffixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + const encryptedLin = await clientEncryption.encrypt('lin', { + keyId: keyId1, + algorithm: 'String', + queryType: 'suffix', + contentionFactor: 0, + stringOptions: { + caseSensitive: false, + diacriticSensitive: false, + suffix: { strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); + + // Use `explicitEncryptedClient` to run a "find" operation on the `db.prefix-suffix-ci-di` + // collection with the following filter: + // { $expr: { $encStrEndsWith: {input: '$encryptedText', suffix: } } } + // Assert the following document is returned: + // { "encryptedText": "BingQiLin" } + const { + _id: _id2, + __safeContent__: _sc2, + ...suffixResult + } = await explicitEncryptedClient + .db('db') + .collection<{ _id: unknown; encryptedText: Binary; __safeContent__: unknown }>( + 'prefix-suffix-ci-di' + ) + .findOne({ $expr: { $encStrEndsWith: { input: '$encryptedText', suffix: encryptedLin } } }); + expect(suffixResult).to.deep.equal({ encryptedText: 'BingQiLin' }); + } + ); + + it( + 'Case 9: can find an auto-encrypted diacritic-insensitively indexed document by prefix and suffix', + metadata, + async function () { + // Use `autoEncryptedClient` to insert the following document into `db.prefix-suffix-ci-di` + // with majority write concern: + // { "encryptedText": "cafébarbäz" } + await autoEncryptedClient + .db('db') + .collection('prefix-suffix-ci-di') + .insertOne({ encryptedText: 'cafébarbäz' }, { writeConcern: { w: 'majority' } }); + + // Use `clientEncryption.encrypt()` to encrypt the string `"cafe"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "prefix", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: false, + // diacriticSensitive: false, + // prefix: PrefixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + const encryptedCafe = await clientEncryption.encrypt('cafe', { + keyId: keyId1, + algorithm: 'String', + queryType: 'prefix', + contentionFactor: 0, + stringOptions: { + caseSensitive: false, + diacriticSensitive: false, + prefix: { strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); + + // Use `explicitEncryptedClient` to run a "find" operation on the `db.prefix-suffix-ci-di` + // collection with the following filter: + // { $expr: { $encStrStartsWith: {input: '$encryptedText', prefix: } } } + // Assert the following document is returned: + // { "encryptedText": "cafébarbäz" } + const { + _id: _id1, + __safeContent__: _sc1, + ...prefixResult + } = await explicitEncryptedClient + .db('db') + .collection<{ _id: unknown; encryptedText: Binary; __safeContent__: unknown }>( + 'prefix-suffix-ci-di' + ) + .findOne({ + $expr: { $encStrStartsWith: { input: '$encryptedText', prefix: encryptedCafe } } + }); + expect(prefixResult).to.deep.equal({ encryptedText: 'cafébarbäz' }); + + // Use `clientEncryption.encrypt()` to encrypt the string `"baz"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "suffix", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: false, + // diacriticSensitive: false, + // suffix: SuffixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + const encryptedBaz = await clientEncryption.encrypt('baz', { + keyId: keyId1, + algorithm: 'String', + queryType: 'suffix', + contentionFactor: 0, + stringOptions: { + caseSensitive: false, + diacriticSensitive: false, + suffix: { strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); + + // Use `explicitEncryptedClient` to run a "find" operation on the `db.prefix-suffix-ci-di` + // collection with the following filter: + // { $expr: { $encStrEndsWith: {input: '$encryptedText', suffix: } } } + // Assert the following document is returned: + // { "encryptedText": "cafébarbäz" } + const { + _id: _id2, + __safeContent__: _sc2, + ...suffixResult + } = await explicitEncryptedClient + .db('db') + .collection<{ _id: unknown; encryptedText: Binary; __safeContent__: unknown }>( + 'prefix-suffix-ci-di' + ) + .findOne({ $expr: { $encStrEndsWith: { input: '$encryptedText', suffix: encryptedBaz } } }); + expect(suffixResult).to.deep.equal({ encryptedText: 'cafébarbäz' }); + } + ); + + it( + 'Case 10: can find an auto-encrypted case-insensitively indexed document by substring', + metadataSubstring, + async function () { + // Use `autoEncryptedClient` to insert the following document into `db.substring-ci-di` + // with majority write concern: + // { "encryptedText": "FooBarBaz" } + await autoEncryptedClient + .db('db') + .collection('substring-ci-di') + .insertOne({ encryptedText: 'FooBarBaz' }, { writeConcern: { w: 'majority' } }); + + // Use `clientEncryption.encrypt()` to encrypt the string `"bar"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "substringPreview", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: false, + // diacriticSensitive: false, + // substring: SubstringOpts { + // strMaxLength: 10, + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + const encryptedBar = await clientEncryption.encrypt('bar', { + keyId: keyId1, + algorithm: 'String', + queryType: 'substringPreview', + contentionFactor: 0, + stringOptions: { + caseSensitive: false, + diacriticSensitive: false, + substring: { strMaxLength: 10, strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); + + // Use `explicitEncryptedClient` to run a "find" operation on the `db.substring-ci-di` + // collection with the following filter: + // { $expr: { $encStrContains: {input: '$encryptedText', substring: } } } + // Assert the following document is returned: + // { "encryptedText": "FooBarBaz" } + const { _id, __safeContent__, ...result } = await explicitEncryptedClient + .db('db') + .collection<{ _id: unknown; encryptedText: Binary; __safeContent__: unknown }>( + 'substring-ci-di' + ) + .findOne({ + $expr: { $encStrContains: { input: '$encryptedText', substring: encryptedBar } } + }); + expect(result).to.deep.equal({ encryptedText: 'FooBarBaz' }); + } + ); + + it( + 'Case 11: can find an auto-encrypted diacritic-insensitively indexed document by substring', + metadataSubstring, + async function () { + // Use `autoEncryptedClient` to insert the following document into `db.substring-ci-di` + // with majority write concern: + // { "encryptedText": "foocafébaz" } + await autoEncryptedClient + .db('db') + .collection('substring-ci-di') + .insertOne({ encryptedText: 'foocafébaz' }, { writeConcern: { w: 'majority' } }); + + // Use `clientEncryption.encrypt()` to encrypt the string `"cafe"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "substringPreview", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: false, + // diacriticSensitive: false, + // substring: SubstringOpts { + // strMaxLength: 10, + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + const encryptedCafe = await clientEncryption.encrypt('cafe', { + keyId: keyId1, + algorithm: 'String', + queryType: 'substringPreview', + contentionFactor: 0, + stringOptions: { + caseSensitive: false, + diacriticSensitive: false, + substring: { strMaxLength: 10, strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); + + // Use `explicitEncryptedClient` to run a "find" operation on the `db.substring-ci-di` + // collection with the following filter: + // { $expr: { $encStrContains: {input: '$encryptedText', substring: } } } + // Assert the following document is returned: + // { "encryptedText": "foocafébaz" } + const { _id, __safeContent__, ...result } = await explicitEncryptedClient + .db('db') + .collection<{ _id: unknown; encryptedText: Binary; __safeContent__: unknown }>( + 'substring-ci-di' + ) + .findOne({ + $expr: { $encStrContains: { input: '$encryptedText', substring: encryptedCafe } } + }); + expect(result).to.deep.equal({ encryptedText: 'foocafébaz' }); + } + ); }); diff --git a/test/spec/client-side-encryption/etc/data/encryptedFields-prefix-suffix-ci-di.json b/test/spec/client-side-encryption/etc/data/encryptedFields-prefix-suffix-ci-di.json new file mode 100644 index 0000000000..3002c642b2 --- /dev/null +++ b/test/spec/client-side-encryption/etc/data/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/test/spec/client-side-encryption/etc/data/encryptedFields-substring-ci-di.json b/test/spec/client-side-encryption/etc/data/encryptedFields-substring-ci-di.json new file mode 100644 index 0000000000..d6aadfb877 --- /dev/null +++ b/test/spec/client-side-encryption/etc/data/encryptedFields-substring-ci-di.json @@ -0,0 +1,31 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "queryType": "substringPreview", + "strMaxLength": { + "$numberInt": "10" + }, + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "contention": 0, + "caseSensitive": false, + "diacriticSensitive": false + } + ] + } + ] +} \ No newline at end of file