@@ -12,6 +12,7 @@ const pkg = require('../../../package.json');
1212const processConnectionOptions = require ( '../../helpers/processConnectionOptions' ) ;
1313const setTimeout = require ( '../../helpers/timers' ) . setTimeout ;
1414const utils = require ( '../../utils' ) ;
15+ const { inferBSONType } = require ( '../../encryption_utils' ) ;
1516
1617/**
1718 * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) connection implementation.
@@ -304,6 +305,17 @@ NativeConnection.prototype.createClient = async function createClient(uri, optio
304305 } ;
305306 }
306307
308+
309+ const { schemaMap, encryptedFieldsMap } = this . _buildEncryptionSchemas ( options ) ;
310+
311+ if ( Object . keys ( schemaMap ) . length > 0 ) {
312+ options . autoEncryption . schemaMap = schemaMap ;
313+ }
314+
315+ if ( Object . keys ( encryptedFieldsMap ) . length > 0 ) {
316+ options . autoEncryption . encryptedFieldsMap = encryptedFieldsMap ;
317+ }
318+
307319 this . readyState = STATES . connecting ;
308320 this . _connectionString = uri ;
309321
@@ -327,6 +339,103 @@ NativeConnection.prototype.createClient = async function createClient(uri, optio
327339 return this ;
328340} ;
329341
342+ /**
343+ * Given a connection, which may or may not have encrypted models, build
344+ * a schemaMap and/or an encryptedFieldsMap for the connection, combining all models
345+ * into a single schemaMap and encryptedFields map.
346+ *
347+ * @returns a copy of the options object with a schemaMap and/or an encryptedFieldsMap added to the options' autoEncryption
348+ * options.
349+ */
350+ NativeConnection . prototype . _buildEncryptionSchemas = function ( ) {
351+ const schemaMap = Object . values ( this . models ) . filter ( model => model . schema . encryptionType ( ) === 'csfle' ) . reduce (
352+ schemaMapReducer . bind ( this ) ,
353+ { }
354+ ) ;
355+ const encryptedFieldsMap = Object . values ( this . models ) . filter ( model => model . schema . encryptionType ( ) === 'qe' ) . reduce (
356+ encryptedFieldsMapReducer . bind ( this ) ,
357+ { }
358+ ) ;
359+
360+ return {
361+ schemaMap, encryptedFieldsMap
362+ } ;
363+
364+ /**
365+ * `schemaMap`s are JSON schemas, which use the following structure to represent objects:
366+ * { field: { bsonType: 'object', properties: { ... } } }
367+ *
368+ * for example, a schema that looks like this `{ a: { b: int32 } }` would be encoded as
369+ * `{ a: { bsonType: 'object', properties: { b: < encryption configuration > } } }`
370+ *
371+ * This function takes an array of path segments, an output object (that gets mutated) and
372+ * a value to associated with the full path, and constructs a valid CSFLE JSON schema path for
373+ * the object. This works for deeply nested properties as well.
374+ *
375+ * @param {string[] } path array of path components
376+ * @param {object } object the object in which to build a JSON schema of `path`'s properties
377+ * @param {object } value the value to associate with the path in object
378+ */
379+ function buildNestedPath ( path , object , value ) {
380+ let i = 0 , component = path [ i ] ;
381+ for ( ; i < path . length - 1 ; ++ i , component = path [ i ] ) {
382+ object [ component ] = object [ component ] == null ? {
383+ bsonType : 'object' ,
384+ properties : { }
385+ } : object [ component ] ;
386+ object = object [ component ] . properties ;
387+ }
388+ object [ component ] = value ;
389+ }
390+
391+ /**
392+ * @param {object } schemaMap the accumulation schemaMap
393+ * @param {Model } the model
394+ * @returns
395+ */
396+ function schemaMapReducer ( schemaMap , model ) {
397+ const { schema, collection : { collectionName } } = model ;
398+ const namespace = `${ this . $dbName } .${ collectionName } ` ;
399+
400+ function schemaMapPropertyReducer ( accum , [ path , propertyConfig ] ) {
401+ const bsonType = inferBSONType ( schema , path ) ;
402+ const pathComponents = path . split ( '.' ) ;
403+ const configuration = { encrypt : { ...propertyConfig , bsonType } } ;
404+ buildNestedPath ( pathComponents , accum , configuration ) ;
405+ return accum ;
406+ }
407+ const properties = Object . entries ( schema . encryptedFields ) . reduce (
408+ schemaMapPropertyReducer ,
409+ { } ) ;
410+
411+ schemaMap [ namespace ] = {
412+ bsonType : 'object' ,
413+ properties
414+ } ;
415+ return schemaMap ;
416+ }
417+
418+ /**
419+ *
420+ * @param {object } encryptedFieldsMap the accumulation encryptedFieldsMap
421+ * @param {Model } the model
422+ * @returns
423+ */
424+ function encryptedFieldsMapReducer ( encryptedFieldsMap , { schema, collection : { collectionName } } ) {
425+ const namespace = `${ this . $dbName } .${ collectionName } ` ;
426+ const fields = Object . entries ( schema . encryptedFields ) . map (
427+ ( [ path , config ] ) => {
428+ const bsonType = inferBSONType ( schema , path ) ;
429+ // { path, bsonType, keyId, queries? }
430+ return { path, bsonType, ...config } ;
431+ } ) ;
432+
433+ encryptedFieldsMap [ namespace ] = { fields } ;
434+
435+ return encryptedFieldsMap ;
436+ }
437+ } ;
438+
330439/*!
331440 * ignore
332441 */
@@ -347,7 +456,7 @@ NativeConnection.prototype.setClient = function setClient(client) {
347456
348457 for ( const model of Object . values ( this . models ) ) {
349458 // Errors handled internally, so safe to ignore error
350- model . init ( ) . catch ( function $modelInitNoop ( ) { } ) ;
459+ model . init ( ) . catch ( function $modelInitNoop ( ) { } ) ;
351460 }
352461
353462 return this ;
@@ -390,9 +499,9 @@ function _setClient(conn, client, options, dbName) {
390499 } ;
391500
392501 const type = client &&
393- client . topology &&
394- client . topology . description &&
395- client . topology . description . type || '' ;
502+ client . topology &&
503+ client . topology . description &&
504+ client . topology . description . type || '' ;
396505
397506 if ( type === 'Single' ) {
398507 client . on ( 'serverDescriptionChanged' , ev => {
0 commit comments