From ca717a8a560cf6aba3eceda06cd0570563960a91 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Sun, 27 Mar 2016 15:04:21 -0400 Subject: [PATCH 1/3] Moves transform to MongoTransform - Adds ACL query injection in MongoTransform --- ...ansform.spec.js => MongoTransform.spec.js} | 2 +- .../Storage/Mongo/MongoSchemaCollection.js | 5 +++ .../Storage/Mongo/MongoStorageAdapter.js | 5 +++ .../Storage/Mongo/MongoTransform.js} | 23 +++++++++++ src/Controllers/DatabaseController.js | 41 +++++-------------- src/Schema.js | 3 +- 6 files changed, 45 insertions(+), 34 deletions(-) rename spec/{transform.spec.js => MongoTransform.spec.js} (99%) rename src/{transform.js => Adapters/Storage/Mongo/MongoTransform.js} (97%) diff --git a/spec/transform.spec.js b/spec/MongoTransform.spec.js similarity index 99% rename from spec/transform.spec.js rename to spec/MongoTransform.spec.js index 16ce8c337a..0ffaffc04e 100644 --- a/spec/transform.spec.js +++ b/spec/MongoTransform.spec.js @@ -1,7 +1,7 @@ // These tests are unit tests designed to only test transform.js. "use strict"; -let transform = require('../src/transform'); +let transform = require('../src/Adapters/Storage/Mongo/MongoTransform'); let dd = require('deep-diff'); let mongodb = require('mongodb'); diff --git a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js index 1366846fbd..28fe615d84 100644 --- a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js +++ b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js @@ -1,5 +1,6 @@ import MongoCollection from './MongoCollection'; +import * as transform from './MongoTransform'; function mongoFieldToParseSchemaField(type) { if (type[0] === '*') { @@ -200,6 +201,10 @@ class MongoSchemaCollection { update = {'$set': update}; return this.upsertSchema(className, query, update); } + + get transform() { + return transform; + } } // Exported for testing reasons and because we haven't moved all mongo schema format diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 1de5fa3aa6..c67a5953d5 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -1,6 +1,7 @@ import MongoCollection from './MongoCollection'; import MongoSchemaCollection from './MongoSchemaCollection'; import {parse as parseUrl, format as formatUrl} from '../../../vendor/mongodbUrl'; +import * as transform from './MongoTransform'; import _ from 'lodash'; let mongodb = require('mongodb'); @@ -125,6 +126,10 @@ export class MongoStorageAdapter { .then(updateResult => this.schemaCollection()) .then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate)); } + + get transform() { + return transform; + } } export default MongoStorageAdapter; diff --git a/src/transform.js b/src/Adapters/Storage/Mongo/MongoTransform.js similarity index 97% rename from src/transform.js rename to src/Adapters/Storage/Mongo/MongoTransform.js index 245b2a5fc0..9cce34f1ed 100644 --- a/src/transform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -767,6 +767,27 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals } } +function addWriteACL(mongoWhere, acl) { + var writePerms = [ + {_wperm: {'$exists': false}} + ]; + for (var entry of acl) { + writePerms.push({_wperm: {'$in': [entry]}}); + } + return {'$and': [mongoWhere, {'$or': writePerms}]}; +} + +function addReadACL(mongoWhere, acl) { + var orParts = [ + {"_rperm" : { "$exists": false }}, + {"_rperm" : { "$in" : ["*"]}} + ]; + for (var entry of acl) { + orParts.push({"_rperm" : { "$in" : [entry]}}); + } + return {'$and': [mongoWhere, {'$or': orParts}]}; +} + var DateCoder = { JSONToDatabase(json) { return new Date(json.iso); @@ -860,5 +881,7 @@ module.exports = { transformCreate: transformCreate, transformUpdate: transformUpdate, transformWhere: transformWhere, + addReadACL: addReadACL, + addWriteACL: addWriteACL, untransformObject: untransformObject }; diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index de79f28d9d..0d808b516e 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -7,7 +7,6 @@ var mongodb = require('mongodb'); var Parse = require('parse/node').Parse; var Schema = require('./../Schema'); -var transform = require('./../transform'); const deepcopy = require('deepcopy'); function DatabaseController(adapter) { @@ -113,7 +112,7 @@ DatabaseController.prototype.validateObject = function(className, object, query, // Filters out any data that shouldn't be on this REST-formatted object. DatabaseController.prototype.untransformObject = function( schema, isMaster, aclGroup, className, mongoObject) { - var object = transform.untransformObject(schema, className, mongoObject); + var object = this.adapter.transform.untransformObject(schema, className, mongoObject); if (className !== '_User') { return object; @@ -159,17 +158,11 @@ DatabaseController.prototype.update = function(className, query, update, options .then(() => this.handleRelationUpdates(className, query.objectId, update)) .then(() => this.adaptiveCollection(className)) .then(collection => { - var mongoWhere = transform.transformWhere(schema, className, query); + var mongoWhere = this.adapter.transform.transformWhere(schema, className, query); if (options.acl) { - var writePerms = [ - {_wperm: {'$exists': false}} - ]; - for (var entry of options.acl) { - writePerms.push({_wperm: {'$in': [entry]}}); - } - mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]}; + mongoWhere = this.adapter.transform.addWriteACL(mongoWhere, options.acl); } - mongoUpdate = transform.transformUpdate(schema, className, update); + mongoUpdate = this.adapter.transform.transformUpdate(schema, className, update); return collection.findOneAndUpdate(mongoWhere, mongoUpdate); }) .then(result => { @@ -296,16 +289,9 @@ DatabaseController.prototype.destroy = function(className, query, options = {}) }) .then(() => this.adaptiveCollection(className)) .then(collection => { - let mongoWhere = transform.transformWhere(schema, className, query); - + let mongoWhere = this.adapter.transform.transformWhere(schema, className, query, options); if (options.acl) { - var writePerms = [ - { _wperm: { '$exists': false } } - ]; - for (var entry of options.acl) { - writePerms.push({ _wperm: { '$in': [entry] } }); - } - mongoWhere = { '$and': [mongoWhere, { '$or': writePerms }] }; + mongoWhere = this.adapter.transform.addWriteACL(mongoWhere, options.acl); } return collection.deleteMany(mongoWhere); }) @@ -341,7 +327,7 @@ DatabaseController.prototype.create = function(className, object, options) { .then(() => this.handleRelationUpdates(className, null, object)) .then(() => this.adaptiveCollection(className)) .then(coll => { - var mongoObject = transform.transformCreate(schema, className, object); + var mongoObject = this.adapter.transform.transformCreate(schema, className, object); return coll.insertOne(mongoObject); }) .then(result => { @@ -596,7 +582,7 @@ DatabaseController.prototype.find = function(className, query, options = {}) { if (options.sort) { mongoOptions.sort = {}; for (let key in options.sort) { - let mongoKey = transform.transformKey(schema, className, key); + let mongoKey = this.adapter.transform.transformKey(schema, className, key); mongoOptions.sort[mongoKey] = options.sort[key]; } } @@ -613,16 +599,9 @@ DatabaseController.prototype.find = function(className, query, options = {}) { .then(() => this.reduceInRelation(className, query, schema)) .then(() => this.adaptiveCollection(className)) .then(collection => { - let mongoWhere = transform.transformWhere(schema, className, query); + let mongoWhere = this.adapter.transform.transformWhere(schema, className, query); if (!isMaster) { - let orParts = [ - {"_rperm" : { "$exists": false }}, - {"_rperm" : { "$in" : ["*"]}} - ]; - for (let acl of aclGroup) { - orParts.push({"_rperm" : { "$in" : [acl]}}); - } - mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]}; + mongoWhere = this.adapter.transform.addReadACL(mongoWhere, aclGroup); } if (options.count) { delete mongoOptions.limit; diff --git a/src/Schema.js b/src/Schema.js index 46983b4049..880a224f86 100644 --- a/src/Schema.js +++ b/src/Schema.js @@ -15,7 +15,6 @@ // TODO: hide all schema logic inside the database adapter. const Parse = require('parse/node').Parse; -const transform = require('./transform'); import MongoSchemaCollection from './Adapters/Storage/Mongo/MongoSchemaCollection'; import _ from 'lodash'; @@ -429,7 +428,7 @@ class Schema { validateField(className, fieldName, type, freeze) { return this.reloadData().then(() => { // Just to check that the fieldName is valid - transform.transformKey(this, className, fieldName); + this._collection.transform.transformKey(this, className, fieldName); if( fieldName.indexOf(".") > 0 ) { // subdocument key (x.y) => ok if x is of type 'object' From fc200d4ed529ae0872767810d9c9e655b01c24b5 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 28 Mar 2016 11:32:52 -0400 Subject: [PATCH 2/3] Removes adaptiveCollection from DatabaseController - All collections manipulations are now handled by a DBController - Adds optional flags to configure an unsafe databaseController for direct access - Adds ability to configure RestWrite with multiple writes - Moves some transfirmations to MongoTransform as they output specific code --- spec/ParseGlobalConfig.spec.js | 33 ++++++- spec/ParseHooks.spec.js | 56 +++++------ spec/PushController.spec.js | 2 - spec/ValidationAndPasswordsReset.spec.js | 9 +- .../Storage/Mongo/MongoStorageAdapter.js | 2 +- src/Adapters/Storage/Mongo/MongoTransform.js | 86 +++++++++++++++-- src/Controllers/DatabaseController.js | 93 +++++++++++++------ src/Controllers/HooksController.js | 31 ++----- src/Controllers/PushController.js | 29 +++--- src/Controllers/UserController.js | 69 +++++--------- src/RestQuery.js | 55 +---------- src/Routers/GlobalConfigRouter.js | 37 ++++---- src/Routers/SchemasRouter.js | 17 +--- src/pushStatusHandler.js | 34 +++---- 14 files changed, 284 insertions(+), 269 deletions(-) diff --git a/spec/ParseGlobalConfig.spec.js b/spec/ParseGlobalConfig.spec.js index 4b684553dc..534527a5bf 100644 --- a/spec/ParseGlobalConfig.spec.js +++ b/spec/ParseGlobalConfig.spec.js @@ -7,7 +7,7 @@ let Config = require('../src/Config'); describe('a GlobalConfig', () => { beforeEach(done => { let config = new Config('test'); - config.database.adaptiveCollection('_GlobalConfig') + config.database.adapter.adaptiveCollection('_GlobalConfig') .then(coll => coll.upsertOne({ '_id': 1 }, { $set: { params: { companies: ['US', 'DK'] } } })) .then(() => { done(); }); }); @@ -43,6 +43,35 @@ describe('a GlobalConfig', () => { }); }); + it('properly handles delete op', (done) => { + request.put({ + url : 'http://localhost:8378/1/config', + json : true, + body : { params: { companies: {__op: 'Delete'}, foo: 'bar' } }, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key' : 'test' + } + }, (error, response, body) => { + expect(response.statusCode).toEqual(200); + expect(body.result).toEqual(true); + request.get({ + url : 'http://localhost:8378/1/config', + json : true, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key' : 'test' + } + }, (error, response, body) => { + expect(response.statusCode).toEqual(200); + expect(body.params.companies).toBeUndefined(); + expect(body.params.foo).toBe('bar'); + expect(Object.keys(body.params).length).toBe(1); + done(); + }); + }); + }); + it('fail to update if master key is missing', (done) => { request.put({ url : 'http://localhost:8378/1/config', @@ -61,7 +90,7 @@ describe('a GlobalConfig', () => { it('failed getting config when it is missing', (done) => { let config = new Config('test'); - config.database.adaptiveCollection('_GlobalConfig') + config.database.adapter.adaptiveCollection('_GlobalConfig') .then(coll => coll.deleteOne({ '_id': 1 })) .then(() => { request.get({ diff --git a/spec/ParseHooks.spec.js b/spec/ParseHooks.spec.js index e24211383e..fc47be1800 100644 --- a/spec/ParseHooks.spec.js +++ b/spec/ParseHooks.spec.js @@ -16,7 +16,7 @@ app.listen(12345); describe('Hooks', () => { - + it("should have some hooks registered", (done) => { Parse.Hooks.getFunctions().then((res) => { expect(res.constructor).toBe(Array.prototype.constructor); @@ -26,7 +26,7 @@ describe('Hooks', () => { done(); }); }); - + it("should have some triggers registered", (done) => { Parse.Hooks.getTriggers().then( (res) => { expect(res.constructor).toBe(Array.prototype.constructor); @@ -59,7 +59,7 @@ describe('Hooks', () => { }).then((res) => { expect(res.functionName).toBe("My-Test-Function"); expect(res.url).toBe("http://anotherurl") - + return Parse.Hooks.deleteFunction("My-Test-Function"); }, (err) => { fail(err); @@ -81,7 +81,7 @@ describe('Hooks', () => { done(); }) }); - + it("should CRUD a trigger registration", (done) => { // Create Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => { @@ -105,7 +105,7 @@ describe('Hooks', () => { }).then((res) => { expect(res.className).toBe("MyClass"); expect(res.url).toBe("http://anotherurl") - + return Parse.Hooks.deleteTrigger("MyClass","beforeDelete"); }, (err) => { fail(err); @@ -127,7 +127,7 @@ describe('Hooks', () => { done(); }); }); - + it("should fail to register hooks without Master Key", (done) => { request.post(Parse.serverURL+"/hooks/functions", { headers: { @@ -141,7 +141,7 @@ describe('Hooks', () => { done(); }) }); - + it("should fail trying to create two times the same function", (done) => { Parse.Hooks.createFunction("my_new_function", "http://url.com").then( () => { return Parse.Hooks.createFunction("my_new_function", "http://url.com") @@ -162,7 +162,7 @@ describe('Hooks', () => { done(); }) }); - + it("should fail trying to create two times the same trigger", (done) => { Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then( () => { return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com") @@ -181,7 +181,7 @@ describe('Hooks', () => { done(); }) }); - + it("should fail trying to update a function that don't exist", (done) => { Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then( () => { fail("Should not succeed") @@ -198,7 +198,7 @@ describe('Hooks', () => { done(); }); }); - + it("should fail trying to update a trigger that don't exist", (done) => { Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then( () => { fail("Should not succeed") @@ -215,8 +215,8 @@ describe('Hooks', () => { done(); }); }); - - + + it("should fail trying to create a malformed function", (done) => { Parse.Hooks.createFunction("MyFunction").then( (res) => { fail(res); @@ -226,7 +226,7 @@ describe('Hooks', () => { done(); }); }); - + it("should fail trying to create a malformed function (REST)", (done) => { request.post(Parse.serverURL+"/hooks/functions", { headers: { @@ -241,16 +241,16 @@ describe('Hooks', () => { done(); }) }); - - + + it("should create hooks and properly preload them", (done) => { - + var promises = []; for (var i = 0; i<5; i++) { promises.push(Parse.Hooks.createTrigger("MyClass"+i, "beforeSave", "http://url.com/beforeSave/"+i)); promises.push(Parse.Hooks.createFunction("AFunction"+i, "http://url.com/function"+i)); } - + Parse.Promise.when(promises).then(function(results){ for (var i=0; i<5; i++) { // Delete everything from memory, as the server just started @@ -263,7 +263,7 @@ describe('Hooks', () => { return hooksController.load() }, (err) => { console.error(err); - fail(); + fail('Should properly create all hooks'); done(); }).then(function() { for (var i=0; i<5; i++) { @@ -273,17 +273,17 @@ describe('Hooks', () => { done(); }, (err) => { console.error(err); - fail(); + fail('should properly load all hooks'); done(); }) }); - + it("should run the function on the test server", (done) => { - + app.post("/SomeFunction", function(req, res) { res.json({success:"OK!"}); }); - + Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunction").then(function(){ return Parse.Cloud.run("SOME_TEST_FUNCTION") }, (err) => { @@ -299,9 +299,9 @@ describe('Hooks', () => { done(); }) }); - + it("should run the function on the test server", (done) => { - + app.post("/SomeFunctionError", function(req, res) { res.json({error: {code: 1337, error: "hacking that one!"}}); }); @@ -322,8 +322,8 @@ describe('Hooks', () => { done(); }); }); - - + + it("should run the beforeSave hook on the test server", (done) => { var triggerCount = 0; app.post("/BeforeSaveSome", function(req, res) { @@ -350,7 +350,7 @@ describe('Hooks', () => { done(); }); }); - + it("should run the afterSave hook on the test server", (done) => { var triggerCount = 0; var newObjectId; @@ -387,4 +387,4 @@ describe('Hooks', () => { done(); }); }); -}); \ No newline at end of file +}); diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 9dd96bc9a9..fbb4124491 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -184,7 +184,6 @@ describe('PushController', () => { }).then((result) => { done(); }, (err) => { - console.error(err); fail("should not fail"); done(); }); @@ -233,7 +232,6 @@ describe('PushController', () => { }).then((result) => { done(); }, (err) => { - console.error(err); fail("should not fail"); done(); }); diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index 20942ce096..e953204931 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -266,7 +266,10 @@ describe("Email Verification", () => { .then((user) => { return user.save(); }).then((user) => { - return Parse.User.requestPasswordReset("cool_guy@parse.com"); + return Parse.User.requestPasswordReset("cool_guy@parse.com").catch((err) => { + fail('Should not fail requesting a password'); + done(); + }) }).then(() => { expect(calls).toBe(2); done(); @@ -551,7 +554,7 @@ describe("Password Reset", () => { Parse.User.requestPasswordReset('user@parse.com', { error: (err) => { console.error(err); - fail("Should not fail"); + fail("Should not fail requesting a password"); done(); } }); @@ -628,7 +631,7 @@ describe("Password Reset", () => { Parse.User.logIn("zxcv", "hello").then(function(user){ let config = new Config('test'); - config.database.adaptiveCollection('_User') + config.database.adapter.adaptiveCollection('_User') .then(coll => coll.find({ 'username': 'zxcv' }, { limit: 1 })) .then((results) => { // _perishable_token should be unset after reset password diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index c67a5953d5..164282a984 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -58,7 +58,7 @@ export class MongoStorageAdapter { schemaCollection() { return this.connect() - .then(() => this.adaptiveCollection(this._collectionPrefix + MongoSchemaCollectionName)) + .then(() => this.adaptiveCollection(MongoSchemaCollectionName)) .then(collection => new MongoSchemaCollection(collection)); } diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 9cce34f1ed..6ca8aba59c 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -1,4 +1,4 @@ -import log from './logger'; +import log from '../../../logger'; import _ from 'lodash'; var mongodb = require('mongodb'); var Parse = require('parse/node').Parse; @@ -185,14 +185,16 @@ export function transformKeyValue(schema, className, restKey, restValue, options // 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(schema, className, restWhere) { +function transformWhere(schema, className, restWhere, options = {validate: true}) { let mongoWhere = {}; if (restWhere['ACL']) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); } + let transformKeyOptions = {query: true}; + transformKeyOptions.validate = options.validate; for (let restKey in restWhere) { let out = transformKeyValue(schema, className, restKey, restWhere[restKey], - {query: true, validate: true}); + transformKeyOptions); mongoWhere[out.key] = out.value; } return mongoWhere; @@ -767,6 +769,66 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals } } +function transformSelect(selectObject, key ,objects) { + var values = []; + for (var result of objects) { + values.push(result[key]); + } + delete selectObject['$select']; + if (Array.isArray(selectObject['$in'])) { + selectObject['$in'] = selectObject['$in'].concat(values); + } else { + selectObject['$in'] = values; + } +} + +function transformDontSelect(dontSelectObject, key, objects) { + var values = []; + for (var result of objects) { + values.push(result[key]); + } + delete dontSelectObject['$dontSelect']; + if (Array.isArray(dontSelectObject['$nin'])) { + dontSelectObject['$nin'] = dontSelectObject['$nin'].concat(values); + } else { + dontSelectObject['$nin'] = values; + } +} + +function transformInQuery(inQueryObject, className, results) { + var values = []; + for (var result of results) { + values.push({ + __type: 'Pointer', + className: className, + objectId: result.objectId + }); + } + delete inQueryObject['$inQuery']; + if (Array.isArray(inQueryObject['$in'])) { + inQueryObject['$in'] = inQueryObject['$in'].concat(values); + } else { + inQueryObject['$in'] = values; + } +} + +function transformNotInQuery(notInQueryObject, className, results) { + var values = []; + for (var result of results) { + values.push({ + __type: 'Pointer', + className: className, + objectId: result.objectId + }); + } + delete notInQueryObject['$notInQuery']; + if (Array.isArray(notInQueryObject['$nin'])) { + notInQueryObject['$nin'] = notInQueryObject['$nin'].concat(values); + } else { + notInQueryObject['$nin'] = values; + } +} + function addWriteACL(mongoWhere, acl) { var writePerms = [ {_wperm: {'$exists': false}} @@ -877,11 +939,15 @@ var FileCoder = { }; module.exports = { - transformKey: transformKey, - transformCreate: transformCreate, - transformUpdate: transformUpdate, - transformWhere: transformWhere, - addReadACL: addReadACL, - addWriteACL: addWriteACL, - untransformObject: untransformObject + transformKey, + transformCreate, + transformUpdate, + transformWhere, + transformSelect, + transformDontSelect, + transformInQuery, + transformNotInQuery, + addReadACL, + addWriteACL, + untransformObject }; diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 0d808b516e..76894ff3c7 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -9,15 +9,25 @@ var Parse = require('parse/node').Parse; var Schema = require('./../Schema'); const deepcopy = require('deepcopy'); -function DatabaseController(adapter) { +function DatabaseController(adapter, { unsafe } = {}) { this.adapter = adapter; // We don't want a mutable this.schema, because then you could have // one request that uses different schemas for different parts of // it. Instead, use loadSchema to get a schema. this.schemaPromise = null; - + this.unsafe = !!unsafe; this.connect(); + + Object.defineProperty(this, 'transform', { + get: function() { + return adapter.transform; + } + }) +} + +DatabaseController.prototype.Unsafe = function() { + return new DatabaseController(this.adapter, {collectionPrefix: this.collectionPrefix, unsafe: true}); } // Connects to the database. Returns a promise that resolves when the @@ -26,10 +36,6 @@ DatabaseController.prototype.connect = function() { return this.adapter.connect(); }; -DatabaseController.prototype.adaptiveCollection = function(className) { - return this.adapter.adaptiveCollection(className); -}; - DatabaseController.prototype.schemaCollection = function() { return this.adapter.schemaCollection(); }; @@ -43,6 +49,9 @@ DatabaseController.prototype.dropCollection = function(className) { }; DatabaseController.prototype.validateClassName = function(className) { + if (this.unsafe) { + return Promise.resolve(); + } if (!Schema.classNameIsValid(className)) { const error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className); return Promise.reject(error); @@ -112,7 +121,7 @@ DatabaseController.prototype.validateObject = function(className, object, query, // Filters out any data that shouldn't be on this REST-formatted object. DatabaseController.prototype.untransformObject = function( schema, isMaster, aclGroup, className, mongoObject) { - var object = this.adapter.transform.untransformObject(schema, className, mongoObject); + var object = this.transform.untransformObject(schema, className, mongoObject); if (className !== '_User') { return object; @@ -135,7 +144,7 @@ DatabaseController.prototype.untransformObject = function( // acl: a list of strings. If the object to be updated has an ACL, // one of the provided strings must provide the caller with // write permissions. -DatabaseController.prototype.update = function(className, query, update, options) { +DatabaseController.prototype.update = function(className, query, update, options = {}) { const originalUpdate = update; // Make a copy of the object, so we don't mutate the incoming data. @@ -156,20 +165,29 @@ DatabaseController.prototype.update = function(className, query, update, options return Promise.resolve(); }) .then(() => this.handleRelationUpdates(className, query.objectId, update)) - .then(() => this.adaptiveCollection(className)) + .then(() => this.adapter.adaptiveCollection(className)) .then(collection => { - var mongoWhere = this.adapter.transform.transformWhere(schema, className, query); + var mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.unsafe}); if (options.acl) { - mongoWhere = this.adapter.transform.addWriteACL(mongoWhere, options.acl); + mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl); + } + mongoUpdate = this.transform.transformUpdate(schema, className, update, {validate: !this.unsafe}); + if (options.many) { + return collection.updateMany(mongoWhere, mongoUpdate); + }else if (options.upsert) { + return collection.upsertOne(mongoWhere, mongoUpdate); + } else { + return collection.findOneAndUpdate(mongoWhere, mongoUpdate); } - mongoUpdate = this.adapter.transform.transformUpdate(schema, className, update); - return collection.findOneAndUpdate(mongoWhere, mongoUpdate); }) .then(result => { if (!result) { return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.')); } + if (this.unsafe) { + return Promise.resolve(result); + } return sanitizeDatabaseResult(originalUpdate, result); }); }; @@ -248,7 +266,7 @@ DatabaseController.prototype.addRelation = function(key, fromClassName, fromId, owningId : fromId }; let className = `_Join:${key}:${fromClassName}`; - return this.adaptiveCollection(className).then((coll) => { + return this.adapter.adaptiveCollection(className).then((coll) => { return coll.upsertOne(doc, doc); }); }; @@ -262,7 +280,7 @@ DatabaseController.prototype.removeRelation = function(key, fromClassName, fromI owningId: fromId }; let className = `_Join:${key}:${fromClassName}`; - return this.adaptiveCollection(className).then(coll => { + return this.adapter.adaptiveCollection(className).then(coll => { return coll.deleteOne(doc); }); }; @@ -287,11 +305,11 @@ DatabaseController.prototype.destroy = function(className, query, options = {}) } return Promise.resolve(); }) - .then(() => this.adaptiveCollection(className)) + .then(() => this.adapter.adaptiveCollection(className)) .then(collection => { - let mongoWhere = this.adapter.transform.transformWhere(schema, className, query, options); + let mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.unsafe}); if (options.acl) { - mongoWhere = this.adapter.transform.addWriteACL(mongoWhere, options.acl); + mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl); } return collection.deleteMany(mongoWhere); }) @@ -306,7 +324,7 @@ DatabaseController.prototype.destroy = function(className, query, options = {}) // Inserts an object into the database. // Returns a promise that resolves successfully iff the object saved. -DatabaseController.prototype.create = function(className, object, options) { +DatabaseController.prototype.create = function(className, object, options = {}) { // Make a copy of the object, so we don't mutate the incoming data. let originalObject = object; object = deepcopy(object); @@ -325,9 +343,9 @@ DatabaseController.prototype.create = function(className, object, options) { return Promise.resolve(); }) .then(() => this.handleRelationUpdates(className, null, object)) - .then(() => this.adaptiveCollection(className)) + .then(() => this.adapter.adaptiveCollection(className)) .then(coll => { - var mongoObject = this.adapter.transform.transformCreate(schema, className, object); + var mongoObject = this.transform.transformCreate(schema, className, object); return coll.insertOne(mongoObject); }) .then(result => { @@ -356,7 +374,7 @@ DatabaseController.prototype.canAddField = function(schema, className, object, a // to avoid Mongo-format dependencies. // Returns a promise that resolves to a list of items. DatabaseController.prototype.mongoFind = function(className, query, options = {}) { - return this.adaptiveCollection(className) + return this.adapter.adaptiveCollection(className) .then(collection => collection.find(query, options)); }; @@ -389,7 +407,7 @@ function keysForQuery(query) { // Returns a promise for a list of related ids given an owning id. // className here is the owning className. DatabaseController.prototype.relatedIds = function(className, key, owningId) { - return this.adaptiveCollection(joinTableName(className, key)) + return this.adapter.adaptiveCollection(joinTableName(className, key)) .then(coll => coll.find({owningId : owningId})) .then(results => results.map(r => r.relatedId)); }; @@ -397,7 +415,7 @@ DatabaseController.prototype.relatedIds = function(className, key, owningId) { // Returns a promise for a list of owning ids given some related ids. // className here is the owning className. DatabaseController.prototype.owningIds = function(className, key, relatedIds) { - return this.adaptiveCollection(joinTableName(className, key)) + return this.adapter.adaptiveCollection(joinTableName(className, key)) .then(coll => coll.find({ relatedId: { '$in': relatedIds } })) .then(results => results.map(r => r.owningId)); }; @@ -582,7 +600,7 @@ DatabaseController.prototype.find = function(className, query, options = {}) { if (options.sort) { mongoOptions.sort = {}; for (let key in options.sort) { - let mongoKey = this.adapter.transform.transformKey(schema, className, key); + let mongoKey = this.transform.transformKey(schema, className, key); mongoOptions.sort[mongoKey] = options.sort[key]; } } @@ -597,11 +615,11 @@ DatabaseController.prototype.find = function(className, query, options = {}) { }) .then(() => this.reduceRelationKeys(className, query)) .then(() => this.reduceInRelation(className, query, schema)) - .then(() => this.adaptiveCollection(className)) + .then(() => this.adapter.adaptiveCollection(className)) .then(collection => { - let mongoWhere = this.adapter.transform.transformWhere(schema, className, query); + let mongoWhere = this.transform.transformWhere(schema, className, query); if (!isMaster) { - mongoWhere = this.adapter.transform.addReadACL(mongoWhere, aclGroup); + mongoWhere = this.transform.addReadACL(mongoWhere, aclGroup); } if (options.count) { delete mongoOptions.limit; @@ -618,6 +636,25 @@ DatabaseController.prototype.find = function(className, query, options = {}) { }); }; +DatabaseController.prototype.deleteSchema = function(className) { + return this.collectionExists(className) + .then(exist => { + if (!exist) { + return Promise.resolve(); + } + return this.adapter.adaptiveCollection(className) + .then(collection => { + return collection.count() + .then(count => { + if (count > 0) { + throw new Parse.Error(255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.`); + } + return collection.drop(); + }) + }) + }) +} + function joinTableName(className, key) { return `_Join:${key}:${className}`; } diff --git a/src/Controllers/HooksController.js b/src/Controllers/HooksController.js index cbf26f8a1a..ebb4e5aa74 100644 --- a/src/Controllers/HooksController.js +++ b/src/Controllers/HooksController.js @@ -15,6 +15,7 @@ export class HooksController { constructor(applicationId:string, collectionPrefix:string = '') { this._applicationId = applicationId; this._collectionPrefix = collectionPrefix; + this.database = DatabaseAdapter.getDatabaseConnection(this._applicationId, this._collectionPrefix).Unsafe(); } load() { @@ -26,18 +27,6 @@ export class HooksController { }); } - getCollection() { - if (this._collection) { - return Promise.resolve(this._collection) - } - - let database = DatabaseAdapter.getDatabaseConnection(this._applicationId, this._collectionPrefix); - return database.adaptiveCollection(DefaultHooksCollectionName).then(collection => { - this._collection = collection; - return collection; - }); - } - getFunction(functionName) { return this._getHooks({ functionName: functionName }, 1).then(results => results[0]); } @@ -64,17 +53,13 @@ export class HooksController { return this._removeHooks({ className: className, triggerName: triggerName }); } - _getHooks(query, limit) { + _getHooks(query = {}, limit) { let options = limit ? { limit: limit } : undefined; - return this.getCollection().then(collection => collection.find(query, options)); + return this.database.find(DefaultHooksCollectionName, query); } _removeHooks(query) { - return this.getCollection().then(collection => { - return collection.deleteMany(query); - }).then(() => { - return {}; - }); + return this.database.destroy(DefaultHooksCollectionName, query); } saveHook(hook) { @@ -86,11 +71,9 @@ export class HooksController { } else { throw new Parse.Error(143, "invalid hook declaration"); } - return this.getCollection() - .then(collection => collection.upsertOne(query, hook)) - .then(() => { - return hook; - }); + return this.database.update(DefaultHooksCollectionName, query, hook, {upsert: true}).then(() => { + return Promise.resolve(hook); + }) } addHookToTriggers(hook) { diff --git a/src/Controllers/PushController.js b/src/Controllers/PushController.js index a2ac327e54..40f7885537 100644 --- a/src/Controllers/PushController.js +++ b/src/Controllers/PushController.js @@ -5,6 +5,8 @@ import AdaptableController from './AdaptableController'; import { PushAdapter } from '../Adapters/Push/PushAdapter'; import deepcopy from 'deepcopy'; import RestQuery from '../RestQuery'; +import RestWrite from '../RestWrite'; +import { master } from '../Auth'; import pushStatusHandler from '../pushStatusHandler'; const FEATURE_NAME = 'push'; @@ -54,30 +56,25 @@ export class PushController extends AdaptableController { } if (body.data && body.data.badge) { let badge = body.data.badge; - let op = {}; + let restUpdate = {}; if (typeof badge == 'string' && badge.toLowerCase() === 'increment') { - op = { $inc: { badge: 1 } } + restUpdate = { badge: { __op: 'Increment', amount: 1 } } } else if (Number(badge)) { - op = { $set: { badge: badge } } + restUpdate = { badge: badge } } else { throw "Invalid value for badge, expected number or 'Increment'"; } let updateWhere = deepcopy(where); badgeUpdate = () => { - let badgeQuery = new RestQuery(config, auth, '_Installation', updateWhere); - return badgeQuery.buildRestWhere().then(() => { - let restWhere = deepcopy(badgeQuery.restWhere); - // Force iOS only devices - if (!restWhere['$and']) { - restWhere['$and'] = [badgeQuery.restWhere]; - } - restWhere['$and'].push({ - 'deviceType': 'ios' - }); - return config.database.adaptiveCollection("_Installation") - .then(coll => coll.updateMany(restWhere, op)); - }) + updateWhere.deviceType = 'ios'; + // Build a real RestQuery so we can use it in RestWrite + let restQuery = new RestQuery(config, master(config), '_Installation', updateWhere); + return restQuery.buildRestWhere().then(() => { + let write = new RestWrite(config, master(config), '_Installation', restQuery.restWhere, restUpdate); + write.runOptions.many = true; + return write.execute(); + }); } } let pushStatus = pushStatusHandler(config); diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index d855c5f04e..74bcdebadf 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -45,38 +45,29 @@ export class UserController extends AdaptableController { // TODO: Better error here. return Promise.reject(); } - - return this.config.database - .adaptiveCollection('_User') - .then(collection => { - // Need direct database access because verification token is not a parse field - return collection.findOneAndUpdate({ - username: username, - _email_verify_token: token - }, {$set: {emailVerified: true}}); - }) - .then(document => { - if (!document) { - return Promise.reject(); - } - return document; - }); + let database = this.config.database.Unsafe(); + return database.update('_User', { + username: username, + _email_verify_token: token + }, {emailVerified: true}).then(document => { + if (!document) { + return Promise.reject(); + } + return Promise.resolve(document); + }); } checkResetTokenValidity(username, token) { - return this.config.database.adaptiveCollection('_User') - .then(collection => { - return collection.find({ - username: username, - _perishable_token: token - }, { limit: 1 }); - }) - .then(results => { - if (results.length != 1) { - return Promise.reject(); - } - return results[0]; - }); + let database = this.config.database.Unsafe(); + return database.find('_User', { + username: username, + _perishable_token: token + }, {limit: 1}).then(results => { + if (results.length != 1) { + return Promise.reject(); + } + return results[0]; + }); } getUserIfNeeded(user) { @@ -124,15 +115,8 @@ export class UserController extends AdaptableController { setPasswordResetToken(email) { let token = randomString(25); - return this.config.database - .adaptiveCollection('_User') - .then(collection => { - // Need direct database access because verification token is not a parse field - return collection.findOneAndUpdate( - { email: email}, // query - { $set: { _perishable_token: token } } // update - ); - }); + let database = this.config.database.Unsafe(); + return database.update('_User', {email: email}, {_perishable_token: token}); } sendPasswordResetEmail(email) { @@ -166,14 +150,11 @@ export class UserController extends AdaptableController { updatePassword(username, token, password, config) { return this.checkResetTokenValidity(username, token).then((user) => { - return updateUserPassword(user._id, password, this.config); + return updateUserPassword(user.objectId, password, this.config); }).then(() => { // clear reset password token - return this.config.database.adaptiveCollection('_User').then(function (collection) { - // Need direct database access because verification token is not a parse field - return collection.findOneAndUpdate({ username: username },// query - { $unset: { _perishable_token: null } } // update - ); + return this.config.database.Unsafe().update('_User', { username }, { + _perishable_token: {__op: 'Delete'} }); }); } diff --git a/src/RestQuery.js b/src/RestQuery.js index 68b50173f3..4a038dcf96 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -213,20 +213,7 @@ RestQuery.prototype.replaceInQuery = function() { this.config, this.auth, inQueryValue.className, inQueryValue.where, additionalOptions); return subquery.execute().then((response) => { - var values = []; - for (var result of response.results) { - values.push({ - __type: 'Pointer', - className: subquery.className, - objectId: result.objectId - }); - } - delete inQueryObject['$inQuery']; - if (Array.isArray(inQueryObject['$in'])) { - inQueryObject['$in'] = inQueryObject['$in'].concat(values); - } else { - inQueryObject['$in'] = values; - } + this.config.database.transform.transformInQuery(inQueryObject, subquery.className, response.results); // Recurse to repeat return this.replaceInQuery(); }); @@ -257,21 +244,7 @@ RestQuery.prototype.replaceNotInQuery = function() { this.config, this.auth, notInQueryValue.className, notInQueryValue.where, additionalOptions); return subquery.execute().then((response) => { - var values = []; - for (var result of response.results) { - values.push({ - __type: 'Pointer', - className: subquery.className, - objectId: result.objectId - }); - } - delete notInQueryObject['$notInQuery']; - if (Array.isArray(notInQueryObject['$nin'])) { - notInQueryObject['$nin'] = notInQueryObject['$nin'].concat(values); - } else { - notInQueryObject['$nin'] = values; - } - + this.config.database.transform.transformNotInQuery(notInQueryObject, subquery.className, response.results); // Recurse to repeat return this.replaceNotInQuery(); }); @@ -308,17 +281,7 @@ RestQuery.prototype.replaceSelect = function() { this.config, this.auth, selectValue.query.className, selectValue.query.where, additionalOptions); return subquery.execute().then((response) => { - var values = []; - for (var result of response.results) { - values.push(result[selectValue.key]); - } - delete selectObject['$select']; - if (Array.isArray(selectObject['$in'])) { - selectObject['$in'] = selectObject['$in'].concat(values); - } else { - selectObject['$in'] = values; - } - + this.config.database.transform.transformSelect(selectObject, selectValue.key, response.results); // Keep replacing $select clauses return this.replaceSelect(); }) @@ -353,17 +316,7 @@ RestQuery.prototype.replaceDontSelect = function() { this.config, this.auth, dontSelectValue.query.className, dontSelectValue.query.where, additionalOptions); return subquery.execute().then((response) => { - var values = []; - for (var result of response.results) { - values.push(result[dontSelectValue.key]); - } - delete dontSelectObject['$dontSelect']; - if (Array.isArray(dontSelectObject['$nin'])) { - dontSelectObject['$nin'] = dontSelectObject['$nin'].concat(values); - } else { - dontSelectObject['$nin'] = values; - } - + this.config.database.transform.transformDontSelect(dontSelectObject, dontSelectValue.key, response.results); // Keep replacing $dontSelect clauses return this.replaceDontSelect(); }) diff --git a/src/Routers/GlobalConfigRouter.js b/src/Routers/GlobalConfigRouter.js index 8cc2646346..59b22a86c6 100644 --- a/src/Routers/GlobalConfigRouter.js +++ b/src/Routers/GlobalConfigRouter.js @@ -5,33 +5,28 @@ import * as middleware from "../middlewares"; export class GlobalConfigRouter extends PromiseRouter { getGlobalConfig(req) { - return req.config.database.adaptiveCollection('_GlobalConfig') - .then(coll => coll.find({ '_id': 1 }, { limit: 1 })) - .then(results => { - if (results.length != 1) { - // If there is no config in the database - return empty config. - return { response: { params: {} } }; - } - let globalConfig = results[0]; - return { response: { params: globalConfig.params } }; - }); + let database = req.config.database.Unsafe(); + return database.find('_GlobalConfig', { '_id': 1 }, { limit: 1 }).then((results) => { + if (results.length != 1) { + // If there is no config in the database - return empty config. + return { response: { params: {} } }; + } + let globalConfig = results[0]; + return { response: { params: globalConfig.params } }; + }); } updateGlobalConfig(req) { - const params = req.body.params; + let params = req.body.params; + // Transform in dot notation to make sure it works const update = Object.keys(params).reduce((acc, key) => { - if(params[key] && params[key].__op && params[key].__op === "Delete") { - if (!acc.$unset) acc.$unset = {}; - acc.$unset[`params.${key}`] = ""; - } else { - if (!acc.$set) acc.$set = {}; - acc.$set[`params.${key}`] = params[key]; - } + acc[`params.${key}`] = params[key]; return acc; }, {}); - return req.config.database.adaptiveCollection('_GlobalConfig') - .then(coll => coll.upsertOne({ _id: 1 }, update)) - .then(() => ({ response: { result: true } })); + let database = req.config.database.Unsafe(); + return database.update('_GlobalConfig', {_id: 1}, update, {upsert: true}).then(() => { + return Promise.resolve({ response: { result: true } }); + }); } mountRoutes() { diff --git a/src/Routers/SchemasRouter.js b/src/Routers/SchemasRouter.js index 23ad4b56f8..574aeb09d4 100644 --- a/src/Routers/SchemasRouter.js +++ b/src/Routers/SchemasRouter.js @@ -103,22 +103,7 @@ function deleteSchema(req) { throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, Schema.invalidClassNameMessage(req.params.className)); } - return req.config.database.collectionExists(req.params.className) - .then(exist => { - if (!exist) { - return Promise.resolve(); - } - return req.config.database.adaptiveCollection(req.params.className) - .then(collection => { - return collection.count() - .then(count => { - if (count > 0) { - throw new Parse.Error(255, `Class ${req.params.className} is not empty, contains ${count} objects, cannot drop schema.`); - } - return collection.drop(); - }) - }) - }) + return req.config.database.deleteSchema(req.params.className) .then(() => { // We've dropped the collection now, so delete the item from _SCHEMA // and clear the _Join collections diff --git a/src/pushStatusHandler.js b/src/pushStatusHandler.js index d9cf23f597..d68127e9e0 100644 --- a/src/pushStatusHandler.js +++ b/src/pushStatusHandler.js @@ -1,6 +1,8 @@ import { md5Hash, newObjectId } from './cryptoUtils'; import { logger } from './logger'; +const PUSH_STATUS_COLLECTION = '_PushStatus'; + export function flatten(array) { return array.reduce((memo, element) => { if (Array.isArray(element)) { @@ -17,9 +19,7 @@ export default function pushStatusHandler(config) { let initialPromise; let pushStatus; let objectId = newObjectId(); - let collection = function() { - return config.database.adaptiveCollection('_PushStatus'); - } + let database = config.database.Unsafe(); let setInitial = function(body = {}, where, options = {source: 'rest'}) { let now = new Date(); @@ -41,24 +41,20 @@ export default function pushStatusHandler(config) { _wperm: [], _rperm: [] } - initialPromise = collection().then((collection) => { - return collection.insertOne(object); - }).then((res) => { + + return database.create(PUSH_STATUS_COLLECTION, object).then(() => { pushStatus = { objectId }; return Promise.resolve(pushStatus); - }) - return initialPromise; + }); } let setRunning = function(installations) { logger.verbose('sending push to %d installations', installations.length); - return initialPromise.then(() => { - return collection(); - }).then((collection) => { - return collection.updateOne({status:"pending", _id: objectId}, {$set: {status: "running"}}); - }); + return database.update(PUSH_STATUS_COLLECTION, + {status:"pending", objectId: objectId}, + {status: "running"}); } let complete = function(results) { @@ -91,11 +87,7 @@ export default function pushStatusHandler(config) { }, update); } logger.verbose('sent push! %d success, %d failures', update.numSent, update.numFailed); - return initialPromise.then(() => { - return collection(); - }).then((collection) => { - return collection.updateOne({status:"running", _id: objectId}, {$set: update}); - }); + return database.update('_PushStatus', {status:"running", objectId }, update); } let fail = function(err) { @@ -104,11 +96,7 @@ export default function pushStatusHandler(config) { status: 'failed' } logger.error('error while sending push', err); - return initialPromise.then(() => { - return collection(); - }).then((collection) => { - return collection.updateOne({_id: objectId}, {$set: update}); - }); + return database.update('_PushStatus', { objectId }, update); } return Object.freeze({ From 5c7c7e430fe3497cb1618052db17d9ca7c467afd Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 12 Apr 2016 07:45:46 -0400 Subject: [PATCH 3/3] Renames Unsafe to WithoutValidation --- src/Controllers/DatabaseController.js | 18 +++++++++--------- src/Controllers/HooksController.js | 2 +- src/Controllers/UserController.js | 8 ++++---- src/Routers/GlobalConfigRouter.js | 4 ++-- src/pushStatusHandler.js | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 76894ff3c7..8643684d4f 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -9,14 +9,14 @@ var Parse = require('parse/node').Parse; var Schema = require('./../Schema'); const deepcopy = require('deepcopy'); -function DatabaseController(adapter, { unsafe } = {}) { +function DatabaseController(adapter, { skipValidation } = {}) { this.adapter = adapter; // We don't want a mutable this.schema, because then you could have // one request that uses different schemas for different parts of // it. Instead, use loadSchema to get a schema. this.schemaPromise = null; - this.unsafe = !!unsafe; + this.skipValidation = !!skipValidation; this.connect(); Object.defineProperty(this, 'transform', { @@ -26,8 +26,8 @@ function DatabaseController(adapter, { unsafe } = {}) { }) } -DatabaseController.prototype.Unsafe = function() { - return new DatabaseController(this.adapter, {collectionPrefix: this.collectionPrefix, unsafe: true}); +DatabaseController.prototype.WithoutValidation = function() { + return new DatabaseController(this.adapter, {collectionPrefix: this.collectionPrefix, skipValidation: true}); } // Connects to the database. Returns a promise that resolves when the @@ -49,7 +49,7 @@ DatabaseController.prototype.dropCollection = function(className) { }; DatabaseController.prototype.validateClassName = function(className) { - if (this.unsafe) { + if (this.skipValidation) { return Promise.resolve(); } if (!Schema.classNameIsValid(className)) { @@ -167,11 +167,11 @@ DatabaseController.prototype.update = function(className, query, update, options .then(() => this.handleRelationUpdates(className, query.objectId, update)) .then(() => this.adapter.adaptiveCollection(className)) .then(collection => { - var mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.unsafe}); + var mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.skipValidation}); if (options.acl) { mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl); } - mongoUpdate = this.transform.transformUpdate(schema, className, update, {validate: !this.unsafe}); + mongoUpdate = this.transform.transformUpdate(schema, className, update, {validate: !this.skipValidation}); if (options.many) { return collection.updateMany(mongoWhere, mongoUpdate); }else if (options.upsert) { @@ -185,7 +185,7 @@ DatabaseController.prototype.update = function(className, query, update, options return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.')); } - if (this.unsafe) { + if (this.skipValidation) { return Promise.resolve(result); } return sanitizeDatabaseResult(originalUpdate, result); @@ -307,7 +307,7 @@ DatabaseController.prototype.destroy = function(className, query, options = {}) }) .then(() => this.adapter.adaptiveCollection(className)) .then(collection => { - let mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.unsafe}); + let mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.skipValidation}); if (options.acl) { mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl); } diff --git a/src/Controllers/HooksController.js b/src/Controllers/HooksController.js index ebb4e5aa74..7e97fd5406 100644 --- a/src/Controllers/HooksController.js +++ b/src/Controllers/HooksController.js @@ -15,7 +15,7 @@ export class HooksController { constructor(applicationId:string, collectionPrefix:string = '') { this._applicationId = applicationId; this._collectionPrefix = collectionPrefix; - this.database = DatabaseAdapter.getDatabaseConnection(this._applicationId, this._collectionPrefix).Unsafe(); + this.database = DatabaseAdapter.getDatabaseConnection(this._applicationId, this._collectionPrefix).WithoutValidation(); } load() { diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index 74bcdebadf..be6e717592 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -45,7 +45,7 @@ export class UserController extends AdaptableController { // TODO: Better error here. return Promise.reject(); } - let database = this.config.database.Unsafe(); + let database = this.config.database.WithoutValidation(); return database.update('_User', { username: username, _email_verify_token: token @@ -58,7 +58,7 @@ export class UserController extends AdaptableController { } checkResetTokenValidity(username, token) { - let database = this.config.database.Unsafe(); + let database = this.config.database.WithoutValidation(); return database.find('_User', { username: username, _perishable_token: token @@ -115,7 +115,7 @@ export class UserController extends AdaptableController { setPasswordResetToken(email) { let token = randomString(25); - let database = this.config.database.Unsafe(); + let database = this.config.database.WithoutValidation(); return database.update('_User', {email: email}, {_perishable_token: token}); } @@ -153,7 +153,7 @@ export class UserController extends AdaptableController { return updateUserPassword(user.objectId, password, this.config); }).then(() => { // clear reset password token - return this.config.database.Unsafe().update('_User', { username }, { + return this.config.database.WithoutValidation().update('_User', { username }, { _perishable_token: {__op: 'Delete'} }); }); diff --git a/src/Routers/GlobalConfigRouter.js b/src/Routers/GlobalConfigRouter.js index 59b22a86c6..c6a82dbbf4 100644 --- a/src/Routers/GlobalConfigRouter.js +++ b/src/Routers/GlobalConfigRouter.js @@ -5,7 +5,7 @@ import * as middleware from "../middlewares"; export class GlobalConfigRouter extends PromiseRouter { getGlobalConfig(req) { - let database = req.config.database.Unsafe(); + let database = req.config.database.WithoutValidation(); return database.find('_GlobalConfig', { '_id': 1 }, { limit: 1 }).then((results) => { if (results.length != 1) { // If there is no config in the database - return empty config. @@ -23,7 +23,7 @@ export class GlobalConfigRouter extends PromiseRouter { acc[`params.${key}`] = params[key]; return acc; }, {}); - let database = req.config.database.Unsafe(); + let database = req.config.database.WithoutValidation(); return database.update('_GlobalConfig', {_id: 1}, update, {upsert: true}).then(() => { return Promise.resolve({ response: { result: true } }); }); diff --git a/src/pushStatusHandler.js b/src/pushStatusHandler.js index d68127e9e0..85beebd0a3 100644 --- a/src/pushStatusHandler.js +++ b/src/pushStatusHandler.js @@ -19,7 +19,7 @@ export default function pushStatusHandler(config) { let initialPromise; let pushStatus; let objectId = newObjectId(); - let database = config.database.Unsafe(); + let database = config.database.WithoutValidation(); let setInitial = function(body = {}, where, options = {source: 'rest'}) { let now = new Date();