From c5ecaebf9a8211242e37bb0d7d5bfefb369ccd69 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Sat, 13 May 2017 22:42:08 -0400 Subject: [PATCH 1/4] Adds count class level permission --- spec/Schema.spec.js | 32 +++++++++++++++++++++++++++ src/Controllers/DatabaseController.js | 1 + src/Controllers/SchemaController.js | 4 ++-- 3 files changed, 35 insertions(+), 2 deletions(-) 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..730ed80435 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -744,6 +744,7 @@ 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'); + op = (typeof count != 'undefined' ? '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') { From 4ff8782ba818eaa337cd6e1ca085f0d819ef756e Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Sat, 13 May 2017 22:50:02 -0400 Subject: [PATCH 2/4] fixup! Adds count class level permission --- src/Controllers/DatabaseController.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 730ed80435..db633981a5 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -744,7 +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'); - op = (typeof count != 'undefined' ? 'count' : op); + // Count operation if counting + op = (count === true ? 'count' : op); + let classExists = true; return this.loadSchema() .then(schemaController => { From 4d855d545ce0d8ebe0cbb058f75a0ea011678576 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Sat, 13 May 2017 23:09:10 -0400 Subject: [PATCH 3/4] Adds missing count property on beforeFind request object --- spec/CloudCode.spec.js | 32 ++++++++++++++++++++++++++++++++ src/triggers.js | 7 +++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index d83dd8b2de..2cb1e44ee6 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1470,4 +1470,36 @@ describe('afterFind hooks', () => { }); }); }); + + it('should report count if passed', (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 report count if passed', (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/src/triggers.js b/src/triggers.js index 9f8a797b23..7345768e77 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, master: false, + count: 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) => { From 930bc03926eb3b2d85f4b999345b5085c7a9c4c6 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Sun, 14 May 2017 19:15:13 -0400 Subject: [PATCH 4/4] nits --- spec/CloudCode.spec.js | 4 ++-- src/triggers.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 2cb1e44ee6..6c56dc9953 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1471,7 +1471,7 @@ describe('afterFind hooks', () => { }); }); - it('should report count if passed', (done) => { + it('should set count to true on beforeFind hooks if query is count', (done) => { const hook = { method: function(req) { expect(req.count).toBe(true); @@ -1487,7 +1487,7 @@ describe('afterFind hooks', () => { }); }); - it('should report count if passed', (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); diff --git a/src/triggers.js b/src/triggers.js index 7345768e77..5495f6c224 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -158,9 +158,9 @@ export function getRequestObject(triggerType, auth, parseObject, originalParseOb export function getRequestQueryObject(triggerType, auth, query, count, config) { var request = { triggerName: triggerType, - query: query, + query, master: false, - count: count, + count, log: config.loggerController };