From eff906ecd7eaa8da5e56ae059cc1ead126ea13cb Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 26 Oct 2020 14:19:16 +1100 Subject: [PATCH 1/3] Initial Commit --- spec/CloudCode.Validator.spec.js | 78 ++++++++++++++++++++++++++++++++ spec/CloudCode.spec.js | 20 +++++++- src/cloud-code/Parse.Cloud.js | 1 + src/triggers.js | 47 ++++++++++++++----- 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/spec/CloudCode.Validator.spec.js b/spec/CloudCode.Validator.spec.js index ef10b419f2..8e13aafdfd 100644 --- a/spec/CloudCode.Validator.spec.js +++ b/spec/CloudCode.Validator.spec.js @@ -554,6 +554,84 @@ describe('cloud validator', () => { done(); }); + it('basic beforeSave resolveMaster', async function (done) { + Parse.Cloud.beforeSave( + 'BeforeSave', + () => { + throw 'before save should have resolved using masterKey.'; + }, + { + resolveMaster: true, + } + ); + const obj = new Parse.Object('BeforeSave'); + obj.set('foo', 'bar'); + await obj.save(null, { useMasterKey: true }); + expect(obj.get('foo')).toBe('bar'); + done(); + }); + + it('basic beforeFind resolveMaster', async function (done) { + Parse.Cloud.beforeFind( + 'beforeFind', + () => { + throw 'before find should have resolved using masterKey.'; + }, + { + resolveMaster: true, + } + ); + const obj = new Parse.Object('beforeFind'); + obj.set('foo', 'bar'); + await obj.save(); + expect(obj.get('foo')).toBe('bar'); + + const query = new Parse.Query('beforeFind'); + try { + const first = await query.first({ useMasterKey: true }); + expect(first).toBeDefined(); + expect(first.id).toBe(obj.id); + done(); + } catch (e) { + console.log(e); + console.log(e.code); + throw e; + } + }); + + it('basic beforeDelete resolveMaster', async function (done) { + Parse.Cloud.beforeDelete( + 'beforeFind', + () => { + throw 'before find should have resolved using masterKey.'; + }, + { + resolveMaster: true, + } + ); + const obj = new Parse.Object('beforeFind'); + obj.set('foo', 'bar'); + await obj.save(); + expect(obj.get('foo')).toBe('bar'); + await obj.destroy({ useMasterKey: true }); + done(); + }); + + it('basic beforeSaveFile resolveMaster', async done => { + Parse.Cloud.beforeSaveFile( + () => { + throw 'beforeSaveFile should have resolved using master key.'; + }, + { + resolveMaster: true, + } + ); + const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain'); + const result = await file.save({ useMasterKey: true }); + expect(result).toBe(file); + done(); + }); + it('basic beforeSave requireUserKey on User Class', async function (done) { Parse.Cloud.beforeSave(Parse.User, () => {}, { requireUser: true, diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 139c919ace..e5f638de06 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -122,6 +122,24 @@ describe('Cloud Code', () => { ); }); + it('beforeFind can throw string', async function (done) { + Parse.Cloud.beforeFind('beforeFind', () => { + throw 'throw beforeFind'; + }); + const obj = new Parse.Object('beforeFind'); + obj.set('foo', 'bar'); + await obj.save(); + expect(obj.get('foo')).toBe('bar'); + try { + const query = new Parse.Query('beforeFind'); + await query.first(); + } catch (e) { + expect(e.code).toBe(141); + expect(e.message).toBe('throw beforeFind'); + done(); + } + }); + it('beforeSave rejection with custom error code', function (done) { Parse.Cloud.beforeSave('BeforeSaveFailWithErrorCode', function () { throw new Parse.Error(999, 'Nope'); @@ -1896,7 +1914,7 @@ describe('beforeFind hooks', () => { done(); }, err => { - expect(err.code).toBe(1); + expect(err.code).toBe(141); expect(err.message).toEqual('Do not run that query'); done(); } diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 30f00b9ebf..23b743bef8 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -712,6 +712,7 @@ module.exports = ParseCloud; * @property {Boolean} requireUser whether the cloud trigger requires a user. * @property {Boolean} requireMaster whether the cloud trigger requires a master key. * @property {Boolean} validateMasterKey whether the validator should run if masterKey is provided. Defaults to false. + * @property {Boolean} resolveMaster whether the cloud code function should be ignored using a masterKey. * * @property {Array|Object} requireUserKeys If set, keys required on request.user to make the request. * @property {String} requireUserKeys.field If requireUserKeys is an object, name of field to validate on request user diff --git a/src/triggers.js b/src/triggers.js index 88dae097eb..3ff342afd3 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -331,13 +331,11 @@ export function getResponseObject(request, resolve, reject) { return resolve(response); }, error: function (error) { - if (error instanceof Parse.Error) { - reject(error); - } else if (error instanceof Error) { - reject(new Parse.Error(Parse.Error.SCRIPT_FAILED, error.message)); - } else { - reject(new Parse.Error(Parse.Error.SCRIPT_FAILED, error)); - } + const e = resolveError(error, { + code: Parse.Error.SCRIPT_FAILED, + message: 'Script failed. Unknown error.', + }); + reject(e); }, }; } @@ -420,6 +418,9 @@ export function maybeRunAfterFindTrigger(triggerType, auth, className, objects, return maybeRunValidator(request, `${triggerType}.${className}`); }) .then(() => { + if (request.resolveMaster) { + return request.objects; + } const response = trigger(request); if (response && typeof response.then === 'function') { return response.then(results => { @@ -482,6 +483,9 @@ export function maybeRunQueryTrigger( return maybeRunValidator(requestObject, `${triggerType}.${className}`); }) .then(() => { + if (requestObject.resolveMaster) { + return requestObject.query; + } return trigger(requestObject); }) .then( @@ -544,11 +548,11 @@ export function maybeRunQueryTrigger( }; }, err => { - if (typeof err === 'string') { - throw new Parse.Error(1, err); - } else { - throw err; - } + const error = resolveError(err, { + code: Parse.Error.SCRIPT_FAILED, + message: 'Script failed. Unknown error.', + }); + throw error; } ); } @@ -583,6 +587,10 @@ export function maybeRunValidator(request, functionName) { if (!theValidator) { return; } + if (typeof theValidator === 'object' && theValidator.resolveMaster && request.master) { + request.resolveMaster = true; + return; + } return new Promise((resolve, reject) => { return Promise.resolve() .then(() => { @@ -797,6 +805,9 @@ export function maybeRunTrigger( return maybeRunValidator(request, `${triggerType}.${parseObject.className}`); }) .then(() => { + if (request.resolveMaster) { + return Promise.resolve(); + } const promise = trigger(request); if ( triggerType === Types.afterSave || @@ -873,6 +884,9 @@ export async function maybeRunFileTrigger(triggerType, fileObject, config, auth) try { const request = getRequestFileObject(triggerType, auth, fileObject, config); await maybeRunValidator(request, `${triggerType}.${FileClassName}`); + if (request.resolveMaster) { + return fileObject; + } const result = await fileTrigger(request); logTriggerSuccessBeforeHook( triggerType, @@ -903,6 +917,9 @@ export async function maybeRunConnectTrigger(triggerType, request) { } request.user = await userForSessionToken(request.sessionToken); await maybeRunValidator(request, `${triggerType}.${ConnectClassName}`); + if (request.resolveMaster) { + return; + } return trigger(request); } @@ -916,6 +933,9 @@ export async function maybeRunSubscribeTrigger(triggerType, className, request) request.query = parseQuery; request.user = await userForSessionToken(request.sessionToken); await maybeRunValidator(request, `${triggerType}.${className}`); + if (request.resolveMaster) { + return; + } await trigger(request); const query = request.query.toJSON(); if (query.keys) { @@ -937,6 +957,9 @@ export async function maybeRunAfterEventTrigger(triggerType, className, request) } request.user = await userForSessionToken(request.sessionToken); await maybeRunValidator(request, `${triggerType}.${className}`); + if (request.resolveMaster) { + return; + } return trigger(request); } From b0832fbeef85f2613bba3855be9433ff9115e3f9 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 26 Oct 2020 14:47:28 +1100 Subject: [PATCH 2/3] Change to resolveMasterKey --- spec/CloudCode.Validator.spec.js | 63 ++++++++++++++++++++++++++++---- src/cloud-code/Parse.Cloud.js | 2 +- src/triggers.js | 19 +++++----- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/spec/CloudCode.Validator.spec.js b/spec/CloudCode.Validator.spec.js index 8e13aafdfd..a91772779b 100644 --- a/spec/CloudCode.Validator.spec.js +++ b/spec/CloudCode.Validator.spec.js @@ -554,14 +554,14 @@ describe('cloud validator', () => { done(); }); - it('basic beforeSave resolveMaster', async function (done) { + it('basic beforeSave resolveMasterKey', async function (done) { Parse.Cloud.beforeSave( 'BeforeSave', () => { throw 'before save should have resolved using masterKey.'; }, { - resolveMaster: true, + resolveMasterKey: true, } ); const obj = new Parse.Object('BeforeSave'); @@ -571,14 +571,14 @@ describe('cloud validator', () => { done(); }); - it('basic beforeFind resolveMaster', async function (done) { + it('basic beforeFind resolveMasterKey', async function (done) { Parse.Cloud.beforeFind( 'beforeFind', () => { throw 'before find should have resolved using masterKey.'; }, { - resolveMaster: true, + resolveMasterKey: true, } ); const obj = new Parse.Object('beforeFind'); @@ -599,14 +599,14 @@ describe('cloud validator', () => { } }); - it('basic beforeDelete resolveMaster', async function (done) { + it('basic beforeDelete resolveMasterKey', async function (done) { Parse.Cloud.beforeDelete( 'beforeFind', () => { throw 'before find should have resolved using masterKey.'; }, { - resolveMaster: true, + resolveMasterKey: true, } ); const obj = new Parse.Object('beforeFind'); @@ -617,13 +617,13 @@ describe('cloud validator', () => { done(); }); - it('basic beforeSaveFile resolveMaster', async done => { + it('basic beforeSaveFile resolveMasterKey', async done => { Parse.Cloud.beforeSaveFile( () => { throw 'beforeSaveFile should have resolved using master key.'; }, { - resolveMaster: true, + resolveMasterKey: true, } ); const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain'); @@ -632,6 +632,53 @@ describe('cloud validator', () => { done(); }); + it('beforeSave validateMasterKey and resolveMasterKey fail', async function (done) { + Parse.Cloud.beforeSave( + 'BeforeSave', + () => { + throw 'beforeSaveFile should have resolved using master key.'; + }, + { + fields: ['foo'], + validateMasterKey: true, + resolveMasterKey: true, + } + ); + + const obj = new Parse.Object('BeforeSave'); + try { + await obj.save(null, { useMasterKey: true }); + fail('function should have failed.'); + } catch (error) { + expect(error.code).toEqual(Parse.Error.VALIDATION_ERROR); + expect(error.message).toEqual('Validation failed. Please specify data for foo.'); + done(); + } + }); + + it('beforeSave validateMasterKey and resolveMasterKey success', async function (done) { + Parse.Cloud.beforeSave( + 'BeforeSave', + () => { + throw 'beforeSaveFile should have resolved using master key.'; + }, + { + fields: ['foo'], + validateMasterKey: true, + resolveMasterKey: true, + } + ); + + const obj = new Parse.Object('BeforeSave'); + obj.set('foo', 'bar'); + try { + await obj.save(null, { useMasterKey: true }); + done(); + } catch (error) { + fail('error should not have been called.'); + } + }); + it('basic beforeSave requireUserKey on User Class', async function (done) { Parse.Cloud.beforeSave(Parse.User, () => {}, { requireUser: true, diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 23b743bef8..acd2dfaa6b 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -712,7 +712,7 @@ module.exports = ParseCloud; * @property {Boolean} requireUser whether the cloud trigger requires a user. * @property {Boolean} requireMaster whether the cloud trigger requires a master key. * @property {Boolean} validateMasterKey whether the validator should run if masterKey is provided. Defaults to false. - * @property {Boolean} resolveMaster whether the cloud code function should be ignored using a masterKey. + * @property {Boolean} resolveMasterKey whether the cloud code function should be ignored using a masterKey. * * @property {Array|Object} requireUserKeys If set, keys required on request.user to make the request. * @property {String} requireUserKeys.field If requireUserKeys is an object, name of field to validate on request user diff --git a/src/triggers.js b/src/triggers.js index 3ff342afd3..9c308eaffb 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -418,7 +418,7 @@ export function maybeRunAfterFindTrigger(triggerType, auth, className, objects, return maybeRunValidator(request, `${triggerType}.${className}`); }) .then(() => { - if (request.resolveMaster) { + if (request.resolveMasterKey) { return request.objects; } const response = trigger(request); @@ -483,7 +483,7 @@ export function maybeRunQueryTrigger( return maybeRunValidator(requestObject, `${triggerType}.${className}`); }) .then(() => { - if (requestObject.resolveMaster) { + if (requestObject.resolveMasterKey) { return requestObject.query; } return trigger(requestObject); @@ -587,9 +587,8 @@ export function maybeRunValidator(request, functionName) { if (!theValidator) { return; } - if (typeof theValidator === 'object' && theValidator.resolveMaster && request.master) { - request.resolveMaster = true; - return; + if (typeof theValidator === 'object' && theValidator.resolveMasterKey && request.master) { + request.resolveMasterKey = true; } return new Promise((resolve, reject) => { return Promise.resolve() @@ -805,7 +804,7 @@ export function maybeRunTrigger( return maybeRunValidator(request, `${triggerType}.${parseObject.className}`); }) .then(() => { - if (request.resolveMaster) { + if (request.resolveMasterKey) { return Promise.resolve(); } const promise = trigger(request); @@ -884,7 +883,7 @@ export async function maybeRunFileTrigger(triggerType, fileObject, config, auth) try { const request = getRequestFileObject(triggerType, auth, fileObject, config); await maybeRunValidator(request, `${triggerType}.${FileClassName}`); - if (request.resolveMaster) { + if (request.resolveMasterKey) { return fileObject; } const result = await fileTrigger(request); @@ -917,7 +916,7 @@ export async function maybeRunConnectTrigger(triggerType, request) { } request.user = await userForSessionToken(request.sessionToken); await maybeRunValidator(request, `${triggerType}.${ConnectClassName}`); - if (request.resolveMaster) { + if (request.resolveMasterKey) { return; } return trigger(request); @@ -933,7 +932,7 @@ export async function maybeRunSubscribeTrigger(triggerType, className, request) request.query = parseQuery; request.user = await userForSessionToken(request.sessionToken); await maybeRunValidator(request, `${triggerType}.${className}`); - if (request.resolveMaster) { + if (request.resolveMasterKey) { return; } await trigger(request); @@ -957,7 +956,7 @@ export async function maybeRunAfterEventTrigger(triggerType, className, request) } request.user = await userForSessionToken(request.sessionToken); await maybeRunValidator(request, `${triggerType}.${className}`); - if (request.resolveMaster) { + if (request.resolveMasterKey) { return; } return trigger(request); From a61814cc5265f158566b59374f06ccdd03a978d4 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 26 Oct 2020 19:13:49 +1100 Subject: [PATCH 3/3] Change to skipWithMasterKey --- spec/CloudCode.Validator.spec.js | 24 ++++++++++++------------ src/cloud-code/Parse.Cloud.js | 2 +- src/triggers.js | 18 +++++++++--------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/spec/CloudCode.Validator.spec.js b/spec/CloudCode.Validator.spec.js index a91772779b..d15bc2479d 100644 --- a/spec/CloudCode.Validator.spec.js +++ b/spec/CloudCode.Validator.spec.js @@ -554,14 +554,14 @@ describe('cloud validator', () => { done(); }); - it('basic beforeSave resolveMasterKey', async function (done) { + it('basic beforeSave skipWithMasterKey', async function (done) { Parse.Cloud.beforeSave( 'BeforeSave', () => { throw 'before save should have resolved using masterKey.'; }, { - resolveMasterKey: true, + skipWithMasterKey: true, } ); const obj = new Parse.Object('BeforeSave'); @@ -571,14 +571,14 @@ describe('cloud validator', () => { done(); }); - it('basic beforeFind resolveMasterKey', async function (done) { + it('basic beforeFind skipWithMasterKey', async function (done) { Parse.Cloud.beforeFind( 'beforeFind', () => { throw 'before find should have resolved using masterKey.'; }, { - resolveMasterKey: true, + skipWithMasterKey: true, } ); const obj = new Parse.Object('beforeFind'); @@ -599,14 +599,14 @@ describe('cloud validator', () => { } }); - it('basic beforeDelete resolveMasterKey', async function (done) { + it('basic beforeDelete skipWithMasterKey', async function (done) { Parse.Cloud.beforeDelete( 'beforeFind', () => { throw 'before find should have resolved using masterKey.'; }, { - resolveMasterKey: true, + skipWithMasterKey: true, } ); const obj = new Parse.Object('beforeFind'); @@ -617,13 +617,13 @@ describe('cloud validator', () => { done(); }); - it('basic beforeSaveFile resolveMasterKey', async done => { + it('basic beforeSaveFile skipWithMasterKey', async done => { Parse.Cloud.beforeSaveFile( () => { throw 'beforeSaveFile should have resolved using master key.'; }, { - resolveMasterKey: true, + skipWithMasterKey: true, } ); const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain'); @@ -632,7 +632,7 @@ describe('cloud validator', () => { done(); }); - it('beforeSave validateMasterKey and resolveMasterKey fail', async function (done) { + it('beforeSave validateMasterKey and skipWithMasterKey fail', async function (done) { Parse.Cloud.beforeSave( 'BeforeSave', () => { @@ -641,7 +641,7 @@ describe('cloud validator', () => { { fields: ['foo'], validateMasterKey: true, - resolveMasterKey: true, + skipWithMasterKey: true, } ); @@ -656,7 +656,7 @@ describe('cloud validator', () => { } }); - it('beforeSave validateMasterKey and resolveMasterKey success', async function (done) { + it('beforeSave validateMasterKey and skipWithMasterKey success', async function (done) { Parse.Cloud.beforeSave( 'BeforeSave', () => { @@ -665,7 +665,7 @@ describe('cloud validator', () => { { fields: ['foo'], validateMasterKey: true, - resolveMasterKey: true, + skipWithMasterKey: true, } ); diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index acd2dfaa6b..c04c48205e 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -712,7 +712,7 @@ module.exports = ParseCloud; * @property {Boolean} requireUser whether the cloud trigger requires a user. * @property {Boolean} requireMaster whether the cloud trigger requires a master key. * @property {Boolean} validateMasterKey whether the validator should run if masterKey is provided. Defaults to false. - * @property {Boolean} resolveMasterKey whether the cloud code function should be ignored using a masterKey. + * @property {Boolean} skipWithMasterKey whether the cloud code function should be ignored using a masterKey. * * @property {Array|Object} requireUserKeys If set, keys required on request.user to make the request. * @property {String} requireUserKeys.field If requireUserKeys is an object, name of field to validate on request user diff --git a/src/triggers.js b/src/triggers.js index 9c308eaffb..1168fe1767 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -418,7 +418,7 @@ export function maybeRunAfterFindTrigger(triggerType, auth, className, objects, return maybeRunValidator(request, `${triggerType}.${className}`); }) .then(() => { - if (request.resolveMasterKey) { + if (request.skipWithMasterKey) { return request.objects; } const response = trigger(request); @@ -483,7 +483,7 @@ export function maybeRunQueryTrigger( return maybeRunValidator(requestObject, `${triggerType}.${className}`); }) .then(() => { - if (requestObject.resolveMasterKey) { + if (requestObject.skipWithMasterKey) { return requestObject.query; } return trigger(requestObject); @@ -587,8 +587,8 @@ export function maybeRunValidator(request, functionName) { if (!theValidator) { return; } - if (typeof theValidator === 'object' && theValidator.resolveMasterKey && request.master) { - request.resolveMasterKey = true; + if (typeof theValidator === 'object' && theValidator.skipWithMasterKey && request.master) { + request.skipWithMasterKey = true; } return new Promise((resolve, reject) => { return Promise.resolve() @@ -804,7 +804,7 @@ export function maybeRunTrigger( return maybeRunValidator(request, `${triggerType}.${parseObject.className}`); }) .then(() => { - if (request.resolveMasterKey) { + if (request.skipWithMasterKey) { return Promise.resolve(); } const promise = trigger(request); @@ -883,7 +883,7 @@ export async function maybeRunFileTrigger(triggerType, fileObject, config, auth) try { const request = getRequestFileObject(triggerType, auth, fileObject, config); await maybeRunValidator(request, `${triggerType}.${FileClassName}`); - if (request.resolveMasterKey) { + if (request.skipWithMasterKey) { return fileObject; } const result = await fileTrigger(request); @@ -916,7 +916,7 @@ export async function maybeRunConnectTrigger(triggerType, request) { } request.user = await userForSessionToken(request.sessionToken); await maybeRunValidator(request, `${triggerType}.${ConnectClassName}`); - if (request.resolveMasterKey) { + if (request.skipWithMasterKey) { return; } return trigger(request); @@ -932,7 +932,7 @@ export async function maybeRunSubscribeTrigger(triggerType, className, request) request.query = parseQuery; request.user = await userForSessionToken(request.sessionToken); await maybeRunValidator(request, `${triggerType}.${className}`); - if (request.resolveMasterKey) { + if (request.skipWithMasterKey) { return; } await trigger(request); @@ -956,7 +956,7 @@ export async function maybeRunAfterEventTrigger(triggerType, className, request) } request.user = await userForSessionToken(request.sessionToken); await maybeRunValidator(request, `${triggerType}.${className}`); - if (request.resolveMasterKey) { + if (request.skipWithMasterKey) { return; } return trigger(request);