Skip to content

Commit 59af7cf

Browse files
fix discriminator logic
1 parent 14a40ff commit 59af7cf

File tree

2 files changed

+143
-51
lines changed

2 files changed

+143
-51
lines changed

lib/helpers/model/discriminator.js

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,32 @@ const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
2727
* @param {Schema} childSchema
2828
*/
2929
function validateDiscriminatorSchemasForEncryption(parentSchema, childSchema) {
30-
/**
31-
* @param schema { Schema }
32-
*
33-
* Given a schema, yields **all** paths to values inside that schema, recursively iterating over any nested schemas. This is
34-
* intended for use with encryption, so nested arrays are not considered because encryption on values inside arrays is not supported.
35-
*
36-
* @returns { Iterable<string> }
37-
*/
38-
function* allPaths(schema, prefix) {
39-
for (const path of Object.keys(schema.paths)) {
40-
const fullPath = prefix != null ? `${prefix}.${path}` : path;
41-
if (schema.path(path).instance === 'Embedded') {
42-
yield* allPaths(schema.path(path).schema, fullPath);
43-
} else {
44-
yield fullPath;
45-
}
30+
if (parentSchema.encryptionType() == null && childSchema.encryptionType() == null) return;
31+
32+
const allSharedNestedPaths = setIntersection(
33+
allNestedPaths(parentSchema),
34+
allNestedPaths(childSchema)
35+
);
36+
37+
for (const path of allSharedNestedPaths) {
38+
if (parentSchema._hasEncryptedField(path) && childSchema._hasEncryptedField(path)) {
39+
throw new Error(`encrypted fields cannot be declared on both the base schema and the child schema in a discriminator. path=${path}`);
40+
}
41+
42+
if (parentSchema._hasEncryptedField(path) || childSchema._hasEncryptedField(path)) {
43+
throw new Error(`encrypted fields cannot have the same path as a non-encrypted field for discriminators. path=${path}`);
4644
}
4745
}
4846

47+
function* allNestedPaths(schema) {
48+
const { paths, singleNestedPaths } = schema;
49+
yield* Object.keys(paths);
50+
yield* Object.keys(singleNestedPaths);
51+
}
52+
4953
/**
50-
* @param {Iterable<T>} i1
51-
* @param {Iterable<T>} i2
52-
*
53-
* @returns {Generator<T>}
54+
* @param {Iterable<string>} i1
55+
* @param {Iterable<string>} i2
5456
*/
5557
function* setIntersection(i1, i2) {
5658
const s1 = new Set(i1);
@@ -60,17 +62,8 @@ function validateDiscriminatorSchemasForEncryption(parentSchema, childSchema) {
6062
}
6163
}
6264
}
63-
64-
for (const path of setIntersection(allPaths(parentSchema), allPaths(childSchema))) {
65-
if (parentSchema._hasEncryptedField(path) && childSchema._hasEncryptedField(path)) {
66-
throw new Error(`encrypted fields cannot be declared on both the base schema and the child schema in a discriminator. path=${path}`);
67-
}
68-
69-
if (parentSchema._hasEncryptedField(path) || childSchema._hasEncryptedField(path)) {
70-
throw new Error(`encrypted fields cannot have the same path as a non-encrypted field for discriminators. path=${path}`);
71-
}
72-
}
7365
}
66+
7467
/*!
7568
* ignore
7669
*/

test/encryption/encryption.test.js

Lines changed: 120 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,39 +1090,138 @@ describe('encryption integration tests', () => {
10901090
/encrypted fields cannot be declared on both the base schema and the child schema in a discriminator\. path=name/
10911091
);
10921092
});
1093+
1094+
it('throws on duplicate keys declared on root and child discriminators, parent with fle, child without', async function() {
1095+
const schema = new Schema({
1096+
name: {
1097+
type: String, encrypt: { keyId }
1098+
}
1099+
}, {
1100+
encryptionType: 'queryableEncryption'
1101+
});
1102+
model = connection.model('Schema', schema);
1103+
assert.throws(() => model.discriminator('Test', new Schema({
1104+
name: {
1105+
type: String
1106+
}
1107+
})),
1108+
/encrypted fields cannot have the same path as a non-encrypted field for discriminators. path=name/
1109+
);
1110+
});
1111+
1112+
it('throws on duplicate keys declared on root and child discriminators, child with fle, parent without', async function() {
1113+
const schema = new Schema({
1114+
name: String
1115+
});
1116+
model = connection.model('Schema', schema);
1117+
assert.throws(() => model.discriminator('Test', new Schema({
1118+
name: {
1119+
type: String, encrypt: { keyId: [keyId], algorithm } }
1120+
}, {
1121+
encryptionType: 'queryableEncryption'
1122+
})),
1123+
/encrypted fields cannot have the same path as a non-encrypted field for discriminators. path=name/
1124+
);
1125+
});
10931126
});
10941127
});
10951128

