diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 43c6203d25..b4fb987d49 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -2968,4 +2968,58 @@ describe('afterLogin hook', () => { obj.set("obj2", obj2); await obj.save(null, { context: { a: 'a' } }); }); + + it('should have access to context as saveAll argument', async () => { + Parse.Cloud.beforeSave('TestObject', (req) => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterSave('TestObject', (req) => { + expect(req.context.a).toEqual('a'); + }); + const obj1 = new TestObject(); + const obj2 = new TestObject(); + await Parse.Object.saveAll([obj1, obj2], { context: { a: 'a' }}); + }); + + it('should have access to context as destroyAll argument', async () => { + Parse.Cloud.beforeDelete('TestObject', (req) => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterDelete('TestObject', (req) => { + expect(req.context.a).toEqual('a'); + }); + const obj1 = new TestObject(); + const obj2 = new TestObject(); + await Parse.Object.saveAll([obj1, obj2]); + await Parse.Object.destroyAll([obj1, obj2], { context: { a: 'a' } }); + }); + + it('should have access to context as destroy a object', async () => { + Parse.Cloud.beforeDelete('TestObject', (req) => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterDelete('TestObject', (req) => { + expect(req.context.a).toEqual('a'); + }); + const obj = new TestObject(); + await obj.save(); + await obj.destroy({ context: { a: 'a' } }); + }); + + it('should have access to context in beforeFind hook', async () => { + Parse.Cloud.beforeFind('TestObject', (req) => { + expect(req.context.a).toEqual('a'); + }); + const query = new Parse.Query('TestObject'); + return query.find({ context: { a: 'a' } }); + }); + + it('should have access to context when cloud function is called.', async () => { + Parse.Cloud.define('contextTest', async (req) => { + expect(req.context.a).toEqual('a'); + return {}; + }); + + await Parse.Cloud.run('contextTest', {}, { context: { a: 'a' } }); + }); }); diff --git a/src/GraphQL/helpers/objectsMutations.js b/src/GraphQL/helpers/objectsMutations.js index aa74e1c8f8..781984bab1 100644 --- a/src/GraphQL/helpers/objectsMutations.js +++ b/src/GraphQL/helpers/objectsMutations.js @@ -5,7 +5,7 @@ const createObject = async (className, fields, config, auth, info) => { fields = {}; } - return (await rest.create(config, auth, className, fields, info.clientSDK)) + return (await rest.create(config, auth, className, fields, info.clientSDK, info.context)) .response; }; @@ -27,12 +27,13 @@ const updateObject = async ( className, { objectId }, fields, - info.clientSDK + info.clientSDK, + info.context )).response; }; const deleteObject = async (className, objectId, config, auth, info) => { - await rest.del(config, auth, className, objectId, info.clientSDK); + await rest.del(config, auth, className, objectId, info.context); return true; }; diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js index 066b3b2280..b9812739df 100644 --- a/src/GraphQL/helpers/objectsQueries.js +++ b/src/GraphQL/helpers/objectsQueries.js @@ -74,7 +74,8 @@ const getObject = async ( className, objectId, options, - info.clientSDK + info.clientSDK, + info.context ); if (!response.results || response.results.length == 0) { @@ -142,7 +143,8 @@ const findObjects = async ( className, where, preCountOptions, - info.clientSDK + info.clientSDK, + info.context ) ).count; if ((skip || 0) + limit < preCount) { @@ -222,7 +224,8 @@ const findObjects = async ( className, where, options, - info.clientSDK + info.clientSDK, + info.context ); results = findResult.results; count = findResult.count; diff --git a/src/GraphQL/loaders/usersQueries.js b/src/GraphQL/loaders/usersQueries.js index 7c00d19315..6a1d3ea945 100644 --- a/src/GraphQL/loaders/usersQueries.js +++ b/src/GraphQL/loaders/usersQueries.js @@ -55,7 +55,8 @@ const getUserFromSessionToken = async ( '_Session', { sessionToken }, options, - info.clientVersion + info.clientVersion, + info.context, ); if ( !response.results || diff --git a/src/ParseServerRESTController.js b/src/ParseServerRESTController.js index 1f70503010..1c6087c237 100644 --- a/src/ParseServerRESTController.js +++ b/src/ParseServerRESTController.js @@ -107,6 +107,7 @@ function ParseServerRESTController(applicationId, router) { info: { applicationId: applicationId, sessionToken: options.sessionToken, + context: options.context || {}, // Add context }, query, }; diff --git a/src/RestWrite.js b/src/RestWrite.js index 077c779db6..b26fc90b77 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -32,6 +32,7 @@ function RestWrite( data, originalData, clientSDK, + context, action ) { if (auth.isReadOnly) { @@ -46,18 +47,12 @@ function RestWrite( this.clientSDK = clientSDK; this.storage = {}; this.runOptions = {}; - this.context = {}; + this.context = context || {}; if (action) { this.runOptions.action = action; } - // Parse context - if (data._context && data._context instanceof Object) { - this.context = data._context; - delete data._context; - } - if (!query) { if (this.config.allowCustomObjectId) { if ( diff --git a/src/Routers/AggregateRouter.js b/src/Routers/AggregateRouter.js index 56a9965191..f68400abd8 100644 --- a/src/Routers/AggregateRouter.js +++ b/src/Routers/AggregateRouter.js @@ -69,7 +69,8 @@ export class AggregateRouter extends ClassesRouter { this.className(req), body.where, options, - req.info.clientSDK + req.info.clientSDK, + req.info.context, ) .then(response => { for (const result of response.results) { diff --git a/src/Routers/AudiencesRouter.js b/src/Routers/AudiencesRouter.js index 312ea9b5ac..ecf4a22f1f 100644 --- a/src/Routers/AudiencesRouter.js +++ b/src/Routers/AudiencesRouter.js @@ -21,7 +21,8 @@ export class AudiencesRouter extends ClassesRouter { '_Audience', body.where, options, - req.info.clientSDK + req.info.clientSDK, + req.info.context, ) .then(response => { response.results.forEach(item => { diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index d85101af6e..9a2469c272 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -40,7 +40,8 @@ export class ClassesRouter extends PromiseRouter { this.className(req), body.where, options, - req.info.clientSDK + req.info.clientSDK, + req.info.context, ) .then(response => { return { response: response }; @@ -120,7 +121,8 @@ export class ClassesRouter extends PromiseRouter { req.auth, this.className(req), req.body, - req.info.clientSDK + req.info.clientSDK, + req.info.context ); } @@ -132,7 +134,8 @@ export class ClassesRouter extends PromiseRouter { this.className(req), where, req.body, - req.info.clientSDK + req.info.clientSDK, + req.info.context ); } @@ -143,7 +146,7 @@ export class ClassesRouter extends PromiseRouter { req.auth, this.className(req), req.params.objectId, - req.info.clientSDK + req.info.context ) .then(() => { return { response: {} }; diff --git a/src/Routers/CloudCodeRouter.js b/src/Routers/CloudCodeRouter.js index dea27faec7..9f4a82a218 100644 --- a/src/Routers/CloudCodeRouter.js +++ b/src/Routers/CloudCodeRouter.js @@ -88,7 +88,8 @@ export class CloudCodeRouter extends PromiseRouter { req.auth, '_JobSchedule', formatJobSchedule(job_schedule), - req.client + req.client, + req.info.context ); } @@ -102,7 +103,9 @@ export class CloudCodeRouter extends PromiseRouter { req.auth, '_JobSchedule', { objectId }, - formatJobSchedule(job_schedule) + formatJobSchedule(job_schedule), + undefined, + req.info.context ) .then(response => { return { @@ -114,7 +117,7 @@ export class CloudCodeRouter extends PromiseRouter { static deleteJob(req) { const { objectId } = req.params; return rest - .del(req.config, req.auth, '_JobSchedule', objectId) + .del(req.config, req.auth, '_JobSchedule', objectId, req.info.context) .then(response => { return { response, diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index 5e6f422718..9b5bcd2d3c 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -148,6 +148,7 @@ export class FunctionsRouter extends PromiseRouter { headers: req.config.headers, ip: req.config.ip, functionName, + context: req.info.context, }; if (theValidator && typeof theValidator === 'function') { diff --git a/src/Routers/IAPValidationRouter.js b/src/Routers/IAPValidationRouter.js index fc21d0df2c..2e67c526e1 100644 --- a/src/Routers/IAPValidationRouter.js +++ b/src/Routers/IAPValidationRouter.js @@ -51,7 +51,8 @@ function getFileForProductIdentifier(productIdentifier, req) { '_Product', { productIdentifier: productIdentifier }, undefined, - req.info.clientSDK + req.info.clientSDK, + req.info.context ) .then(function(result) { const products = result.results; diff --git a/src/Routers/InstallationsRouter.js b/src/Routers/InstallationsRouter.js index a35afd9bb1..28876b7f31 100644 --- a/src/Routers/InstallationsRouter.js +++ b/src/Routers/InstallationsRouter.js @@ -21,7 +21,8 @@ export class InstallationsRouter extends ClassesRouter { '_Installation', body.where, options, - req.info.clientSDK + req.info.clientSDK, + req.info.context ) .then(response => { return { response: response }; diff --git a/src/Routers/SessionsRouter.js b/src/Routers/SessionsRouter.js index 4b8488bf43..dac7cff803 100644 --- a/src/Routers/SessionsRouter.js +++ b/src/Routers/SessionsRouter.js @@ -23,7 +23,8 @@ export class SessionsRouter extends ClassesRouter { '_Session', { sessionToken: req.info.sessionToken }, undefined, - req.info.clientSDK + req.info.clientSDK, + req.info.context ) .then(response => { if (!response.results || response.results.length == 0) { diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index c2587bed6e..05d0081792 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -178,7 +178,8 @@ export class UsersRouter extends ClassesRouter { '_Session', { sessionToken }, { include: 'user' }, - req.info.clientSDK + req.info.clientSDK, + req.info.context ) .then(response => { if ( @@ -302,7 +303,8 @@ export class UsersRouter extends ClassesRouter { '_Session', { sessionToken: req.info.sessionToken }, undefined, - req.info.clientSDK + req.info.clientSDK, + req.info.context ) .then(records => { if (records.results && records.results.length) { @@ -311,7 +313,8 @@ export class UsersRouter extends ClassesRouter { req.config, Auth.master(req.config), '_Session', - records.results[0].objectId + records.results[0].objectId, + req.info.context ) .then(() => { this._runAfterLogoutTrigger(req, records.results[0]); diff --git a/src/batch.js b/src/batch.js index 612887af4b..d12582238e 100644 --- a/src/batch.js +++ b/src/batch.js @@ -100,9 +100,6 @@ function handleBatch(router, req) { info: req.info, }; - // Add context to request body - if (req.body._context) { request.body._context = req.body._context; } - return router .tryRouteRequest(restRequest.method, routablePath, request) .then( diff --git a/src/middlewares.js b/src/middlewares.js index 681053e8e9..0f48459c24 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -33,6 +33,7 @@ export function handleParseHeaders(req, res, next) { dotNetKey: req.get('X-Parse-Windows-Key'), restAPIKey: req.get('X-Parse-REST-API-Key'), clientVersion: req.get('X-Parse-Client-Version'), + context: {}, }; var basicAuth = httpAuth(req); @@ -103,6 +104,10 @@ export function handleParseHeaders(req, res, next) { info.masterKey = req.body._MasterKey; delete req.body._MasterKey; } + if (req.body._context && req.body._context instanceof Object) { + info.context = req.body._context; + delete req.body._context; + } if (req.body._ContentType) { req.headers['content-type'] = req.body._ContentType; delete req.body._ContentType; diff --git a/src/rest.js b/src/rest.js index 201cd89bbc..812a0da7f7 100644 --- a/src/rest.js +++ b/src/rest.js @@ -31,7 +31,7 @@ function checkLiveQuery(className, config) { } // Returns a promise for an object with optional keys 'results' and 'count'. -function find(config, auth, className, restWhere, restOptions, clientSDK) { +function find(config, auth, className, restWhere, restOptions, clientSDK, context) { enforceRoleSecurity('find', className, auth); return triggers .maybeRunQueryTrigger( @@ -40,7 +40,8 @@ function find(config, auth, className, restWhere, restOptions, clientSDK) { restWhere, restOptions, config, - auth + auth, + context ) .then(result => { restWhere = result.restWhere || restWhere; @@ -58,7 +59,7 @@ function find(config, auth, className, restWhere, restOptions, clientSDK) { } // get is just like find but only queries an objectId. -const get = (config, auth, className, objectId, restOptions, clientSDK) => { +const get = (config, auth, className, objectId, restOptions, clientSDK, context) => { var restWhere = { objectId }; enforceRoleSecurity('get', className, auth); return triggers @@ -69,6 +70,7 @@ const get = (config, auth, className, objectId, restOptions, clientSDK) => { restOptions, config, auth, + context, true ) .then(result => { @@ -87,7 +89,7 @@ const get = (config, auth, className, objectId, restOptions, clientSDK) => { }; // Returns a promise that doesn't resolve to any useful value. -function del(config, auth, className, objectId) { +function del(config, auth, className, objectId, context) { if (typeof objectId !== 'string') { throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad objectId'); } @@ -134,7 +136,8 @@ function del(config, auth, className, objectId) { auth, inflatedObject, null, - config + config, + context ); } throw new Parse.Error( @@ -187,7 +190,8 @@ function del(config, auth, className, objectId) { auth, inflatedObject, null, - config + config, + context ); }) .catch(error => { @@ -196,7 +200,7 @@ function del(config, auth, className, objectId) { } // Returns a promise for a {response, status, location} object. -function create(config, auth, className, restObject, clientSDK) { +function create(config, auth, className, restObject, clientSDK, context) { enforceRoleSecurity('create', className, auth); var write = new RestWrite( config, @@ -205,7 +209,8 @@ function create(config, auth, className, restObject, clientSDK) { null, restObject, null, - clientSDK + clientSDK, + context ); return write.execute(); } @@ -213,7 +218,7 @@ function create(config, auth, className, restObject, clientSDK) { // Returns a promise that contains the fields of the update that the // REST API is supposed to return. // Usually, this is just updatedAt. -function update(config, auth, className, restWhere, restObject, clientSDK) { +function update(config, auth, className, restWhere, restObject, clientSDK, context) { enforceRoleSecurity('update', className, auth); return Promise.resolve() @@ -252,6 +257,7 @@ function update(config, auth, className, restWhere, restObject, clientSDK) { restObject, originalRestObject, clientSDK, + context, 'update' ).execute(); }) diff --git a/src/triggers.js b/src/triggers.js index 9ffa0fd7e6..998e6a3cf6 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -233,7 +233,10 @@ export function getRequestObject( request.original = originalParseObject; } - if (triggerType === Types.beforeSave || triggerType === Types.afterSave) { + if (triggerType === Types.beforeSave || + triggerType === Types.afterSave || + triggerType === Types.beforeDelete || + triggerType === Types.afterDelete) { // Set a copy of the context on the request object. request.context = Object.assign({}, context); } @@ -259,6 +262,7 @@ export function getRequestQueryObject( query, count, config, + context, isGet ) { isGet = !!isGet; @@ -272,6 +276,7 @@ export function getRequestQueryObject( isGet, headers: config.headers, ip: config.ip, + context: context || {}, }; if (!auth) { @@ -460,6 +465,7 @@ export function maybeRunQueryTrigger( restOptions, config, auth, + context, isGet ) { const trigger = getTrigger(className, triggerType, config.applicationId); @@ -485,6 +491,7 @@ export function maybeRunQueryTrigger( parseQuery, count, config, + context, isGet ); return Promise.resolve() @@ -605,7 +612,9 @@ export function maybeRunTrigger( ); if ( triggerType === Types.beforeSave || - triggerType === Types.afterSave + triggerType === Types.afterSave || + triggerType === Types.beforeDelete || + triggerType === Types.afterDelete ) { Object.assign(context, request.context); }