From 15fc186a517db3ed7f7d4f0e1550e5b1e9d13c01 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Wed, 18 May 2016 18:05:04 -0700 Subject: [PATCH] Extract query validation logic --- .../Storage/Mongo/MongoStorageAdapter.js | 7 +-- src/Adapters/Storage/Mongo/MongoTransform.js | 60 ++++++++++--------- src/Controllers/DatabaseController.js | 12 ++-- src/Routers/GlobalConfigRouter.js | 2 +- 4 files changed, 38 insertions(+), 43 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 949ef1cc..d6b0f1ab 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -185,12 +185,7 @@ export class MongoStorageAdapter { deleteObjectsByQuery(className, query, validate, schema) { return this.adaptiveCollection(className) .then(collection => { - if (query.ACL) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); - } - if (validate && Object.keys(query).some(restKey => !specialQuerykeys.includes(restKey) && !restKey.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/))) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: ${restKey}`); - } + transform.validateQuery(query); let mongoWhere = transform.transformWhere(className, query, schema); return collection.deleteMany(mongoWhere) }) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 279888c9..e92cacee 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -167,31 +167,9 @@ function transformQueryKeyValue(className, key, value, schema) { case '_perishable_token': case '_email_verify_token': return {key, value} case '$or': - if (!(value instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $or format - use an array value'); - } - if (value.some(subQuery => subQuery.ACL)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); - Object.keys(subQuery).forEach(restKey => { - if (!specialQuerykeys.includes(restKey) && !restKey.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: ${restKey}`); - } - }); - } - return {key: '$or', value: value.map(subQuery => transformWhere(className, subQuery, {}, schema))}; + return {key: '$or', value: value.map(subQuery => transformWhere(className, subQuery, schema))}; case '$and': - if (!(value instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $and format - use an array value'); - } - if (value.some(subQuery => subQuery.ACL)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); - Object.keys(subQuery).forEach(restKey => { - if (!specialQuerykeys.includes(restKey) && !restKey.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: ${restKey}`); - } - }); - } - return {key: '$and', value: value.map(subQuery => transformWhere(className, subQuery, {}, schema))}; + return {key: '$and', value: value.map(subQuery => transformWhere(className, subQuery, schema))}; default: // Other auth data const authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); @@ -233,17 +211,42 @@ function transformQueryKeyValue(className, key, value, schema) { } } +const validateQuery = query => { + if (query.ACL) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); + } + + if (query.$or) { + if (query.$or instanceof Array) { + query.$or.forEach(validateQuery); + } else { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $or format - use an array value.'); + } + } + + if (query.$and) { + if (query.$and instanceof Array) { + query.$and.forEach(validateQuery); + } else { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $and format - use an array value.'); + } + } + + Object.keys(query).forEach(key => { + if (!specialQuerykeys.includes(key) && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: ${key}`); + } + }); +} + // Main exposed method to help run queries. // restWhere is the "where" clause in REST API form. // Returns the mongo form of the query. // Throws a Parse.Error if the input query is invalid. const specialQuerykeys = ['$and', '$or', '_rperm', '_wperm', '_perishable_token', '_email_verify_token']; -function transformWhere(className, restWhere, { validate = true } = {}, schema) { +function transformWhere(className, restWhere, schema) { let mongoWhere = {}; for (let restKey in restWhere) { - if (validate && !specialQuerykeys.includes(restKey) && !restKey.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: ${restKey}`); - } let out = transformQueryKeyValue(className, restKey, restWhere[restKey], schema); mongoWhere[out.key] = out.value; } @@ -1045,6 +1048,7 @@ var FileCoder = { module.exports = { transformKey, + validateQuery, parseObjectToMongoObjectForCreate, transformUpdate, transformWhere, diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index ac0ada29..2657806b 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -184,10 +184,8 @@ DatabaseController.prototype.update = function(className, query, update, { throw error; }) .then(parseFormatSchema => { - if (query.ACL) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); - } - var mongoWhere = this.transform.transformWhere(className, query, {validate: !this.skipValidation}, parseFormatSchema); + this.transform.validateQuery(query); + var mongoWhere = this.transform.transformWhere(className, query, parseFormatSchema); mongoUpdate = this.transform.transformUpdate( schemaController, className, @@ -671,10 +669,8 @@ DatabaseController.prototype.find = function(className, query, { if (!isMaster) { query = addReadACL(query, aclGroup); } - if (query.ACL) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); - } - let mongoWhere = this.transform.transformWhere(className, query, {}, schema); + this.transform.validateQuery(query); + let mongoWhere = this.transform.transformWhere(className, query, schema); if (count) { delete mongoOptions.limit; return collection.count(mongoWhere, mongoOptions); diff --git a/src/Routers/GlobalConfigRouter.js b/src/Routers/GlobalConfigRouter.js index ab498522..5ab89b0b 100644 --- a/src/Routers/GlobalConfigRouter.js +++ b/src/Routers/GlobalConfigRouter.js @@ -24,7 +24,7 @@ export class GlobalConfigRouter extends PromiseRouter { return acc; }, {}); let database = req.config.database.WithoutValidation(); - return database.update('_GlobalConfig', {_id: 1}, update, {upsert: true}).then(() => { + return database.update('_GlobalConfig', {objectId: 1}, update, {upsert: true}).then(() => { return Promise.resolve({ response: { result: true } }); }); }