1096-
describe('Nested Schema overrides nested path', function() {
1129+
describe('Nested paths in discriminators with conflicting definitions for the same key', function() {
10971130
beforeEach(async function() {
10981131
connection = createConnection();
10991132
});
11001133

1101-
it('nested objects throw an error', async function() {
1102-
model = connection.model('Schema', new Schema({
1103-
name: {
1104-
first: { type: String, encrypt: { keyId: [keyId], algorithm } }
1105-
}
1106-
}, { encryptionType: 'csfle' }));
1134+
describe('same definition on parent and child', function() {
1135+
it('throws an error', function() {
1136+
model = connection.model('Schema', new Schema({
1137+
name: {
1138+
first: { type: String, encrypt: { keyId: [keyId], algorithm } }
1139+
}
1140+
}, { encryptionType: 'csfle' }));
11071141

1108-
assert.throws(() => {
1109-
model.discriminator('Test', new Schema({
1110-
name: { first: Number } // Different type, no encryption, stored as same field in MDB
1111-
}));
1142+
assert.throws(() => {
1143+
model.discriminator('Test', new Schema({
1144+
name: { first: { type: String, encrypt: { keyId: [keyId], algorithm } } } // Different type, no encryption, stored as same field in MDB
1145+
}, { encryptionType: 'csfle' }));
1146+
}, /encrypted fields cannot be declared on both the base schema and the child schema in a discriminator. path/);
11121147
});
11131148
});
11141149

1115-
it('nested schemas throw an error', async function() {
1116-
model = connection.model('Schema', new Schema({
1117-
name: {
1118-
first: { type: String, encrypt: { keyId: [keyId], algorithm } }
1119-
}
1120-
}, { encryptionType: 'csfle' }));
1150+
describe('child overrides parent\'s encryption', function() {
1151+
it('throws an error', function() {
1152+
model = connection.model('Schema', new Schema({
1153+
name: {
1154+
first: { type: String, encrypt: { keyId: [keyId], algorithm } }
1155+
}
1156+
}, { encryptionType: 'csfle' }));
11211157

1122-
assert.throws(() => {
1123-
model.discriminator('Test', new Schema({
1124-
name: new Schema({ first: Number }) // Different type, no encryption, stored as same field in MDB
1125-
}));
1158+
assert.throws(() => {
1159+
model.discriminator('Test', new Schema({
1160+
name: { first: Number } // Different type, no encryption, stored as same field in MDB
1161+
}));
1162+
}, /encrypted fields cannot have the same path as a non-encrypted field for discriminators. path=name/);
1163+
});
1164+
});
1165+
});
1166+
1167+
describe('Nested schemas in discriminators with conflicting definitions for the same key', function() {
1168+
beforeEach(async function() {
1169+
connection = createConnection();
1170+
});
1171+
1172+
describe('same definition on parent and child', function() {
1173+
it('throws an error', function() {
1174+
model = connection.model('Schema', new Schema({
1175+
name: new Schema({
1176+
first: { type: String, encrypt: { keyId: [keyId], algorithm } }
1177+
}, { encryptionType: 'csfle' })
1178+
}, { encryptionType: 'csfle' }));
1179+
1180+
assert.throws(() => {
1181+
model.discriminator('Test', new Schema({
1182+
name: new Schema({
1183+
first: { type: String, encrypt: { keyId: [keyId], algorithm } }
1184+
}, { encryptionType: 'csfle' }) // Different type, no encryption, stored as same field in MDB
1185+
}, { encryptionType: 'csfle' }));
1186+
}, /encrypted fields cannot be declared on both the base schema and the child schema in a discriminator. path/);
1187+
});
1188+
});
1189+
1190+
describe('child overrides parent\'s encryption', function() {
1191+
it('throws an error', function() {
1192+
model = connection.model('Schema', new Schema({
1193+
name: new Schema({
1194+
first: { type: String, encrypt: { keyId: [keyId], algorithm } }
1195+
}, { encryptionType: 'csfle' })
1196+
}, { encryptionType: 'csfle' }));
1197+
1198+
assert.throws(() => {
1199+
model.discriminator('Test', new Schema({
1200+
name: new Schema({
1201+
first: Number
1202+
})
1203+
}));
1204+
}, /encrypted fields cannot have the same path as a non-encrypted field for discriminators. path=name.first/);
1205+
});
1206+
});
1207+
1208+
describe('multiple levels of nesting', function() {
1209+
it('throws an error', function() {
1210+
model = connection.model('Schema', new Schema({
1211+
name: new Schema({
1212+
first: new Schema({
1213+
first: { type: String, encrypt: { keyId: [keyId], algorithm } }
1214+
}, { encryptionType: 'csfle' })
1215+
}, { encryptionType: 'csfle' })
1216+
}, { encryptionType: 'csfle' }));
1217+
1218+
assert.throws(() => {
1219+
model.discriminator('Test', new Schema({
1220+
name: new Schema({
1221+
first: { type: String, encrypt: { keyId: [keyId], algorithm } }
1222+
}, { encryptionType: 'csfle' }) // Different type, no encryption, stored as same field in MDB
1223+
}, { encryptionType: 'csfle' }));
1224+
}, /encrypted fields cannot have the same path as a non-encrypted field for discriminators. path=name.first/);
11261225
});
11271226
});
11281227
});

0 commit comments

Comments
 (0)