Skip to content

Commit ee24cd0

Browse files
committedAug 15, 2018
fix: sanitize query by default
1 parent 99bca4b commit ee24cd0

File tree

2 files changed

+113
-10
lines changed

2 files changed

+113
-10
lines changed
 

‎lib/mongodb.js

+34-10
Original file line numberDiff line numberDiff line change
@@ -840,19 +840,22 @@ function idIncluded(fields, idName) {
840840
return true;
841841
}
842842

843-
MongoDB.prototype.buildWhere = function(model, where) {
843+
MongoDB.prototype.buildWhere = function(model, where, options) {
844844
var self = this;
845845
var query = {};
846846
if (where === null || typeof where !== 'object') {
847847
return query;
848848
}
849+
850+
where = sanitizeFilter(where, options);
851+
849852
var idName = self.idName(model);
850853
Object.keys(where).forEach(function(k) {
851854
var cond = where[k];
852855
if (k === 'and' || k === 'or' || k === 'nor') {
853856
if (Array.isArray(cond)) {
854857
cond = cond.map(function(c) {
855-
return self.buildWhere(model, c);
858+
return self.buildWhere(model, c, options);
856859
});
857860
}
858861
query['$' + k] = cond;
@@ -961,6 +964,7 @@ MongoDB.prototype.buildSort = function(model, order, options) {
961964
}
962965
}
963966
if (order) {
967+
order = sanitizeFilter(order, options);
964968
var keys = order;
965969
if (typeof keys === 'string') {
966970
keys = keys.split(',');
@@ -1217,7 +1221,7 @@ MongoDB.prototype.all = function all(model, filter, options, callback) {
12171221
var idName = self.idName(model);
12181222
var query = {};
12191223
if (filter.where) {
1220-
query = self.buildWhere(model, filter.where);
1224+
query = self.buildWhere(model, filter.where, options);
12211225
}
12221226
var fields = filter.fields;
12231227

@@ -1308,7 +1312,8 @@ MongoDB.prototype.destroyAll = function destroyAll(
13081312
callback = where;
13091313
where = undefined;
13101314
}
1311-
where = self.buildWhere(model, where);
1315+
where = self.buildWhere(model, where, options);
1316+
13121317
this.execute(model, 'remove', where || {}, function(err, info) {
13131318
if (err) return callback && callback(err);
13141319

@@ -1335,7 +1340,7 @@ MongoDB.prototype.count = function count(model, where, options, callback) {
13351340
if (self.debug) {
13361341
debug('count', model, where);
13371342
}
1338-
where = self.buildWhere(model, where);
1343+
where = self.buildWhere(model, where, options);
13391344
this.execute(model, 'countDocuments', where, function(err, count) {
13401345
if (self.debug) {
13411346
debug('count.callback', model, err, count);
@@ -1506,9 +1511,9 @@ MongoDB.prototype.update = MongoDB.prototype.updateAll = function updateAll(
15061511
}
15071512
var idName = this.idName(model);
15081513

1509-
where = self.buildWhere(model, where);
1510-
delete data[idName];
1514+
where = self.buildWhere(model, where, options);
15111515

1516+
delete data[idName];
15121517
data = self.toDatabase(model, data);
15131518

15141519
// Check for other operators and sanitize the data obj
@@ -1798,6 +1803,23 @@ MongoDB.prototype.isObjectIDProperty = function(model, prop, value) {
17981803
}
17991804
};
18001805

1806+
function sanitizeFilter(filter, options) {
1807+
options = Object.assign({}, options);
1808+
if (options && options.disableSanitization) return filter;
1809+
if (!filter || typeof filter !== 'object') return filter;
1810+
1811+
for (const key in filter) {
1812+
if (key === '$where' || key === 'mapReduce') {
1813+
debug(`sanitizeFilter: deleting ${key}`);
1814+
delete filter[key];
1815+
}
1816+
}
1817+
1818+
return filter;
1819+
}
1820+
1821+
exports.sanitizeFilter = sanitizeFilter;
1822+
18011823
/**
18021824
* Find a matching model instances by the filter or create a new instance
18031825
*
@@ -1808,12 +1830,14 @@ MongoDB.prototype.isObjectIDProperty = function(model, prop, value) {
18081830
* @param {Object} filter The filter
18091831
* @param {Function} [callback] The callback function
18101832
*/
1811-
function optimizedFindOrCreate(model, filter, data, callback) {
1833+
function optimizedFindOrCreate(model, filter, data, options, callback) {
18121834
var self = this;
18131835
if (self.debug) {
18141836
debug('findOrCreate', model, filter, data);
18151837
}
18161838

1839+
if (!callback) callback = options;
1840+
18171841
var idValue = self.getIdValue(model, data);
18181842
var idName = self.idName(model);
18191843

@@ -1836,10 +1860,10 @@ function optimizedFindOrCreate(model, filter, data, callback) {
18361860
id = self.coerceId(model, id);
18371861
filter.where._id = id;
18381862
}
1839-
query = self.buildWhere(model, filter.where);
1863+
query = self.buildWhere(model, filter.where, options);
18401864
}
18411865

1842-
var sort = self.buildSort(model, filter.order);
1866+
var sort = self.buildSort(model, filter.order, options);
18431867

18441868
this.collection(model).findOneAndUpdate(
18451869
query,

‎test/mongodb.test.js

+79
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var should = require('./init.js');
1111
var testUtils = require('../lib/test-utils');
1212
var async = require('async');
1313
var sinon = require('sinon');
14+
var sanitizeFilter = require('../lib/mongodb').sanitizeFilter;
1415

1516
var GeoPoint = require('loopback-datasource-juggler').GeoPoint;
1617

@@ -717,6 +718,52 @@ describe('mongodb connector', function() {
717718
});
718719
});
719720

721+
it('should not return data for nested `$where` in where', function(done) {
722+
Post.create({title: 'Post1', content: 'Post1 content'}, (err, p1) => {
723+
Post.create({title: 'Post2', content: 'Post2 content'}, (err2, p2) => {
724+
Post.create({title: 'Post3', content: 'Post3 data'}, (err3, p3) => {
725+
Post.find({where: {$where: 'function() {return this.content.contains("content")}'}}, (err, p) => {
726+
should.not.exist(err);
727+
p.length.should.be.equal(3);
728+
done();
729+
});
730+
});
731+
});
732+
});
733+
});
734+
735+
it('should allow $where in where with options.disableSanitization', function(done) {
736+
Post.create({title: 'Post1', content: 'Post1 content'}, (err, p1) => {
737+
Post.create({title: 'Post2', content: 'Post2 content'}, (err2, p2) => {
738+
Post.create({title: 'Post3', content: 'Post3 data'}, (err3, p3) => {
739+
Post.find(
740+
{where: {$where: 'function() {return this.content.contains("content")}'}},
741+
{disableSanitization: true},
742+
(err, p) => {
743+
should.not.exist(err);
744+
p.length.should.be.equal(2);
745+
done();
746+
}
747+
);
748+
});
749+
});
750+
});
751+
});
752+
753+
it('does not execute a nested `$where`', function(done) {
754+
Post.create({title: 'Post1', content: 'Post1 content'}, (err, p1) => {
755+
Post.create({title: 'Post2', content: 'Post2 content'}, (err2, p2) => {
756+
Post.create({title: 'Post3', content: 'Post3 data'}, (err3, p3) => {
757+
Post.find({where: {content: {$where: 'function() {return this.content.contains("content")}'}}}, (err, p) => {
758+
should.not.exist(err);
759+
p.length.should.be.equal(0);
760+
done();
761+
});
762+
});
763+
});
764+
});
765+
});
766+
720767
it('should allow to find by id using where inq', function(done) {
721768
Post.create({title: 'Post1', content: 'Post1 content'}, function(
722769
err,
@@ -3218,6 +3265,38 @@ describe('mongodb connector', function() {
32183265
});
32193266
});
32203267

3268+
context('sanitizeFilter()', () => {
3269+
it('returns filter if not an object', () => {
3270+
const input = false;
3271+
const out = sanitizeFilter(input);
3272+
out.should.equal(input);
3273+
});
3274+
3275+
it('returns the filter if options.disableSanitization is true', () => {
3276+
const input = {key: 'value', $where: 'a value'};
3277+
const out = sanitizeFilter(input, {disableSanitization: true});
3278+
out.should.deepEqual(input);
3279+
});
3280+
3281+
it('removes `$where` property', () => {
3282+
const input = {key: 'value', $where: 'a value'};
3283+
const out = sanitizeFilter(input);
3284+
out.should.deepEqual({key: 'value'});
3285+
});
3286+
3287+
it('does not remove properties with `$` in it', () => {
3288+
const input = {key: 'value', $where: 'a value', random$key: 'random'};
3289+
const out = sanitizeFilter(input);
3290+
out.should.deepEqual({key: 'value', random$key: 'random'});
3291+
});
3292+
3293+
it('removes `mapReduce` property in the object', () => {
3294+
const input = {key: 'value', random$key: 'a value', mapReduce: 'map'};
3295+
const out = sanitizeFilter(input);
3296+
out.should.deepEqual({key: 'value', random$key: 'a value'});
3297+
});
3298+
});
3299+
32213300
after(function(done) {
32223301
User.destroyAll(function() {
32233302
Post.destroyAll(function() {

0 commit comments

Comments
 (0)
Please sign in to comment.