Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(NODE-6284): make sparsity and trimFactor optional #4189

Merged
merged 12 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .evergreen/install-mongodb-client-encryption.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#! /usr/bin/env bash
set +o xtrace # Do not write AWS credentials to stderr
set +o xtrace

# Initial checks for running these tests
if [ -z ${PROJECT_DIRECTORY+omitted} ]; then echo "PROJECT_DIRECTORY is unset" && exit 1; fi
Expand All @@ -9,20 +9,19 @@ source "${PROJECT_DIRECTORY}/.evergreen/init-node-and-npm-env.sh"
set -o xtrace # Write all commands first to stderr
set -o errexit # Exit the script with error if any of the commands fail

rm -rf $INSTALL_DIR
rm -rf mongodb-client-encryption
git clone https://github.com/mongodb-js/mongodb-client-encryption.git
pushd mongodb-client-encryption

if [ -n "${LIBMONGOCRYPT_VERSION}" ]; then
# nightly tests test with `latest` to test against the laster FLE build.
npm run install:libmongocrypt -- --libVersion $LIBMONGOCRYPT_VERSION
npm run install:libmongocrypt -- --build --libVersion $LIBMONGOCRYPT_VERSION
else
# otherwise use whatever is specified in the package.json.
npm run install:libmongocrypt
fi

echo "finished installing libmongocrypt"
BINDINGS_DIR=$(pwd)

popd

Expand Down
25 changes: 4 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"js-yaml": "^4.1.0",
"mocha": "^10.4.0",
"mocha-sinon": "^2.1.2",
"mongodb-client-encryption": "^6.1.0-alpha.0",
"mongodb-client-encryption": "^6.1.0",
"mongodb-legacy": "^6.0.1",
"nyc": "^15.1.0",
"prettier": "^2.8.8",
Expand Down
20 changes: 17 additions & 3 deletions src/client-side-encryption/client_encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ import type {
MongoCryptOptions
} from 'mongodb-client-encryption';

