diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index d83dd8b2de..6c56dc9953 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1470,4 +1470,36 @@ describe('afterFind hooks', () => { }); }); }); + + it('should set count to true on beforeFind hooks if query is count', (done) => { + const hook = { + method: function(req) { + expect(req.count).toBe(true); + return Promise.resolve(); + } + }; + spyOn(hook, 'method').and.callThrough(); + Parse.Cloud.beforeFind('Stuff', hook.method); + new Parse.Query('Stuff').count().then((count) => { + expect(count).toBe(0); + expect(hook.method).toHaveBeenCalled(); + done(); + }); + }); + + it('should set count to false on beforeFind hooks if query is not count', (done) => { + const hook = { + method: function(req) { + expect(req.count).toBe(false); + return Promise.resolve(); + } + }; + spyOn(hook, 'method').and.callThrough(); + Parse.Cloud.beforeFind('Stuff', hook.method); + new Parse.Query('Stuff').find().then((res) => { + expect(res.length).toBe(0); + expect(hook.method).toHaveBeenCalled(); + done(); + }); + }); }); diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index c592ab0ca2..3329bd4bfc 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -178,6 +178,38 @@ describe('SchemaController', () => { }); }); + it('class-level permissions test count', (done) => { + var obj; + return config.database.loadSchema() + // Create a valid class + .then(schema => schema.validateObject('Stuff', {foo: 'bar'})) + .then(schema => { + var count = {}; + return schema.setPermissions('Stuff', { + 'create': {'*': true}, + 'find': {'*': true}, + 'count': count + }) + }).then(() => { + obj = new Parse.Object('Stuff'); + obj.set('foo', 'bar'); + return obj.save(); + }).then((o) => { + obj = o; + var query = new Parse.Query('Stuff'); + return query.find(); + }).then((results) => { + expect(results.length).toBe(1); + var query = new Parse.Query('Stuff'); + return query.count(); + }).then(() => { + fail('Class permissions should have rejected this query.'); + }, (err) => { + expect(err.message).toEqual('Permission denied for action count on class Stuff.'); + done(); + }); + }); + it('can add classes without needing an object', done => { config.database.loadSchema() .then(schema => schema.addClassIfNotExists('NewClass', { diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 2287543b10..db633981a5 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -744,6 +744,9 @@ DatabaseController.prototype.find = function(className, query, { const isMaster = acl === undefined; const aclGroup = acl || []; op = op || (typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find'); + // Count operation if counting + op = (count === true ? 'count' : op); + let classExists = true; return this.loadSchema() .then(schemaController => { diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index c953d5ba9e..a16a59571a 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -137,7 +137,7 @@ function verifyPermissionKey(key) { } } -const CLPValidKeys = Object.freeze(['find', 'get', 'create', 'update', 'delete', 'addField', 'readUserFields', 'writeUserFields']); +const CLPValidKeys = Object.freeze(['find', 'count', 'get', 'create', 'update', 'delete', 'addField', 'readUserFields', 'writeUserFields']); function validateCLP(perms, fields) { if (!perms) { return; @@ -820,7 +820,7 @@ export default class SchemaController { // No matching CLP, let's check the Pointer permissions // And handle those later - const permissionField = ['get', 'find'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields'; + const permissionField = ['get', 'find', 'count'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields'; // Reject create when write lockdown if (permissionField == 'writeUserFields' && operation == 'create') { diff --git a/src/triggers.js b/src/triggers.js index 9f8a797b23..5495f6c224 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -155,11 +155,12 @@ export function getRequestObject(triggerType, auth, parseObject, originalParseOb return request; } -export function getRequestQueryObject(triggerType, auth, query, config) { +export function getRequestQueryObject(triggerType, auth, query, count, config) { var request = { triggerName: triggerType, - query: query, + query, master: false, + count, log: config.loggerController }; @@ -298,6 +299,7 @@ export function maybeRunQueryTrigger(triggerType, className, restWhere, restOpti if (restWhere) { parseQuery._where = restWhere; } + let count = false; if (restOptions) { if (restOptions.include && restOptions.include.length > 0) { parseQuery._include = restOptions.include.split(','); @@ -308,8 +310,9 @@ export function maybeRunQueryTrigger(triggerType, className, restWhere, restOpti if (restOptions.limit) { parseQuery._limit = restOptions.limit; } + count = !!restOptions.count; } - const requestObject = getRequestQueryObject(triggerType, auth, parseQuery, config); + const requestObject = getRequestQueryObject(triggerType, auth, parseQuery, count, config); return Promise.resolve().then(() => { return trigger(requestObject); }).then((result) => {