diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 81af4e97..e194dfe0 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -180,11 +180,11 @@ export class MongoStorageAdapter { // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined. // If there is some other error, reject with INTERNAL_SERVER_ERROR. - // Currently accepts validate for legacy reasons. Currently accepts the schema, that may not actually be necessary. - deleteObjectsByQuery(className, query, validate, schema) { + // Currently accepts the schema, that may not actually be necessary. + deleteObjectsByQuery(className, query, schema) { return this.adaptiveCollection(className) .then(collection => { - let mongoWhere = transform.transformWhere(className, query, { validate }, schema); + let mongoWhere = transform.transformWhere(className, query, schema); return collection.deleteMany(mongoWhere) }) .then(({ result }) => { diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 31c3a919..33aa666b 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -141,7 +141,7 @@ const valueAsDate = value => { return false; } -function transformQueryKeyValue(className, key, value, { validate } = {}, schema) { +function transformQueryKeyValue(className, key, value, schema) { switch(key) { case 'createdAt': if (valueAsDate(value)) { @@ -167,15 +167,9 @@ function transformQueryKeyValue(className, key, value, { validate } = {}, 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'); - } - 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'); - } - 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$/); @@ -184,9 +178,6 @@ function transformQueryKeyValue(className, key, value, { validate } = {}, schema // Special-case auth data. return {key: `_auth_data_${provider}.id`, value}; } - if (validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'invalid key name: ' + key); - } } const expectedTypeIsArray = @@ -223,14 +214,10 @@ function transformQueryKeyValue(className, key, value, { validate } = {}, schema // 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. -function transformWhere(className, restWhere, { validate = true } = {}, schema) { +function transformWhere(className, restWhere, schema) { let mongoWhere = {}; - if (restWhere['ACL']) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); - } for (let restKey in restWhere) { - let out = transformQueryKeyValue(className, restKey, restWhere[restKey], { validate }, schema); + let out = transformQueryKeyValue(className, restKey, restWhere[restKey], schema); mongoWhere[out.key] = out.value; } return mongoWhere; diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index fefd5afe..522a2f14 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -24,6 +24,35 @@ function addReadACL(query, acl) { return newQuery; } +const specialQuerykeys = ['$and', '$or', '_rperm', '_wperm', '_perishable_token', '_email_verify_token']; +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}`); + } + }); +} + function DatabaseController(adapter, { skipValidation } = {}) { this.adapter = adapter; @@ -174,6 +203,7 @@ DatabaseController.prototype.update = function(className, query, update, { if (acl) { query = addWriteACL(query, acl); } + validateQuery(query); return schemaController.getOneSchema(className) .catch(error => { // If the schema doesn't exist, pretend it exists with no fields. This behaviour @@ -184,7 +214,7 @@ DatabaseController.prototype.update = function(className, query, update, { throw error; }) .then(parseFormatSchema => { - var mongoWhere = this.transform.transformWhere(className, query, {validate: !this.skipValidation}, parseFormatSchema); + var mongoWhere = this.transform.transformWhere(className, query, parseFormatSchema); mongoUpdate = this.transform.transformUpdate( schemaController, className, @@ -328,6 +358,7 @@ DatabaseController.prototype.destroy = function(className, query, { acl } = {}) if (acl) { query = addWriteACL(query, acl); } + validateQuery(query); return schemaController.getOneSchema(className) .catch(error => { // If the schema doesn't exist, pretend it exists with no fields. This behaviour @@ -337,7 +368,7 @@ DatabaseController.prototype.destroy = function(className, query, { acl } = {}) } throw error; }) - .then(parseFormatSchema => this.adapter.deleteObjectsByQuery(className, query, !this.skipValidation, parseFormatSchema)) + .then(parseFormatSchema => this.adapter.deleteObjectsByQuery(className, query, parseFormatSchema)) .catch(error => { // When deleting sessions while changing passwords, don't throw an error if they don't have any sessions. if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) { @@ -668,7 +699,8 @@ DatabaseController.prototype.find = function(className, query, { if (!isMaster) { query = addReadACL(query, aclGroup); } - let mongoWhere = this.transform.transformWhere(className, query, {}, schema); + 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 } }); }); }