import { type Binary, deserialize, type Document, type Long, serialize, type UUID } from '../bson';
import {
type Binary,
deserialize,
type Document,
type Int32,
type Long,
serialize,
type UUID
} from '../bson';
import { type AnyBulkWriteOperation, type BulkWriteResult } from '../bulk/common';
import { type ProxyOptions } from '../cmap/connection';
import { type Collection } from '../collection';
Expand Down Expand Up @@ -956,13 +964,19 @@ export interface ClientEncryptionRewrapManyDataKeyResult {
/**
* @public
* RangeOptions specifies index options for a Queryable Encryption field supporting "rangePreview" queries.
* min, max, sparsity, and range must match the values set in the encryptedFields of the destination collection.
* min, max, sparsity, trimFactor and range must match the values set in the encryptedFields of the destination collection.
* For double and decimal128, min/max/precision must all be set, or all be unset.
*/
export interface RangeOptions {
/** min is the minimum value for the encrypted index. Required if precision is set. */
min?: any;
/** max is the minimum value for the encrypted index. Required if precision is set. */
max?: any;
sparsity: Long;
/** sparsity may be used to tune performance. must be non-negative. When omitted, a default value is used. */
sparsity?: Long | bigint;
/** trimFactor may be used to tune performance. must be non-negative. When omitted, a default value is used. */
trimFactor?: Int32 | number;
/* precision determines the number of significant digits after the decimal point. May only be set for double or decimal128. */
precision?: number;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { expect } from 'chai';

/* eslint-disable @typescript-eslint/no-restricted-imports */
import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption';
import { type Binary, EJSON, Int32, Long } from '../../mongodb';
import { installNodeDNSWorkaroundHooks } from '../../tools/runner/hooks/configuration';

const metaData: MongoDBMetadataUI = {
requires: {
clientSideEncryption: '>=6.1.0',

// The Range Explicit Encryption tests require MongoDB server 7.0+ for QE v2.
// The tests must not run against a standalone.
//
// `range` is not supported on 8.0+ servers.
mongodb: '>=8.0.0',
topology: '!single'
}
};

const getKmsProviders = (): { local: { key: string } } => {
const result = EJSON.parse(process.env.CSFLE_KMS_PROVIDERS || '{}') as unknown as {
local: { key: string };
};

return { local: result.local };
};

describe('Range Explicit Encryption Defaults', function () {
installNodeDNSWorkaroundHooks();

let clientEncryption: ClientEncryption;
let keyId;
let keyVaultClient;
let payload_defaults: Binary;

beforeEach(async function () {
// Create a MongoClient named `keyVaultClient`.
keyVaultClient = this.configuration.newClient();

// Create a ClientEncryption object named `clientEncryption` with these options:
// ```typescript
// class ClientEncryptionOpts {
// keyVaultClient: keyVaultClient,
// keyVaultNamespace: "keyvault.datakeys",
// kmsProviders: { "local": { "key": "<base64 decoding of LOCAL_MASTERKEY>" } },
// }
// ```
clientEncryption = new ClientEncryption(keyVaultClient, {
keyVaultNamespace: 'keyvault.datakeys',
kmsProviders: getKmsProviders()
});

// Create a key with `clientEncryption.createDataKey`. Store the returned key ID in a variable named `keyId`.
keyId = await clientEncryption.createDataKey('local');

// Call `clientEncryption.encrypt` to encrypt the int32 value `123` with these options:
// ```typescript
// class EncryptOpts {
// keyId : keyId,
// algorithm: "Range",
// contentionFactor: 0,
// rangeOpts: RangeOpts {
// min: 0,
// max: 1000
// }
// }
// ```
// Store the result in a variable named `payload_defaults`.
payload_defaults = await clientEncryption.encrypt(new Int32(123), {
keyId,
algorithm: 'Range',
contentionFactor: 0,
rangeOptions: {
min: 0,
max: 1000
}
});
});

afterEach(async function () {
await keyVaultClient.close();
});

it('Case 1: Uses libmongocrypt defaults', metaData, async function () {
// Call `clientEncryption.encrypt` to encrypt the int32 value `123` with these options:
// ```typescript
// class EncryptOpts {
// keyId : keyId,
// algorithm: "Range",
// contentionFactor: 0,
// rangeOpts: RangeOpts {
// min: 0,
// max: 1000,
// sparsity: 2,
// trimFactor: 6
// }
// }
// ```
const encrypted = await clientEncryption.encrypt(new Int32(123), {
keyId: keyId,
algorithm: 'Range',
contentionFactor: 0,
rangeOptions: {
min: 0,
max: 1000,
sparsity: new Long(2),
trimFactor: new Int32(6)
}
});

// Assert the returned payload size equals the size of `payload_defaults`.
expect(encrypted.length()).to.equal(payload_defaults.length());
});

it('Case 2: can find encrypted range and return the maximum', metaData, async function () {
// Call `clientEncryption.encrypt` to encrypt the int32 value `123` with these options:
// ```typescript
// class EncryptOpts {
// keyId : keyId,
// algorithm: "Range",
// contentionFactor: 0,
// rangeOpts: RangeOpts {
// min: 0,
// max: 1000,
// trimFactor: 0
// }
// }
// ```
const encrypted = await clientEncryption.encrypt(new Int32(123), {
keyId: keyId,
algorithm: 'Range',
contentionFactor: 0,
rangeOptions: {
min: 0,
max: 1000,
trimFactor: new Int32(0)
}
});

// Assert the returned payload size is greater than the size of `payload_defaults`.
expect(encrypted.length()).to.be.greaterThan(payload_defaults.length());
});
});
71 changes: 71 additions & 0 deletions test/integration/client-side-encryption/driver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,74 @@ describe('Client Side Encryption Functional', function () {
}
);
});

describe('Range Explicit Encryption with JS native types', function () {
installNodeDNSWorkaroundHooks();

const metaData: MongoDBMetadataUI = {
requires: {
clientSideEncryption: '>=6.1.0',

// The Range Explicit Encryption tests require MongoDB server 7.0+ for QE v2.
// The tests must not run against a standalone.
//
// `range` is not supported on 8.0+ servers.
mongodb: '>=8.0.0',
topology: '!single'
}
};

const getKmsProviders = (): { local: { key: string } } => {
const result = EJSON.parse(process.env.CSFLE_KMS_PROVIDERS || '{}') as unknown as {
local: { key: string };
};

return { local: result.local };
};

let clientEncryption: ClientEncryption;
let keyId;
let keyVaultClient;

beforeEach(async function () {
keyVaultClient = this.configuration.newClient();
clientEncryption = new ClientEncryption(keyVaultClient, {
keyVaultNamespace: 'keyvault.datakeys',
kmsProviders: getKmsProviders()
});

keyId = await clientEncryption.createDataKey('local');
});

afterEach(async function () {
await keyVaultClient.close();
});

it('supports a js number for trimFactor', metaData, async function () {
await clientEncryption.encrypt(new BSON.Int32(123), {
keyId,
algorithm: 'Range',
contentionFactor: 0,
rangeOptions: {
min: 0,
max: 1000,
trimFactor: 1,
sparsity: new BSON.Long(1)
}
});
});

it('supports a bigint for sparsity', metaData, async function () {
await clientEncryption.encrypt(new BSON.Int32(123), {
keyId,
algorithm: 'Range',
contentionFactor: 0,
rangeOptions: {
min: 0,
max: 1000,
trimFactor: new BSON.Int32(1),
sparsity: 1n
}
});
});
});
Loading