Skip to content

Commit df21dee

Browse files
authored
Merge pull request #15404 from mongodb-js/csfle-review-comments
chore: address comments on CSFLE feature branch review
2 parents 699c82d + 6fc22b6 commit df21dee

File tree

9 files changed

+186
-115
lines changed

9 files changed

+186
-115
lines changed

docs/field-level-encryption.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ const UserModel = connection.model('User', encryptedUserSchema);
164164

165165
### Connecting and configuring encryption options
166166

167-
CSFLE/QE in Mongoose work by generating the encryption schema that the MongoDB driver expects for each encrypted model on the connection. This happens automatically the model's connection is established.
167+
CSFLE/QE in Mongoose work by generating the encryption schema that the MongoDB driver expects for each encrypted model on the connection. This happens automatically when the model's connection is established.
168168

169-
Queryable encryption and CSFLE requires all the same configuration as outlined in <>, except for the schemaMap or encryptedFieldsMap options.
169+
Queryable encryption and CSFLE requires all the same configuration as outlined in the [MongoDB encryption in-use documentation](https://www.mongodb.com/docs/manual/core/security-in-use-encryption/), except for the schemaMap or encryptedFieldsMap options.
170170

171171
```javascript
172172
const keyVaultNamespace = 'client.encryption';
@@ -215,7 +215,7 @@ const ModelWithBirthday = model.discriminator('ModelWithBirthday', new Schema({
215215
}));
216216
```
217217

218-
When generating encryption schemas, Mongoose merges all discriminators together for the all discriminators declared on the same namespace. As a result, discriminators that declare the same key with different types are not supported. Furthermore, all discriminators must share the same encryption type - it is not possible to configure discriminators on the same model for both CSFLE and QE.
218+
When generating encryption schemas, Mongoose merges all discriminators together for all of the discriminators declared on the same namespace. As a result, discriminators that declare the same key with different types are not supported. Furthermore, all discriminators must share the same encryption type - it is not possible to configure discriminators on the same model for both CSFLE and QE.
219219

220220
## Managing Data Keys
221221

lib/model.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4879,6 +4879,12 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
48794879
return model;
48804880
};
48814881

