diff --git a/package-lock.json b/package-lock.json index a880adc69fe..29429837b42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "js-yaml": "^4.1.0", "mocha": "^11.7.5", "mocha-sinon": "^2.1.2", - "mongodb-client-encryption": "^7.0.0", + "mongodb-client-encryption": "^7.1.0", "nyc": "^17.1.0", "prettier": "^3.6.2", "semver": "^7.7.2", @@ -72,7 +72,7 @@ "@mongodb-js/zstd": "^7.0.0", "gcp-metadata": "^7.0.1", "kerberos": "^7.0.0", - "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "mongodb-client-encryption": "^7.1.0", "snappy": "^7.3.2", "socks": "^2.8.6" }, @@ -7593,9 +7593,9 @@ } }, "node_modules/mongodb-client-encryption": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-7.0.0.tgz", - "integrity": "sha512-0egSmyCQ31MLdDFH2j5fHnX8OkAWytUC4ZoPuelU0E+lgPQ2/UcpxkYQXF20SW0rCzADIc0qouiULtqAKDs/uQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-7.1.0.tgz", + "integrity": "sha512-zrjbXeAVUOjPR9YlNyVt/B0kBtgWfKSdjhR6m5CIOdRTWnNngnIi8YbePOnyjLl3FfNAJnouiPWh0axYPy2ueg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", diff --git a/package.json b/package.json index 4da3355ea7f..519073df79f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@mongodb-js/zstd": "^7.0.0", "gcp-metadata": "^7.0.1", "kerberos": "^7.0.0", - "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "mongodb-client-encryption": "^7.1.0", "snappy": "^7.3.2", "socks": "^2.8.6" }, @@ -96,7 +96,7 @@ "js-yaml": "^4.1.0", "mocha": "^11.7.5", "mocha-sinon": "^2.1.2", - "mongodb-client-encryption": "^7.0.0", + "mongodb-client-encryption": "^7.1.0", "nyc": "^17.1.0", "prettier": "^3.6.2", "semver": "^7.7.2", diff --git a/src/client-side-encryption/client_encryption.ts b/src/client-side-encryption/client_encryption.ts index 948f27256fb..79d67559e4b 100644 --- a/src/client-side-encryption/client_encryption.ts +++ b/src/client-side-encryption/client_encryption.ts @@ -746,8 +746,16 @@ export class ClientEncryption { expressionMode: boolean, options: ClientEncryptionEncryptOptions ): Promise { - const { algorithm, keyId, keyAltName, contentionFactor, queryType, rangeOptions, textOptions } = - options; + const { + algorithm, + keyId, + keyAltName, + contentionFactor, + queryType, + rangeOptions, + stringOptions, + textOptions + } = options; const contextOptions: ExplicitEncryptionContextOptions = { expressionMode, algorithm @@ -780,8 +788,9 @@ export class ClientEncryption { contextOptions.rangeOptions = serialize(rangeOptions); } - if (typeof textOptions === 'object') { - contextOptions.textOptions = serialize(textOptions); + const resolvedStringOptions = stringOptions ?? textOptions; + if (typeof resolvedStringOptions === 'object') { + contextOptions.textOptions = serialize(resolvedStringOptions); } const valueBuffer = serialize({ v: value }); @@ -815,6 +824,8 @@ export interface ClientEncryptionEncryptOptions { | 'Indexed' | 'Unindexed' | 'Range' + | 'String' + /** @deprecated Use `'String'` instead. */ | 'TextPreview'; /** @@ -833,26 +844,38 @@ export interface ClientEncryptionEncryptOptions { /** * The query type. */ - queryType?: 'equality' | 'range' | 'prefixPreview' | 'suffixPreview' | 'substringPreview'; + queryType?: + | 'equality' + | 'range' + | 'prefix' + | 'suffix' + /** @deprecated Use `'prefix'` instead. */ + | 'prefixPreview' + /** @deprecated Use `'suffix'` instead. */ + | 'suffixPreview' + /** @experimental Public Technical Preview: `substringPreview` is an experimental feature and may break at any time. */ + | 'substringPreview'; /** The index options for a Queryable Encryption field supporting "range" queries.*/ rangeOptions?: RangeOptions; + /** Options for a Queryable Encryption field supporting string queries. Only valid when `algorithm` is `'String'`. */ + stringOptions?: StringQueryOptions; + /** - * Options for a Queryable Encryption field supporting text queries. Only valid when `algorithm` is `TextPreview`. + * Options for a Queryable Encryption field supporting text queries. Only valid when `algorithm` is `'String'`. * - * @experimental Public Technical Preview: `textPreview` is an experimental feature and may break at any time. + * @deprecated Use `stringOptions` instead. */ - textOptions?: TextQueryOptions; + textOptions?: StringQueryOptions; } /** - * Options for a Queryable Encryption field supporting text queries. + * Options for a Queryable Encryption field supporting string queries. * * @public - * @experimental Public Technical Preview: `textPreview` is an experimental feature and may break at any time. */ -export interface TextQueryOptions { +export interface StringQueryOptions { /** Indicates that text indexes for this field are case sensitive */ caseSensitive: boolean; /** Indicates that text indexes for this field are diacritic sensitive. */ @@ -872,6 +895,9 @@ export interface TextQueryOptions { strMinQueryLength: Int32 | number; }; + /** + * @experimental Public Technical Preview: `substring` is an experimental feature and may break at any time. + */ substring?: { /** The maximum allowed length to insert. */ strMaxLength: Int32 | number; @@ -882,6 +908,12 @@ export interface TextQueryOptions { }; } +/** + * @public + * @deprecated Use {@link StringQueryOptions} instead. + */ +export type TextQueryOptions = StringQueryOptions; + /** * @public * @experimental diff --git a/src/index.ts b/src/index.ts index 74803dfa2a0..542a0c17406 100644 --- a/src/index.ts +++ b/src/index.ts @@ -241,6 +241,7 @@ export type { GCPEncryptionKeyOptions, KMIPEncryptionKeyOptions, RangeOptions, + StringQueryOptions, TextQueryOptions } from './client-side-encryption/client_encryption'; export { diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.25.lookup.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.25.lookup.test.ts index 0c873ee1ccd..4de965f3347 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.25.lookup.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.25.lookup.test.ts @@ -1,3 +1,5 @@ +/* https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#25-test-lookup */ + import * as fs from 'node:fs/promises'; import * as path from 'node:path'; @@ -37,7 +39,7 @@ const newEncryptedClient = ({ configuration }: { configuration: TestConfiguratio } ); -describe('$lookup support', defaultMetadata, function () { +describe('25. Test $lookup', defaultMetadata, function () { before(async function () { const mochaTest = { metadata: defaultMetadata }; @@ -83,6 +85,7 @@ describe('$lookup support', defaultMetadata, function () { * db.qe2 with options: { "encryptedFields": ""}. * db.no_schema with no options. * db.no_schema2 with no options. + * db.non_csfle_schema with options: { "validator": { "$jsonSchema": ""}}. * ``` */ const collections = [ @@ -115,6 +118,11 @@ describe('$lookup support', defaultMetadata, function () { name: 'no_schema2', options: {}, document: { no_schema2: 'no_schema2' } + }, + { + name: 'non_csfle_schema', + options: { validator: { $jsonSchema: await readFixture('schema-non-csfle.json') } }, + document: { non_csfle_schema: 'non_csfle_schema' } } ]; @@ -126,6 +134,7 @@ describe('$lookup support', defaultMetadata, function () { unencryptedClient = this.configuration.newClient({}, { writeConcern: { w: 'majority' } }); /** + * Insert documents with encryptedClient: * ``` * {"csfle": "csfle"} into db.csfle * Use the unencrypted client to retrieve it. Assert the csfle field is BSON binary. @@ -137,12 +146,13 @@ describe('$lookup support', defaultMetadata, function () { * Use the unencrypted client to retrieve it. Assert the qe2 field is BSON binary. * {"no_schema": "no_schema"} into db.no_schema * {"no_schema2": "no_schema2"} into db.no_schema2 + * {"non_csfle_schema": "non_csfle_schema"} into db.non_csfle_schema * ``` */ for (const { name, document } of collections) { const { insertedId } = await encryptedClient.db('db').collection(name).insertOne(document); - if (name.startsWith('no_')) continue; + if (name.startsWith('no_') || name === 'non_csfle_schema') continue; expect(await unencryptedClient.db('db').collection(name).findOne(insertedId)) .to.have.property(Object.keys(document)[0]) @@ -325,6 +335,11 @@ describe('$lookup support', defaultMetadata, function () { { requires: { ...defaultMetadata.requires, mongodb: '>=8.1.0' } } ); + // Case 8 expects one of two error messages depending on mongocryptd/crypt_shared and libmongocrypt versions: + // - mongocryptd/crypt_shared <8.2 or libmongocrypt <1.17.0: "not supported" + // - mongocryptd/crypt_shared 8.2+ and libmongocrypt 1.17.0+: + // "Cannot specify both encryptionInformation and csfleEncryptionSchemas unless + // csfleEncryptionSchemas only contains non-encryption JSON schema validators" test( 'Case 8: db.csfle joins db.qe', 'csfle', @@ -339,7 +354,7 @@ describe('$lookup support', defaultMetadata, function () { }, { $project: { _id: 0 } } ], - /not supported/i, + /not supported|Cannot specify both encryptionInformation and csfleEncryptionSchemas/i, { requires: { ...defaultMetadata.requires, mongodb: '>=8.1.0' } } ); @@ -360,4 +375,26 @@ describe('$lookup support', defaultMetadata, function () { /Upgrade/i, { requires: { ...defaultMetadata.requires, mongodb: '>=7.0.0 <8.1.0' } } ); + + // Case 10 requires server 8.2+, mongocryptd/crypt_shared 8.2+, and libmongocrypt 1.17.0+. + test( + 'Case 10: db.qe joins db.non_csfle_schema', + 'qe', + [ + { $match: { qe: 'qe' } }, + { + $lookup: { + from: 'non_csfle_schema', + as: 'matched', + pipeline: [ + { $match: { non_csfle_schema: 'non_csfle_schema' } }, + { $project: { _id: 0, __safeContent__: 0 } } + ] + } + }, + { $project: { _id: 0, __safeContent__: 0 } } + ], + { qe: 'qe', matched: [{ non_csfle_schema: 'non_csfle_schema' }] }, + { requires: { ...defaultMetadata.requires, mongodb: '>=8.2.0' } } + ); }); 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 e6dbc8f45c5..b307bb489c1 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 @@ -1,3 +1,5 @@ +/* https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#27-string-explicit-encryption */ + import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; @@ -7,22 +9,34 @@ import * as semver from 'semver'; import { getCSFLEKMSProviders } from '../../csfle-kms-providers'; import { ClientEncryption, type MongoClient, MongoDBCollectionNamespace } from '../../mongodb'; -// # Server 9.0.0-rc0 removes support for "prefixPreview" and "suffixPreview": SERVER-123416 -const metadataWithoutPreview: MongoDBMetadataUI = { + +// Cases 1-4: prefix/suffix GA requires server 9.0+ (SERVER-123416) and libmongocrypt 1.19.0+ (MONGOCRYPT-870). +const metadata: MongoDBMetadataUI = { + requires: { + clientSideEncryption: '>=6.4.0', + mongodb: '>=9.0.0', + topology: '!single', + libmongocrypt: '>=1.19.0' + } +}; + +// Cases 1-4 preview: prefixPreview/suffixPreview removed in server 9.0.0 (SERVER-123416). +const metadataPreview: MongoDBMetadataUI = { requires: { clientSideEncryption: '>=6.4.0', mongodb: '>=8.2.0 <9.0.0', topology: '!single', - libmongocrypt: '>=1.15.1' + libmongocrypt: '>=1.19.1' } }; + // TODO(NODE-7623): substringPreview contention validation broken on MongoDB 9.0+ (SERVER-91887). -const metadataWithoutSubstringPreview: MongoDBMetadataUI = { +const metadataSubstring: MongoDBMetadataUI = { requires: { clientSideEncryption: '>=6.4.0', mongodb: '>=8.2.0 <9.0.0', topology: '!single', - libmongocrypt: '>=1.15.1' + libmongocrypt: '>=1.18.1' } }; @@ -34,23 +48,20 @@ const loadFLEDataFile = async (filename: string) => { relaxed: false } ); -describe('27. Text Explicit Encryption', function () { +describe('27. String Explicit Encryption', function () { let keyDocument1: Document; let keyId1: Binary; let utilClient: MongoClient; let keyVaultClient: MongoClient; let clientEncryption: ClientEncryption; - let encryptedClient: MongoClient; + let explicitEncryptedClient: MongoClient; beforeEach(async function () { utilClient = this.configuration.newClient(); const isServer9OrAbove = semver.satisfies(this.configuration.version, '>=9.0.0'); - const shouldRunPrefixSuffixTests = !isServer9OrAbove; - // Using QE CreateCollection() and Collection.Drop(), drop and create the following collections with majority write concern: - // - db.prefix-suffix using the encryptedFields option set to the contents of encryptedFields-prefix-suffix.json - // Skip this step if testing server 9.0.0+. - // - db.substring using the encryptedFields option set to the contents of encryptedFields-substring.json + // Using QE CreateCollection() and Collection.Drop(), drop and create the following collections + // with majority write concern: async function dropAndCreateCollection(ns: string, encryptedFields?: Document) { const { db, collection } = MongoDBCollectionNamespace.fromString(ns); await utilClient.db(db).dropCollection(collection, { @@ -63,26 +74,39 @@ describe('27. Text Explicit Encryption', function () { }); } - if (shouldRunPrefixSuffixTests) { + // - `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-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) { await dropAndCreateCollection( 'db.prefix-suffix', await loadFLEDataFile('encryptedFields-prefix-suffix.json') ); + } else { + await dropAndCreateCollection( + 'db.prefix-suffix-preview', + await loadFLEDataFile('encryptedFields-prefix-suffix-preview.json') + ); } + + // - `db.substring` using the `encryptedFields` option set to the contents of + // encryptedFields-substring.json await dropAndCreateCollection( 'db.substring', await loadFLEDataFile('encryptedFields-substring.json') ); - // Load the file key1-document.json as key1Document. + + // Load the file key1-document.json as `key1Document`. keyDocument1 = await loadFLEDataFile('keys/key1-document.json'); - // Read the "_id" field of key1Document as key1ID. + // Read the `"_id"` field of `key1Document` as `key1ID`. keyId1 = keyDocument1._id; - // Drop and create the collection keyvault.datakeys with majority write concern. + // Drop and create the collection `keyvault.datakeys`. await dropAndCreateCollection('keyvault.datakeys'); - // Insert `key1Document` in `keyvault.datakeys` with majority write concern with majority write concern. + // Insert `key1Document` in `keyvault.datakeys` with majority write concern. await utilClient .db('keyvault') .collection('datakeys') @@ -99,142 +123,129 @@ describe('27. Text Explicit Encryption', function () { // } clientEncryption = new ClientEncryption(keyVaultClient, { keyVaultNamespace: 'keyvault.datakeys', - kmsProviders: { - local: getCSFLEKMSProviders().local - } + kmsProviders: { local: getCSFLEKMSProviders().local } }); - // Create a MongoClient named `encryptedClient` with these `AutoEncryptionOpts`: + // Create a MongoClient named `explicitEncryptedClient` with these `AutoEncryptionOpts`: // class AutoEncryptionOpts { // keyVaultNamespace: "keyvault.datakeys", // kmsProviders: { "local": { "key": } }, // bypassQueryAnalysis: true, // } - encryptedClient = this.configuration.newClient( + explicitEncryptedClient = this.configuration.newClient( {}, { autoEncryption: { keyVaultNamespace: 'keyvault.datakeys', - kmsProviders: { - local: getCSFLEKMSProviders().local - }, + kmsProviders: { local: getCSFLEKMSProviders().local }, bypassQueryAnalysis: true } } ); + // Use `clientEncryption` to encrypt the string `"foobarbaz"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: true, + // diacriticSensitive: true, + // prefix: PrefixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // }, + // suffix: SuffixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // }, + // }, + // } + // Use `explicitEncryptedClient` to insert the following document into `db.prefix-suffix` + // (if created) and `db.prefix-suffix-preview` (if created) with majority write concern: + // { "_id": 0, "encryptedText": } { - // Use `clientEncryption` to encrypt the string `"foobarbaz"` with the following `EncryptOpts`: - // class EncryptOpts { - // keyId : , - // algorithm: "TextPreview", - // contentionFactor: 0, - // textOpts: TextOpts { - // caseSensitive: true, - // diacriticSensitive: true, - // prefix: PrefixOpts { - // strMaxQueryLength: 10, - // strMinQueryLength: 2, - // }, - // suffix: SuffixOpts { - // strMaxQueryLength: 10, - // strMinQueryLength: 2, - // }, - // }, - // } const encryptedText = await clientEncryption.encrypt('foobarbaz', { keyId: keyId1, - algorithm: 'TextPreview', + algorithm: 'String', contentionFactor: 0, - textOptions: { + stringOptions: { caseSensitive: true, diacriticSensitive: true, - prefix: { - strMaxQueryLength: 10, - strMinQueryLength: 2 - }, - suffix: { - strMaxQueryLength: 10, - strMinQueryLength: 2 - } + prefix: { strMaxQueryLength: 10, strMinQueryLength: 2 }, + suffix: { strMaxQueryLength: 10, strMinQueryLength: 2 } } }); - - if (shouldRunPrefixSuffixTests) { - // Use `encryptedClient` to insert the following document into `db.prefix-suffix` with majority write concern: - // { "_id": 0, "encryptedText": } - await encryptedClient + if (isServer9OrAbove) { + await explicitEncryptedClient .db('db') .collection<{ _id: number; encryptedText: Binary }>('prefix-suffix') - .insertOne( - { - _id: 0, - encryptedText - }, - { writeConcern: { w: 'majority' } } - ); + .insertOne({ _id: 0, encryptedText }, { writeConcern: { w: 'majority' } }); + } else { + await explicitEncryptedClient + .db('db') + .collection<{ _id: number; encryptedText: Binary }>('prefix-suffix-preview') + .insertOne({ _id: 0, encryptedText }, { writeConcern: { w: 'majority' } }); } } + // Use `clientEncryption` to encrypt the string `"foobarbaz"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: true, + // diacriticSensitive: true, + // substring: SubstringOpts { + // strMaxLength: 10, + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + // Use `explicitEncryptedClient` to insert the following document into `db.substring` with + // majority write concern: + // { "_id": 0, "encryptedText": } { - // Use `clientEncryption` to encrypt the string `"foobarbaz"` with the following `EncryptOpts`: - // class EncryptOpts { - // keyId : , - // algorithm: "TextPreview", - // contentionFactor: 0, - // textOpts: TextOpts { - // caseSensitive: true, - // diacriticSensitive: true, - // substring: SubstringOpts { - // strMaxLength: 10, - // strMaxQueryLength: 10, - // strMinQueryLength: 2, - // } - // }, - // } const encryptedText = await clientEncryption.encrypt('foobarbaz', { keyId: keyId1, - algorithm: 'TextPreview', + algorithm: 'String', contentionFactor: 0, - textOptions: { + stringOptions: { caseSensitive: true, diacriticSensitive: true, - substring: { - strMaxLength: 10, - strMaxQueryLength: 10, - strMinQueryLength: 2 - } + substring: { strMaxLength: 10, strMaxQueryLength: 10, strMinQueryLength: 2 } } }); - - // Use `encryptedClient` to insert the following document into `db.substring` with majority write concern: - // { "_id": 0, "encryptedText": } - await encryptedClient + await explicitEncryptedClient .db('db') .collection<{ _id: number; encryptedText: Binary }>('substring') - .insertOne( - { - _id: 0, - encryptedText - }, - { writeConcern: { w: 'majority' } } - ); + .insertOne({ _id: 0, encryptedText }, { writeConcern: { w: 'majority' } }); } }); afterEach(async function () { - await Promise.allSettled([utilClient.close(), encryptedClient.close(), keyVaultClient.close()]); + await Promise.allSettled([ + utilClient.close(), + explicitEncryptedClient.close(), + keyVaultClient.close() + ]); }); - it('Case 1: can find a document by prefix', metadataWithoutPreview, async function () { - // Skip this test case if testing MongoDB server 9.0.0+. - // Use clientEncryption.encrypt() to encrypt the string "foo" with the following EncryptOpts: + // Run this case multiple times with the following sets of parameters: + // - `queryType=prefix` and `collection=prefix-suffix` + // - Require server 9.0.0+ and libmongocrypt 1.19.0+. + // - `queryType=prefixPreview` and `collection=prefix-suffix-preview` + // - Require server pre-9.0.0 and libmongocrypt 1.19.1+. + context('Case 1: can find a document by prefix', function () { + // `queryType=prefix` and `collection=prefix-suffix` // class EncryptOpts { // keyId : , - // algorithm: "TextPreview", - // queryType: "prefixPreview", + // algorithm: "String", + // queryType: "prefix", // contentionFactor: 0, - // textOpts: TextOpts { + // stringOpts: StringOpts { // caseSensitive: true, // diacriticSensitive: true, // prefix: PrefixOpts { @@ -243,51 +254,103 @@ describe('27. Text Explicit Encryption', function () { // } // }, // } - const encryptedFoo = await clientEncryption.encrypt('foo', { - keyId: keyId1, - algorithm: 'TextPreview', - queryType: 'prefixPreview', - contentionFactor: 0, - textOptions: { - caseSensitive: true, - diacriticSensitive: true, - prefix: { - strMaxQueryLength: 10, - strMinQueryLength: 2 + it('GA', metadata, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"foo"` with the following `EncryptOpts`: + const encryptedFoo = await clientEncryption.encrypt('foo', { + keyId: keyId1, + algorithm: 'String', + queryType: 'prefix', + contentionFactor: 0, + stringOptions: { + caseSensitive: true, + diacriticSensitive: true, + prefix: { strMaxQueryLength: 10, strMinQueryLength: 2 } } - } - }); + }); - // Use encryptedClient to run a "find" operation on the db.prefix-suffix collection with the following filter: - // { $expr: { $encStrStartsWith: {input: '$encryptedText', prefix: } } } + // Use `explicitEncryptedClient` to run a "find" operation on the `db.` collection + // with the following filter: + // { $expr: { $encStrStartsWith: {input: '$encryptedText', prefix: } } } + const filter = { + $expr: { $encStrStartsWith: { input: '$encryptedText', prefix: encryptedFoo } } + }; + const { __safeContent__, ...result } = await explicitEncryptedClient + .db('db') + .collection<{ + _id: number; + encryptedText: Binary; + __safeContent__: unknown; + }>('prefix-suffix') + .findOne(filter); - const filter = { - $expr: { $encStrStartsWith: { input: '$encryptedText', prefix: encryptedFoo } } - }; + // Assert the following document is returned: + // { "_id": 0, "encryptedText": "foobarbaz" } + expect(result).to.deep.equal({ _id: 0, encryptedText: 'foobarbaz' }); + }); - const { __safeContent__, ...result } = await encryptedClient - .db('db') - .collection<{ - _id: number; - encryptedText: Binary; - __safeContent__: any; - }>('prefix-suffix') - .findOne(filter); + // `queryType=prefixPreview` and `collection=prefix-suffix-preview` + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "prefixPreview", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: true, + // diacriticSensitive: true, + // prefix: PrefixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + it('preview', metadataPreview, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"foo"` with the following `EncryptOpts`: + const encryptedFoo = await clientEncryption.encrypt('foo', { + keyId: keyId1, + algorithm: 'String', + queryType: 'prefixPreview', + contentionFactor: 0, + stringOptions: { + caseSensitive: true, + diacriticSensitive: true, + prefix: { strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); - // Assert the following document is returned: - // { "_id": 0, "encryptedText": "foobarbaz" } - expect(result).to.deep.equal({ _id: 0, encryptedText: 'foobarbaz' }); + // Use `explicitEncryptedClient` to run a "find" operation on the `db.` collection + // with the following filter: + // { $expr: { $encStrStartsWith: {input: '$encryptedText', prefix: } } } + const filter = { + $expr: { $encStrStartsWith: { input: '$encryptedText', prefix: encryptedFoo } } + }; + const { __safeContent__, ...result } = await explicitEncryptedClient + .db('db') + .collection<{ + _id: number; + encryptedText: Binary; + __safeContent__: unknown; + }>('prefix-suffix-preview') + .findOne(filter); + + // Assert the following document is returned: + // { "_id": 0, "encryptedText": "foobarbaz" } + expect(result).to.deep.equal({ _id: 0, encryptedText: 'foobarbaz' }); + }); }); - it('Case 2: can find a document by suffix', metadataWithoutPreview, async function () { - // Skip this test case if testing MongoDB server 9.0.0+. - // Use clientEncryption.encrypt() to encrypt the string "baz" with the following EncryptOpts: + // Run this case multiple times with the following sets of parameters: + // - `queryType=suffix` and `collection=prefix-suffix` + // - Require server 9.0.0+ and libmongocrypt 1.19.0+. + // - `queryType=suffixPreview` and `collection=prefix-suffix-preview` + // - Require server pre-9.0.0 and libmongocrypt 1.19.1+. + context('Case 2: can find a document by suffix', function () { + // `queryType=suffix` and `collection=prefix-suffix` // class EncryptOpts { // keyId : , - // algorithm: "TextPreview", - // queryType: "suffixPreview", + // algorithm: "String", + // queryType: "suffix", // contentionFactor: 0, - // textOpts: TextOpts { + // stringOpts: StringOpts { // caseSensitive: true, // diacriticSensitive: true, // suffix: SuffixOpts { @@ -296,50 +359,103 @@ describe('27. Text Explicit Encryption', function () { // } // }, // } - const encryptedBaz = await clientEncryption.encrypt('baz', { - keyId: keyId1, - algorithm: 'TextPreview', - queryType: 'suffixPreview', - contentionFactor: 0, - textOptions: { - caseSensitive: true, - diacriticSensitive: true, - suffix: { - strMaxQueryLength: 10, - strMinQueryLength: 2 + it('GA', metadata, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"baz"` with the following `EncryptOpts`: + const encryptedBaz = await clientEncryption.encrypt('baz', { + keyId: keyId1, + algorithm: 'String', + queryType: 'suffix', + contentionFactor: 0, + stringOptions: { + caseSensitive: true, + diacriticSensitive: true, + suffix: { strMaxQueryLength: 10, strMinQueryLength: 2 } } - } + }); + + // Use `explicitEncryptedClient` to run a "find" operation on the `db.` collection + // with the following filter: + // { $expr: { $encStrEndsWith: {input: '$encryptedText', suffix: } } } + const filter = { + $expr: { $encStrEndsWith: { input: '$encryptedText', suffix: encryptedBaz } } + }; + const { __safeContent__, ...result } = await explicitEncryptedClient + .db('db') + .collection<{ + _id: number; + encryptedText: Binary; + __safeContent__: unknown; + }>('prefix-suffix') + .findOne(filter); + + // Assert the following document is returned: + // { "_id": 0, "encryptedText": "foobarbaz" } + expect(result).to.deep.equal({ _id: 0, encryptedText: 'foobarbaz' }); }); - // Use encryptedClient to run a "find" operation on the db.prefix-suffix collection with the following filter: - // { $expr: { $encStrEndsWith: {input: '$encryptedText', suffix: } } } - const filter = { - $expr: { $encStrEndsWith: { input: '$encryptedText', suffix: encryptedBaz } } - }; + // `queryType=suffixPreview` and `collection=prefix-suffix-preview` + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "suffixPreview", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: true, + // diacriticSensitive: true, + // suffix: SuffixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + it('preview', metadataPreview, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"baz"` with the following `EncryptOpts`: + const encryptedBaz = await clientEncryption.encrypt('baz', { + keyId: keyId1, + algorithm: 'String', + queryType: 'suffixPreview', + contentionFactor: 0, + stringOptions: { + caseSensitive: true, + diacriticSensitive: true, + suffix: { strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); - const { __safeContent__, ...result } = await encryptedClient - .db('db') - .collection<{ - _id: number; - encryptedText: Binary; - __safeContent__: any; - }>('prefix-suffix') - .findOne(filter); + // Use `explicitEncryptedClient` to run a "find" operation on the `db.` collection + // with the following filter: + // { $expr: { $encStrEndsWith: {input: '$encryptedText', suffix: } } } + const filter = { + $expr: { $encStrEndsWith: { input: '$encryptedText', suffix: encryptedBaz } } + }; + const { __safeContent__, ...result } = await explicitEncryptedClient + .db('db') + .collection<{ + _id: number; + encryptedText: Binary; + __safeContent__: unknown; + }>('prefix-suffix-preview') + .findOne(filter); - // Assert the following document is returned: - // { "_id": 0, "encryptedText": "foobarbaz" } - expect(result).to.deep.equal({ _id: 0, encryptedText: 'foobarbaz' }); + // Assert the following document is returned: + // { "_id": 0, "encryptedText": "foobarbaz" } + expect(result).to.deep.equal({ _id: 0, encryptedText: 'foobarbaz' }); + }); }); - it('Case 3: assert no document found by prefix', metadataWithoutPreview, async function () { - // Skip this test case if testing MongoDB server 9.0.0+. - // Use clientEncryption.encrypt() to encrypt the string "baz" with the following EncryptOpts: + // Run this case multiple times with the following sets of parameters: + // - `queryType=prefix` and `collection=prefix-suffix` + // - Require server 9.0.0+ and libmongocrypt 1.19.0+. + // - `queryType=prefixPreview` and `collection=prefix-suffix-preview` + // - Require server pre-9.0.0 and libmongocrypt 1.19.1+. + context('Case 3: assert no document found by prefix', function () { + // `queryType=prefix` and `collection=prefix-suffix` // class EncryptOpts { // keyId : , - // algorithm: "TextPreview", - // queryType: "prefixPreview", + // algorithm: "String", + // queryType: "prefix", // contentionFactor: 0, - // textOpts: TextOpts { + // stringOpts: StringOpts { // caseSensitive: true, // diacriticSensitive: true, // prefix: PrefixOpts { @@ -348,193 +464,258 @@ describe('27. Text Explicit Encryption', function () { // } // }, // } - const encryptedBaz = await clientEncryption.encrypt('baz', { - keyId: keyId1, - algorithm: 'TextPreview', - queryType: 'prefixPreview', - contentionFactor: 0, - textOptions: { - caseSensitive: true, - diacriticSensitive: true, - prefix: { - strMaxQueryLength: 10, - strMinQueryLength: 2 + it('GA', metadata, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"baz"` with the following `EncryptOpts`: + const encryptedBaz = await clientEncryption.encrypt('baz', { + keyId: keyId1, + algorithm: 'String', + queryType: 'prefix', + contentionFactor: 0, + stringOptions: { + caseSensitive: true, + diacriticSensitive: true, + prefix: { strMaxQueryLength: 10, strMinQueryLength: 2 } } - } - }); + }); - // Use encryptedClient to run a "find" operation on the db.prefix-suffix collection with the following filter: - // { $expr: { $encStrStartsWith: {input: '$encryptedText', prefix: } } } - // Assert that no documents are returned. - const filter = { - $expr: { $encStrStartsWith: { input: '$encryptedText', prefix: encryptedBaz } } - }; - expect(await encryptedClient.db('db').collection('prefix-suffix').findOne(filter)).to.be.null; - }); + // Use `explicitEncryptedClient` to run a "find" operation on the `db.` collection + // with the following filter: + // { $expr: { $encStrStartsWith: {input: '$encryptedText', prefix: } } } + const filter = { + $expr: { $encStrStartsWith: { input: '$encryptedText', prefix: encryptedBaz } } + }; - it('Case 4: assert no document found by suffix', metadataWithoutPreview, async function () { - // Skip this test case if testing MongoDB server 9.0.0+. - // Use clientEncryption.encrypt() to encrypt the string "foo" with the following EncryptOpts: + // Assert that no documents are returned. + expect(await explicitEncryptedClient.db('db').collection('prefix-suffix').findOne(filter)).to + .be.null; + }); + + // `queryType=prefixPreview` and `collection=prefix-suffix-preview` // class EncryptOpts { // keyId : , - // algorithm: "TextPreview", - // queryType: "suffixPreview", + // algorithm: "String", + // queryType: "prefixPreview", // contentionFactor: 0, - // textOpts: TextOpts { + // stringOpts: StringOpts { // caseSensitive: true, // diacriticSensitive: true, - // suffix: SuffixOpts { + // prefix: PrefixOpts { // strMaxQueryLength: 10, // strMinQueryLength: 2, // } // }, // } - const encryptedFoo = await clientEncryption.encrypt('foo', { - keyId: keyId1, - algorithm: 'TextPreview', - queryType: 'suffixPreview', - contentionFactor: 0, - textOptions: { - caseSensitive: true, - diacriticSensitive: true, - suffix: { - strMaxQueryLength: 10, - strMinQueryLength: 2 + it('preview', metadataPreview, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"baz"` with the following `EncryptOpts`: + const encryptedBaz = await clientEncryption.encrypt('baz', { + keyId: keyId1, + algorithm: 'String', + queryType: 'prefixPreview', + contentionFactor: 0, + stringOptions: { + caseSensitive: true, + diacriticSensitive: true, + prefix: { strMaxQueryLength: 10, strMinQueryLength: 2 } } - } - }); + }); - const filter = { - $expr: { $encStrEndsWith: { input: '$encryptedText', suffix: encryptedFoo } } - }; + // Use `explicitEncryptedClient` to run a "find" operation on the `db.` collection + // with the following filter: + // { $expr: { $encStrStartsWith: {input: '$encryptedText', prefix: } } } + const filter = { + $expr: { $encStrStartsWith: { input: '$encryptedText', prefix: encryptedBaz } } + }; - const result = await encryptedClient - .db('db') - .collection<{ - _id: number; - encryptedText: Binary; - __safeContent__: any; - }>('prefix-suffix') - .findOne(filter); - expect(result).to.be.null; + // Assert that no documents are returned. + expect( + await explicitEncryptedClient.db('db').collection('prefix-suffix-preview').findOne(filter) + ).to.be.null; + }); }); - it( - 'Case 5: can find a document by substring', - metadataWithoutSubstringPreview, - async function () { - // Use clientEncryption.encrypt() to encrypt the string "bar" with the following EncryptOpts: - // class EncryptOpts { - // keyId : , - // algorithm: "TextPreview", - // queryType: "substringPreview", - // contentionFactor: 0, - // textOpts: TextOpts { - // caseSensitive: true, - // diacriticSensitive: true, - // substring: SubstringOpts { - // strMaxLength: 10, - // strMaxQueryLength: 10, - // strMinQueryLength: 2, - // } - // }, - // } - const encryptedFoo = await clientEncryption.encrypt('bar', { + // Run this case multiple times with the following sets of parameters: + // - `queryType=suffix` and `collection=prefix-suffix` + // - Require server 9.0.0+ and libmongocrypt 1.19.0+. + // - `queryType=suffixPreview` and `collection=prefix-suffix-preview` + // - Require server pre-9.0.0 and libmongocrypt 1.19.1+. + context('Case 4: assert no document found by suffix', function () { + // `queryType=suffix` and `collection=prefix-suffix` + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "suffix", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: true, + // diacriticSensitive: true, + // suffix: SuffixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + it('GA', metadata, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"foo"` with the following `EncryptOpts`: + const encryptedFoo = await clientEncryption.encrypt('foo', { keyId: keyId1, - algorithm: 'TextPreview', - queryType: 'substringPreview', + algorithm: 'String', + queryType: 'suffix', contentionFactor: 0, - textOptions: { + stringOptions: { caseSensitive: true, diacriticSensitive: true, - substring: { - strMaxLength: 10, - strMaxQueryLength: 10, - strMinQueryLength: 2 - } + suffix: { strMaxQueryLength: 10, strMinQueryLength: 2 } } }); - // Use encryptedClient to run a "find" operation on the db.substring collection with the following filter: - // { $expr: { $encStrContains: {input: '$encryptedText', substring: } } } + // Use `explicitEncryptedClient` to run a "find" operation on the `db.` collection + // with the following filter: + // { $expr: { $encStrEndsWith: {input: '$encryptedText', suffix: } } } const filter = { - $expr: { $encStrContains: { input: '$encryptedText', substring: encryptedFoo } } + $expr: { $encStrEndsWith: { input: '$encryptedText', suffix: encryptedFoo } } }; - const { __safeContent__, ...result } = await encryptedClient - .db('db') - .collection<{ - _id: number; - encryptedText: Binary; - __safeContent__: any; - }>('substring') - .findOne(filter); - expect(result).to.deep.equal({ _id: 0, encryptedText: 'foobarbaz' }); - } - ); + // Assert that no documents are returned. + expect(await explicitEncryptedClient.db('db').collection('prefix-suffix').findOne(filter)).to + .be.null; + }); - it( - 'Case 6: assert no document found by substring', - metadataWithoutSubstringPreview, - async function () { - // Use clientEncryption.encrypt() to encrypt the string "bar" with the following EncryptOpts: - // class EncryptOpts { - // keyId : , - // algorithm: "TextPreview", - // queryType: "substringPreview", - // contentionFactor: 0, - // textOpts: TextOpts { - // caseSensitive: true, - // diacriticSensitive: true, - // substring: SubstringOpts { - // strMaxLength: 10, - // strMaxQueryLength: 10, - // strMinQueryLength: 2, - // } - // }, - // } - const encryptedQux = await clientEncryption.encrypt('qux', { + // `queryType=suffixPreview` and `collection=prefix-suffix-preview` + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "suffixPreview", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: true, + // diacriticSensitive: true, + // suffix: SuffixOpts { + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + it('preview', metadataPreview, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"foo"` with the following `EncryptOpts`: + const encryptedFoo = await clientEncryption.encrypt('foo', { keyId: keyId1, - algorithm: 'TextPreview', - queryType: 'substringPreview', + algorithm: 'String', + queryType: 'suffixPreview', contentionFactor: 0, - textOptions: { + stringOptions: { caseSensitive: true, diacriticSensitive: true, - substring: { - strMaxLength: 10, - strMaxQueryLength: 10, - strMinQueryLength: 2 - } + suffix: { strMaxQueryLength: 10, strMinQueryLength: 2 } } }); - // Use encryptedClient to run a "find" operation on the db.substring collection with the following filter: - // { $expr: { $encStrContains: {input: '$encryptedText', substring: } } } + // Use `explicitEncryptedClient` to run a "find" operation on the `db.` collection + // with the following filter: + // { $expr: { $encStrEndsWith: {input: '$encryptedText', suffix: } } } const filter = { - $expr: { $encStrContains: { input: '$encryptedText', substring: encryptedQux } } + $expr: { $encStrEndsWith: { input: '$encryptedText', suffix: encryptedFoo } } }; - const result = await encryptedClient - .db('db') - .collection<{ - _id: number; - encryptedText: Binary; - __safeContent__: any; - }>('substring') - .findOne(filter); - expect(result).to.be.null; - } - ); + // Assert that no documents are returned. + expect( + await explicitEncryptedClient.db('db').collection('prefix-suffix-preview').findOne(filter) + ).to.be.null; + }); + }); - it('Case 7: assert contentionFactor is required', metadataWithoutPreview, async function () { - // Skip this test case if testing MongoDB server 9.0.0+. - // Use clientEncryption.encrypt() to encrypt the string "foo" with the following EncryptOpts: + it('Case 5: can find a document by substring', metadataSubstring, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"bar"` with the following `EncryptOpts`: // class EncryptOpts { // keyId : , - // algorithm: "TextPreview", - // queryType: "prefixPreview", - // textOpts: TextOpts { + // algorithm: "String", + // queryType: "substringPreview", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: true, + // diacriticSensitive: true, + // substring: SubstringOpts { + // strMaxLength: 10, + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + const encryptedBar = await clientEncryption.encrypt('bar', { + keyId: keyId1, + algorithm: 'String', + queryType: 'substringPreview', + contentionFactor: 0, + stringOptions: { + caseSensitive: true, + diacriticSensitive: true, + substring: { strMaxLength: 10, strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); + + // Use `explicitEncryptedClient` to run a "find" operation on the `db.substring` collection + // with the following filter: + // { $expr: { $encStrContains: {input: '$encryptedText', substring: } } } + const filter = { + $expr: { $encStrContains: { input: '$encryptedText', substring: encryptedBar } } + }; + const { __safeContent__, ...result } = await explicitEncryptedClient + .db('db') + .collection<{ _id: number; encryptedText: Binary; __safeContent__: unknown }>('substring') + .findOne(filter); + + // Assert the following document is returned: + // { "_id": 0, "encryptedText": "foobarbaz" } + expect(result).to.deep.equal({ _id: 0, encryptedText: 'foobarbaz' }); + }); + + it('Case 6: assert no document found by substring', metadataSubstring, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"qux"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "substringPreview", + // contentionFactor: 0, + // stringOpts: StringOpts { + // caseSensitive: true, + // diacriticSensitive: true, + // substring: SubstringOpts { + // strMaxLength: 10, + // strMaxQueryLength: 10, + // strMinQueryLength: 2, + // } + // }, + // } + const encryptedQux = await clientEncryption.encrypt('qux', { + keyId: keyId1, + algorithm: 'String', + queryType: 'substringPreview', + contentionFactor: 0, + stringOptions: { + caseSensitive: true, + diacriticSensitive: true, + substring: { strMaxLength: 10, strMaxQueryLength: 10, strMinQueryLength: 2 } + } + }); + + // Use `explicitEncryptedClient` to run a "find" operation on the `db.substring` collection + // with the following filter: + // { $expr: { $encStrContains: {input: '$encryptedText', substring: } } } + const filter = { + $expr: { $encStrContains: { input: '$encryptedText', substring: encryptedQux } } + }; + + // Assert that no documents are returned. + expect(await explicitEncryptedClient.db('db').collection('substring').findOne(filter)).to.be + .null; + }); + + it('Case 7: assert contentionFactor is required', metadata, async function () { + // Use `clientEncryption.encrypt()` to encrypt the string `"foo"` with the following `EncryptOpts`: + // class EncryptOpts { + // keyId : , + // algorithm: "String", + // queryType: "prefix", + // stringOpts: StringOpts { // caseSensitive: true, // diacriticSensitive: true, // prefix: PrefixOpts { @@ -543,23 +724,21 @@ describe('27. Text Explicit Encryption', function () { // } // }, // } - // Expect an error from libmongocrypt with a message containing the string: "contention factor is required for textPreview algorithm". + // Expect an error from libmongocrypt with a message containing the string: + // "contention factor is required for string algorithm". const error = await clientEncryption .encrypt('foo', { keyId: keyId1, - algorithm: 'TextPreview', - queryType: 'prefixPreview', - textOptions: { + algorithm: 'String', + queryType: 'prefix', + stringOptions: { caseSensitive: true, diacriticSensitive: true, - prefix: { - strMaxQueryLength: 10, - strMinQueryLength: 2 - } + prefix: { strMaxQueryLength: 10, strMinQueryLength: 2 } } }) .catch(e => e); - expect(error).to.match(/contention factor is required for textPreview algorithm/); + expect(error).to.match(/contention factor is required for string algorithm/); }); }); diff --git a/test/spec/client-side-encryption/etc/data/encryptedFields-prefix-suffix-preview.json b/test/spec/client-side-encryption/etc/data/encryptedFields-prefix-suffix-preview.json new file mode 100644 index 00000000000..047064beb15 --- /dev/null +++ b/test/spec/client-side-encryption/etc/data/encryptedFields-prefix-suffix-preview.json @@ -0,0 +1,44 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "queryType": "prefixPreview", + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "contention": { + "$numberLong": "0" + }, + "caseSensitive": true, + "diacriticSensitive": true + }, + { + "queryType": "suffixPreview", + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "contention": { + "$numberLong": "0" + }, + "caseSensitive": true, + "diacriticSensitive": true + } + ] + } + ] +} diff --git a/test/spec/client-side-encryption/etc/data/encryptedFields-prefix-suffix.json b/test/spec/client-side-encryption/etc/data/encryptedFields-prefix-suffix.json index e8b9f653b72..a96e6167239 100644 --- a/test/spec/client-side-encryption/etc/data/encryptedFields-prefix-suffix.json +++ b/test/spec/client-side-encryption/etc/data/encryptedFields-prefix-suffix.json @@ -11,28 +11,34 @@ "bsonType": "string", "queries": [ { - "queryType": "prefixPreview", + "queryType": "prefix", "strMinQueryLength": { "$numberInt": "2" }, "strMaxQueryLength": { "$numberInt": "10" }, + "contention": { + "$numberLong": "0" + }, "caseSensitive": true, "diacriticSensitive": true }, { - "queryType": "suffixPreview", + "queryType": "suffix", "strMinQueryLength": { "$numberInt": "2" }, "strMaxQueryLength": { "$numberInt": "10" }, + "contention": { + "$numberLong": "0" + }, "caseSensitive": true, "diacriticSensitive": true } ] } ] -} \ No newline at end of file +} diff --git a/test/spec/client-side-encryption/etc/data/encryptedFields-substring.json b/test/spec/client-side-encryption/etc/data/encryptedFields-substring.json index 8951dd10948..d321a32c5c1 100644 --- a/test/spec/client-side-encryption/etc/data/encryptedFields-substring.json +++ b/test/spec/client-side-encryption/etc/data/encryptedFields-substring.json @@ -1,6 +1,6 @@ { - "fields": [ - { + "fields": [ + { "keyId": { "$binary": { "base64": "EjRWeBI0mHYSNBI0VniQEg==", @@ -21,10 +21,13 @@ "strMaxQueryLength": { "$numberInt": "10" }, + "contention": { + "$numberLong": "0" + }, "caseSensitive": true, "diacriticSensitive": true } ] } - ] -} \ No newline at end of file + ] +} diff --git a/test/spec/client-side-encryption/etc/data/lookup/schema-non-csfle.json b/test/spec/client-side-encryption/etc/data/lookup/schema-non-csfle.json new file mode 100644 index 00000000000..3edd12c8f4c --- /dev/null +++ b/test/spec/client-side-encryption/etc/data/lookup/schema-non-csfle.json @@ -0,0 +1,3 @@ +{ + "bsonType": "object" +} diff --git a/test/spec/client-side-encryption/tests/unified/QE-Text-cleanupStructuredEncryptionData.json b/test/spec/client-side-encryption/tests/unified/QE-Text-cleanupStructuredEncryptionData.json index fd74573ea26..dc979b5019c 100644 --- a/test/spec/client-side-encryption/tests/unified/QE-Text-cleanupStructuredEncryptionData.json +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-cleanupStructuredEncryptionData.json @@ -3,15 +3,14 @@ "schemaVersion": "1.25", "runOnRequirements": [ { - "minServerVersion": "8.2.0", - "maxServerVersion": "8.99.99", + "minServerVersion": "9.0.0", "topologies": [ "replicaset", "sharded", "load-balanced" ], "csfle": { - "minLibmongocryptVersion": "1.15.0" + "minLibmongocryptVersion": "1.19.0" } } ], @@ -102,7 +101,7 @@ "bsonType": "string", "queries": [ { - "queryType": "suffixPreview", + "queryType": "suffix", "contention": { "$numberLong": "0" }, diff --git a/test/spec/client-side-encryption/tests/unified/QE-Text-cleanupStructuredEncryptionData.yml b/test/spec/client-side-encryption/tests/unified/QE-Text-cleanupStructuredEncryptionData.yml index 8d8979bd234..69243bfa4fd 100644 --- a/test/spec/client-side-encryption/tests/unified/QE-Text-cleanupStructuredEncryptionData.yml +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-cleanupStructuredEncryptionData.yml @@ -1,11 +1,10 @@ description: QE-Text-cleanupStructuredEncryptionData schemaVersion: "1.25" runOnRequirements: - - minServerVersion: "8.2.0" # Server 8.2.0 adds preview support for QE text queries. - maxServerVersion: "8.99.99" # Server 9.0.0-rc0 removes support for "prefixPreview" and "suffixPreview": SERVER-123416 + - 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.15.0 # For SPM-4158. + minLibmongocryptVersion: 1.19.0 # For MONGOCRYPT-870. createEntities: - client: id: &client "client" @@ -61,7 +60,7 @@ initialData: "bsonType": "string", "queries": [ { - "queryType": "suffixPreview", + "queryType": "suffix", "contention": { "$numberLong": "0" }, "strMinQueryLength": { "$numberLong": "3" }, "strMaxQueryLength": { "$numberLong": "30" }, diff --git a/test/spec/client-side-encryption/tests/unified/QE-Text-compactStructuredEncryptionData.json b/test/spec/client-side-encryption/tests/unified/QE-Text-compactStructuredEncryptionData.json index a89ab96fc4c..1c3c6cc0de1 100644 --- a/test/spec/client-side-encryption/tests/unified/QE-Text-compactStructuredEncryptionData.json +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-compactStructuredEncryptionData.json @@ -3,15 +3,14 @@ "schemaVersion": "1.25", "runOnRequirements": [ { - "minServerVersion": "8.2.0", - "maxServerVersion": "8.99.99", + "minServerVersion": "9.0.0", "topologies": [ "replicaset", "sharded", "load-balanced" ], "csfle": { - "minLibmongocryptVersion": "1.15.0" + "minLibmongocryptVersion": "1.19.0" } } ], @@ -102,7 +101,7 @@ "bsonType": "string", "queries": [ { - "queryType": "suffixPreview", + "queryType": "suffix", "contention": { "$numberLong": "0" }, @@ -210,7 +209,7 @@ "bsonType": "string", "queries": [ { - "queryType": "suffixPreview", + "queryType": "suffix", "contention": { "$numberLong": "0" }, diff --git a/test/spec/client-side-encryption/tests/unified/QE-Text-compactStructuredEncryptionData.yml b/test/spec/client-side-encryption/tests/unified/QE-Text-compactStructuredEncryptionData.yml index d608cd038f8..4bdc55ee88c 100644 --- a/test/spec/client-side-encryption/tests/unified/QE-Text-compactStructuredEncryptionData.yml +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-compactStructuredEncryptionData.yml @@ -1,11 +1,10 @@ description: QE-Text-compactStructuredEncryptionData schemaVersion: "1.25" runOnRequirements: - - minServerVersion: "8.2.0" # Server 8.2.0 adds preview support for QE text queries. - maxServerVersion: "8.99.99" # Server 9.0.0-rc0 removes support for "prefixPreview" and "suffixPreview": SERVER-123416 + - 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.15.0 # For SPM-4158. + minLibmongocryptVersion: 1.19.0 # For MONGOCRYPT-870. createEntities: - client: id: &client "client" @@ -61,7 +60,7 @@ initialData: "bsonType": "string", "queries": [ { - "queryType": "suffixPreview", + "queryType": "suffix", "contention": { "$numberLong": "0" }, "strMinQueryLength": { "$numberLong": "3" }, "strMaxQueryLength": { "$numberLong": "30" }, diff --git a/test/spec/client-side-encryption/tests/unified/QE-Text-prefix.json b/test/spec/client-side-encryption/tests/unified/QE-Text-prefix.json new file mode 100644 index 00000000000..25475e2c3a0 --- /dev/null +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-prefix.json @@ -0,0 +1,338 @@ +{ + "description": "QE-Text-prefix", + "schemaVersion": "1.25", + "runOnRequirements": [ + { + "minServerVersion": "9.0.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "csfle": { + "minLibmongocryptVersion": "1.19.0" + } + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "autoEncryptOpts": { + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "local": { + "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + } + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "db" + } + }, + { + "collection": { + "id": "coll", + "database": "db", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$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" + } + } + ] + }, + { + "databaseName": "db", + "collectionName": "coll", + "documents": [], + "createOptions": { + "encryptedFields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "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": [ + { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 1, + "encryptedText": { + "$$type": "binData" + } + } + ], + "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/test/spec/client-side-encryption/tests/unified/QE-Text-prefix.yml b/test/spec/client-side-encryption/tests/unified/QE-Text-prefix.yml new file mode 100644 index 00000000000..7cdc4e45f73 --- /dev/null +++ b/test/spec/client-side-encryption/tests/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/test/spec/client-side-encryption/tests/unified/QE-Text-prefixPreview.json b/test/spec/client-side-encryption/tests/unified/QE-Text-prefixPreview.json index c193608e887..51e72fd3cad 100644 --- a/test/spec/client-side-encryption/tests/unified/QE-Text-prefixPreview.json +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-prefixPreview.json @@ -11,7 +11,7 @@ "load-balanced" ], "csfle": { - "minLibmongocryptVersion": "1.15.0" + "minLibmongocryptVersion": "1.19.1" } } ], diff --git a/test/spec/client-side-encryption/tests/unified/QE-Text-prefixPreview.yml b/test/spec/client-side-encryption/tests/unified/QE-Text-prefixPreview.yml index aca60fdabf2..d1fbe26a696 100644 --- a/test/spec/client-side-encryption/tests/unified/QE-Text-prefixPreview.yml +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-prefixPreview.yml @@ -5,7 +5,7 @@ runOnRequirements: maxServerVersion: "8.99.99" # Server 9.0.0-rc0 removes support for "prefixPreview" and "suffixPreview": SERVER-123416 topologies: ["replicaset", "sharded", "load-balanced"] # QE does not support standalone. csfle: - minLibmongocryptVersion: 1.15.0 # For SPM-4158. + minLibmongocryptVersion: 1.19.1 # For MONGOCRYPT-937. createEntities: - client: id: &client "client" diff --git a/test/spec/client-side-encryption/tests/unified/QE-Text-suffix.json b/test/spec/client-side-encryption/tests/unified/QE-Text-suffix.json new file mode 100644 index 00000000000..ad6cdc06c96 --- /dev/null +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-suffix.json @@ -0,0 +1,338 @@ +{ + "description": "QE-Text-suffix", + "schemaVersion": "1.25", + "runOnRequirements": [ + { + "minServerVersion": "9.0.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "csfle": { + "minLibmongocryptVersion": "1.19.0" + } + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "autoEncryptOpts": { + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "local": { + "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + } + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "db" + } + }, + { + "collection": { + "id": "coll", + "database": "db", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$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" + } + } + ] + }, + { + "databaseName": "db", + "collectionName": "coll", + "documents": [], + "createOptions": { + "encryptedFields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedText", + "bsonType": "string", + "queries": [ + { + "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": [ + { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 1, + "encryptedText": { + "$$type": "binData" + } + } + ], + "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/test/spec/client-side-encryption/tests/unified/QE-Text-suffix.yml b/test/spec/client-side-encryption/tests/unified/QE-Text-suffix.yml new file mode 100644 index 00000000000..c98cb5e9dc5 --- /dev/null +++ b/test/spec/client-side-encryption/tests/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/test/spec/client-side-encryption/tests/unified/QE-Text-suffixPreview.json b/test/spec/client-side-encryption/tests/unified/QE-Text-suffixPreview.json index 2de5cde4a4b..e30d0cfd58c 100644 --- a/test/spec/client-side-encryption/tests/unified/QE-Text-suffixPreview.json +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-suffixPreview.json @@ -11,7 +11,7 @@ "load-balanced" ], "csfle": { - "minLibmongocryptVersion": "1.15.0" + "minLibmongocryptVersion": "1.19.1" } } ], @@ -207,7 +207,7 @@ ] }, { - "description": "Query with matching $encStrStartsWith", + "description": "Query with matching $encStrEndsWith", "operations": [ { "name": "insertOne", diff --git a/test/spec/client-side-encryption/tests/unified/QE-Text-suffixPreview.yml b/test/spec/client-side-encryption/tests/unified/QE-Text-suffixPreview.yml index e2ddd9f0d41..3a1414cf383 100644 --- a/test/spec/client-side-encryption/tests/unified/QE-Text-suffixPreview.yml +++ b/test/spec/client-side-encryption/tests/unified/QE-Text-suffixPreview.yml @@ -5,7 +5,7 @@ runOnRequirements: maxServerVersion: "8.99.99" # Server 9.0.0-rc0 removes support for "prefixPreview" and "suffixPreview": SERVER-123416 topologies: ["replicaset", "sharded", "load-balanced"] # QE does not support standalone. csfle: - minLibmongocryptVersion: 1.15.0 # For SPM-4158. + minLibmongocryptVersion: 1.19.1 # For MONGOCRYPT-937. createEntities: - client: id: &client "client" @@ -110,7 +110,7 @@ tests: - { "_id": 1, "encryptedText": { $$type: "binData" } } # Sends encrypted payload ordered: true commandName: insert - - description: "Query with matching $encStrStartsWith" + - description: "Query with matching $encStrEndsWith" operations: - name: insertOne arguments: