Skip to content

Commit 64a9f97

Browse files
committed
fix: disallow nested $where in populate match
1 parent 15bdccf commit 64a9f97

File tree

2 files changed

+40
-22
lines changed

2 files changed

+40
-22
lines changed

lib/helpers/populate/getModelsMapForPopulate.js

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
182182
if (hasMatchFunction) {
183183
match = match.call(doc, doc);
184184
}
185-
if (Array.isArray(match)) {
186-
for (const item of match) {
187-
if (item != null && item.$where) {
188-
throw new MongooseError('Cannot use $where filter with populate() match');
189-
}
190-
}
191-
} else if (match != null && match.$where != null) {
192-
throw new MongooseError('Cannot use $where filter with populate() match');
193-
}
185+
throwOn$where(match);
194186
data.match = match;
195187
data.hasMatchFunction = hasMatchFunction;
196188
data.isRefPath = isRefPath;
@@ -469,15 +461,7 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
469461
data.match = match;
470462
data.hasMatchFunction = hasMatchFunction;
471463

472-
if (Array.isArray(match)) {
473-
for (const item of match) {
474-
if (item != null && item.$where) {
475-
throw new MongooseError('Cannot use $where filter with populate() match');
476-
}
477-
}
478-
} else if (match != null && match.$where != null) {
479-
throw new MongooseError('Cannot use $where filter with populate() match');
480-
}
464+
throwOn$where(match);
481465

482466
// Get local fields
483467
const ret = _getLocalFieldValues(doc, localField, model, options, virtual);
@@ -749,3 +733,24 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz
749733

750734
return modelNames;
751735
}
736+
737+
/**
738+
* Throw an error if there are any $where keys
739+
*/
740+
741+
function throwOn$where(match) {
742+
if (match == null) {
743+
return;
744+
}
745+
if (typeof match !== 'object') {
746+
return;
747+
}
748+
for (const key of Object.keys(match)) {
749+
if (key === '$where') {
750+
throw new MongooseError('Cannot use $where filter with populate() match');
751+
}
752+
if (match[key] != null && typeof match[key] === 'object') {
753+
throwOn$where(match[key]);
754+
}
755+
}
756+
}

test/model.populate.test.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4195,21 +4195,34 @@ describe('model: populate:', function() {
41954195
const parent = await Parent.create({ name: 'Anakin', child: child._id });
41964196

41974197
await assert.rejects(
4198-
() => Parent.findOne().populate({ path: 'child', match: { $where: 'console.log("oops!");' } }),
4198+
() => Parent.findOne().populate({ path: 'child', match: () => ({ $where: 'typeof console !== "undefined" ? doesNotExist("foo") : true;' }) }),
41994199
/Cannot use \$where filter with populate\(\) match/
42004200
);
42014201
await assert.rejects(
4202-
() => Parent.find().populate({ path: 'child', match: { $where: 'console.log("oops!");' } }),
4202+
() => Parent.find().populate({ path: 'child', match: () => ({ $where: 'typeof console !== "undefined" ? doesNotExist("foo") : true;' }) }),
42034203
/Cannot use \$where filter with populate\(\) match/
42044204
);
42054205
await assert.rejects(
4206-
() => parent.populate({ path: 'child', match: { $where: 'console.log("oops!");' } }),
4206+
() => parent.populate({ path: 'child', match: () => ({ $where: 'typeof console !== "undefined" ? doesNotExist("foo") : true;' }) }),
42074207
/Cannot use \$where filter with populate\(\) match/
42084208
);
42094209
await assert.rejects(
4210-
() => Child.find().populate({ path: 'parent', match: { $where: 'console.log("oops!");' } }),
4210+
() => Child.find().populate({ path: 'parent', match: () => ({ $where: 'typeof console !== "undefined" ? doesNotExist("foo") : true;' }) }),
42114211
/Cannot use \$where filter with populate\(\) match/
42124212
);
4213+
await assert.rejects(
4214+
() => Child.find().populate({ path: 'parent', match: () => ({ $or: [{ $where: 'typeof console !== "undefined" ? doesNotExist("foo") : true;' }] }) }),
4215+
/Cannot use \$where filter with populate\(\) match/
4216+
);
4217+
await assert.rejects(
4218+
() => Child.find().populate({ path: 'parent', match: () => ({ $and: [{ $where: 'typeof console !== "undefined" ? doesNotExist("foo") : true;' }] }) }),
4219+
/Cannot use \$where filter with populate\(\) match/
4220+
);
4221+
4222+
class MyClass {}
4223+
MyClass.prototype.$where = 'typeof console !== "undefined" ? doesNotExist("foo") : true;';
4224+
// OK because sift only looks through own properties
4225+
await Child.find().populate({ path: 'parent', match: () => new MyClass() });
42134226
});
42144227

42154228
it('multiple source docs', function(done) {

0 commit comments

Comments
 (0)