4882+
/**
4883+
* If auto encryption is enabled, returns a ClientEncryption instance that is configured with the same settings that
4884+
* Mongoose's underlying MongoClient is using. If the client has not yet been configured, returns null.
4885+
*
4886+
* @returns {ClientEncryption | null}
4887+
*/
48824888
Model.clientEncryption = function clientEncryption() {
48834889
const ClientEncryption = this.base.driver.get().ClientEncryption;
48844890
if (!ClientEncryption) {

lib/schema.js

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ Schema.prototype.pick = function(paths, options) {
499499
}
500500

501501
for (const path of paths) {
502-
if (path in this.encryptedFields) {
502+
if (this._hasEncryptedField(path)) {
503503
const encrypt = this.encryptedFields[path];
504504
const schemaType = this.path(path);
505505
newSchema.add({
@@ -508,8 +508,7 @@ Schema.prototype.pick = function(paths, options) {
508508
[this.options.typeKey]: schemaType
509509
}
510510
});
511-
}
512-
else if (this.nested[path]) {
511+
} else if (this.nested[path]) {
513512
newSchema.add({ [path]: get(this.tree, path) });
514513
} else {
515514
const schematype = this.path(path);
@@ -689,17 +688,16 @@ Schema.prototype._defaultToObjectOptions = function(json) {
689688
* Sets the encryption type of the schema, if a value is provided, otherwise
690689
* returns the encryption type.
691690
*
692-
* @param {'csfle' | 'queryableEncryption' | undefined} encryptionType plain object with paths to add, or another schema
691+
* @param {'csfle' | 'queryableEncryption' | null | undefined} encryptionType plain object with paths to add, or another schema
693692
*/
694693
Schema.prototype.encryptionType = function encryptionType(encryptionType) {
695694
if (arguments.length === 0) {
696695
return this.options.encryptionType;
697696
}
698-
if (typeof encryptionType === 'string' || encryptionType === null) {
699-
this.options.encryptionType = encryptionType;
700-
} else {
701-
throw new TypeError('Invalid encryptionType. Expected a string, null, or no arguments.');
697+
if (!(typeof encryptionType === 'string' || encryptionType === null)) {
698+
throw new Error('invalid `encryptionType`: ${encryptionType}');
702699
}
700+
this.options.encryptionType = encryptionType;
703701
};
704702

705703
/**
@@ -863,8 +861,7 @@ Schema.prototype.add = function add(obj, prefix) {
863861
const path = fullPath + '.' + encryptedField;
864862
this._addEncryptedField(path, encryptedFieldConfig);
865863
}
866-
}
867-
else if (typeof val === 'object' && 'encrypt' in val) {
864+
} else if (typeof val === 'object' && 'encrypt' in val) {
868865
// schema.add({ field: { type: <schema type>, encrypt: { ... }}})
869866
const { encrypt } = val;
870867

@@ -903,6 +900,8 @@ Schema.prototype._addEncryptedField = function _addEncryptedField(path, fieldCon
903900
};
904901

905902
/**
903+
* @param {string} path
904+
*
906905
* @api private
907906
*/
908907
Schema.prototype._removeEncryptedField = function _removeEncryptedField(path) {
@@ -911,12 +910,17 @@ Schema.prototype._removeEncryptedField = function _removeEncryptedField(path) {
911910

912911
/**
913912
* @api private
913+
*
914+
* @returns {boolean}
914915
*/
915916
Schema.prototype._hasEncryptedFields = function _hasEncryptedFields() {
916917
return Object.keys(this.encryptedFields).length > 0;
917918
};
918919

919920
/**
921+
* @param {string} path
922+
* @returns {boolean}
923+
*
920924
* @api private
921925
*/
922926
Schema.prototype._hasEncryptedField = function _hasEncryptedField(path) {
@@ -926,6 +930,8 @@ Schema.prototype._hasEncryptedField = function _hasEncryptedField(path) {
926930

927931
/**
928932
* Builds an encryptedFieldsMap for the schema.
933+
*
934+
* @api private
929935
*/
930936
Schema.prototype._buildEncryptedFields = function() {
931937
const fields = Object.entries(this.encryptedFields).map(
@@ -940,6 +946,8 @@ Schema.prototype._buildEncryptedFields = function() {
940946

941947
/**
942948
* Builds a schemaMap for the schema, if the schema is configured for client-side field level encryption.
949+
*
950+
* @api private
943951
*/
944952
Schema.prototype._buildSchemaMap = function() {
945953
/**
@@ -950,7 +958,7 @@ Schema.prototype._buildSchemaMap = function() {
950958
* `{ a: { bsonType: 'object', properties: { b: < encryption configuration > } } }`
951959
*
952960
* This function takes an array of path segments, an output object (that gets mutated) and
953-
* a value to associated with the full path, and constructs a valid CSFLE JSON schema path for
961+
* a value to be associated with the full path, and constructs a valid CSFLE JSON schema path for
954962
* the object. This works for deeply nested properties as well.
955963
*
956964
* @param {string[]} path array of path components

lib/schema/uuid.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ SchemaUUID.prototype.toJSONSchema = function toJSONSchema(options) {
298298
return createJSONSchemaTypeDefinition('string', 'binData', options?.useBsonType, isRequired);
299299
};
300300

301+
SchemaUUID.prototype.autoEncryptionType = function autoEncryptionType() {
302+
return 'binData';
303+
};
304+
301305
/*!
302306
* Module exports.
303307
*/

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,5 @@
148148
"target": "ES2017"
149149
}
150150
}
151-
}
151+
}
152+

scripts/setup-encryption-tests.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
'use strict';
2+
23
const { downloadMongoDb } = require('@mongodb-js/mongodb-downloader');
34
const { instances, start } = require('mongodb-runner');
45
const { rm, readdir, writeFile } = require('fs/promises');
56
const { tmpdir } = require('os');
67
const { join, resolve } = require('path');
78

8-
99
async function main() {
1010
const runnerDir = join(resolve(__dirname), '../data');
1111
const serverVersion = '8.0';
@@ -38,12 +38,14 @@ async function main() {
3838
'sharded', runnerDir, tmpDir: tmpdir() });
3939

4040
for await (const instance of instances({ runnerDir })) {
41-
if (instance.id === 'encryption-test-cluster') return {
42-
cryptShared, uri: instance.connectionString
43-
};
41+
if (instance.id === 'encryption-test-cluster') {
42+
return {
43+
cryptShared, uri: instance.connectionString
44+
};
45+
}
4446
}
4547

46-
throw new Error('Unable to location newly configured instance of mongod - should never happen.');
48+
throw new Error('Unable to locate newly configured instance of mongod - should never happen.');
4749
}
4850
}
4951

test/encryptedSchema.test.js

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
'use strict';
32

43
const assert = require('assert');
@@ -13,7 +12,7 @@ const Schema = mongoose.Schema;
1312
*
1413
* @param {import('../lib').Schema} object
1514
* @param {Array<string> | string} path
16-
* @returns
15+
* @returns { boolean }
1716
*/
1817
function schemaHasEncryptedProperty(schema, path) {
1918
path = [path].flat();
@@ -240,7 +239,7 @@ describe('encrypted schema declaration', function() {
240239
});
241240
});
242241

243-
describe('When a schema is instantiated with a custom schema type plugin', function() {
242+
describe('When a schema is instantiated with a custom schema type plugin that does not have a encryption type', function() {
244243
class Int8 extends mongoose.SchemaType {
245244
constructor(key, options) {
246245
super(key, options, 'Int8');
@@ -266,6 +265,33 @@ describe('encrypted schema declaration', function() {
266265
});
267266
});
268267

268+
describe('When a schema is instantiated with a custom schema type plugin that does have a encryption type', function() {
269+
class Int8 extends mongoose.SchemaType {
270+
constructor(key, options) {
271+
super(key, options, 'Int8');
272+
}
273+
274+
autoEncryptionType() {
275+
return 'int';
276+
}
277+
}
278+
279+
beforeEach(function() {
280+
// Don't forget to add `Int8` to the type registry
281+
mongoose.Schema.Types.Int8 = Int8;
282+
});
283+
afterEach(function() {
284+
delete mongoose.Schema.Types.Int8;
285+
});
286+
287+
it('No error is thrown', function() {
288+
new Schema({
289+
field: {
290+
type: Int8, encrypt: { keyId: KEY_ID, algorithm }
291+
}
292+
}, { encryptionType: 'csfle' });
293+
});
294+
});
269295
});
270296

271297
describe('options.encryptionType', function() {
@@ -746,6 +772,58 @@ function primitiveSchemaMapTests() {
746772
]
747773
}
748774
},
775+
{
776+
name: 'uuid',
777+
encryptionType: 'csfle',
778+
type: Schema.Types.UUID,
779+
schemaMap: {
780+
bsonType: 'object',
781+
properties: {
782+
field: {
783+
encrypt: {
784+
keyId: '9fbdace3-4e48-412d-88df-3807e8009522',
785+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic',
786+
bsonType: 'binData'
787+
}
788+
}
789+
}
790+
},
791+
encryptedFields: {
792+
fields: [
793+
{
794+
path: 'field',
795+
bsonType: 'binData',
796+
keyId: '9fbdace3-4e48-412d-88df-3807e8009522',
797+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
798+
}
799+
]
800+
}
801+
},
802+
{
803+
name: 'uuid',
804+
encryptionType: 'queryableEncryption',
805+
type: Schema.Types.UUID,
806+
schemaMap: {
807+
bsonType: 'object',
808+
properties: {
809+
field: {
810+
encrypt: {
811+
keyId: '9fbdace3-4e48-412d-88df-3807e8009522',
812+
bsonType: 'binData'
813+
}
814+
}
815+
}
816+
},
817+
encryptedFields: {
818+
fields: [
819+
{
820+
path: 'field',
821+
bsonType: 'binData',
822+
keyId: '9fbdace3-4e48-412d-88df-3807e8009522'
823+
}
824+
]
825+
}
826+
},
749827
{
750828
name: 'buffer',
751829
encryptionType: 'csfle',

0 commit comments

Comments
 (0)