From fdfb67b8a1a422787039ffa44adf02f3bccfce22 Mon Sep 17 00:00:00 2001 From: Kartal Kaan Bozdogan Date: Fri, 6 Sep 2024 11:28:37 +0200 Subject: [PATCH 1/7] Cloud code and triggers: Pass the set of roles the user has --- spec/CloudCode.spec.js | 78 ++++++++++++++++++++++++++++++++++ src/Routers/FunctionsRouter.js | 6 +++ src/triggers.js | 3 ++ 3 files changed, 87 insertions(+) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 99ec4910d1..3062165f20 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1163,6 +1163,53 @@ describe('Cloud Code', () => { ); }); + it('test save triggers get user roles', async () => { + let beforeSaveFlag = false, + afterSaveFlag = false; + Parse.Cloud.beforeSave('SaveTriggerUserRoles', async function (req) { + expect(await req.getRoles()).toEqual(['TestRole']); + beforeSaveFlag = true; + }); + + Parse.Cloud.afterSave('SaveTriggerUserRoles', async function (req) { + expect(await req.getRoles()).toEqual(['TestRole']); + afterSaveFlag = true; + }); + + const user = new Parse.User(); + user.set('password', 'asdf'); + user.set('email', 'asdf@example.com'); + user.set('username', 'zxcv'); + await user.signUp(); + const role = new Parse.Role('TestRole', new Parse.ACL({ '*': { read: true, write: true } })); + role.getUsers().add(user); + await role.save(); + + const obj = new Parse.Object('SaveTriggerUserRoles'); + await obj.save(); + expect(beforeSaveFlag).toBeTrue(); + expect(afterSaveFlag).toBeTrue(); + }); + + it('should not have user roles for anonymous calls', async () => { + let beforeSaveFlag = false, + afterSaveFlag = false; + Parse.Cloud.beforeSave('SaveTriggerUserRoles', async function (req) { + expect(req.getRoles).toBeUndefined(); + beforeSaveFlag = true; + }); + + Parse.Cloud.afterSave('SaveTriggerUserRoles', async function (req) { + expect(req.getRoles).toBeUndefined(); + afterSaveFlag = true; + }); + + const obj = new Parse.Object('SaveTriggerUserRoles'); + await obj.save(); + expect(beforeSaveFlag).toBeTrue(); + expect(afterSaveFlag).toBeTrue(); + }); + it('beforeSave change propagates through the save response', done => { Parse.Cloud.beforeSave('ChangingObject', function (request) { request.object.set('foo', 'baz'); @@ -2014,6 +2061,37 @@ describe('cloud functions', () => { Parse.Cloud.run('myFunction', {}).then(() => done()); }); + + it('should have user roles', async () => { + let flag = false; + Parse.Cloud.define('myFunction', async function (req) { + expect(await req.getRoles()).toEqual(['TestRole']); + flag = true; + }); + + const user = new Parse.User(); + user.set('password', 'asdf'); + user.set('email', 'asdf@example.com'); + user.set('username', 'zxcv'); + await user.signUp(); + const role = new Parse.Role('TestRole', new Parse.ACL({ '*': { read: true, write: true } })); + role.getUsers().add(user); + await role.save(); + + await Parse.Cloud.run('myFunction', { sessionToken: user.getSessionToken() }); + expect(flag).toBeTrue(); + }); + + it('should not have user roles for anonymous calls', async () => { + let flag = false; + Parse.Cloud.define('myFunction', async function (req) { + expect(req.getRoles).toBeUndefined(); + flag = true; + }); + + await Parse.Cloud.run('myFunction'); + expect(flag).toBeTrue(); + }); }); describe('beforeSave hooks', () => { diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index 77c0dff7cc..d02d983fa3 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -131,6 +131,12 @@ export class FunctionsRouter extends PromiseRouter { params: params, master: req.auth && req.auth.isMaster, user: req.auth && req.auth.user, + getRoles: + req.auth && req.auth.user + ? async () => { + return (await req.auth.getUserRoles()).map(r => r.substr('role:'.length)); + } + : undefined, installationId: req.info.installationId, log: req.config.loggerController, headers: req.config.headers, diff --git a/src/triggers.js b/src/triggers.js index e34c5fd3a8..888f6b4361 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -292,6 +292,9 @@ export function getRequestObject( } if (auth.user) { request['user'] = auth.user; + request['getRoles'] = async () => { + return (await auth.getUserRoles()).map(r => r.substr('role:'.length)); + }; } if (auth.installationId) { request['installationId'] = auth.installationId; From 105838f5413800f15b8bbd87717abb0b2d2263b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 10 Sep 2024 10:21:26 +0200 Subject: [PATCH 2/7] Update spec/CloudCode.spec.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel <5673677+mtrezza@users.noreply.github.com> Signed-off-by: Kartal Kaan Bozdoğan --- spec/CloudCode.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 3062165f20..d350d05c9f 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1164,8 +1164,8 @@ describe('Cloud Code', () => { }); it('test save triggers get user roles', async () => { - let beforeSaveFlag = false, - afterSaveFlag = false; + let beforeSaveFlag = false; + let afterSaveFlag = false; Parse.Cloud.beforeSave('SaveTriggerUserRoles', async function (req) { expect(await req.getRoles()).toEqual(['TestRole']); beforeSaveFlag = true; From 147060d077dfd2c021398f88951c75bd165f4961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 10 Sep 2024 10:21:43 +0200 Subject: [PATCH 3/7] Update spec/CloudCode.spec.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel <5673677+mtrezza@users.noreply.github.com> Signed-off-by: Kartal Kaan Bozdoğan --- spec/CloudCode.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index d350d05c9f..67c4211f7b 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1192,8 +1192,8 @@ describe('Cloud Code', () => { }); it('should not have user roles for anonymous calls', async () => { - let beforeSaveFlag = false, - afterSaveFlag = false; + let beforeSaveFlag = false; + let afterSaveFlag = false; Parse.Cloud.beforeSave('SaveTriggerUserRoles', async function (req) { expect(req.getRoles).toBeUndefined(); beforeSaveFlag = true; From 2317a95c434eff07397bcbeda5d478b9c0fdc354 Mon Sep 17 00:00:00 2001 From: Kartal Kaan Bozdogan Date: Tue, 10 Sep 2024 10:27:26 +0200 Subject: [PATCH 4/7] Use arrow functions --- spec/CloudCode.spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 67c4211f7b..291421232c 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1166,12 +1166,12 @@ describe('Cloud Code', () => { it('test save triggers get user roles', async () => { let beforeSaveFlag = false; let afterSaveFlag = false; - Parse.Cloud.beforeSave('SaveTriggerUserRoles', async function (req) { + Parse.Cloud.beforeSave('SaveTriggerUserRoles', async req => { expect(await req.getRoles()).toEqual(['TestRole']); beforeSaveFlag = true; }); - Parse.Cloud.afterSave('SaveTriggerUserRoles', async function (req) { + Parse.Cloud.afterSave('SaveTriggerUserRoles', async req => { expect(await req.getRoles()).toEqual(['TestRole']); afterSaveFlag = true; }); @@ -1194,12 +1194,12 @@ describe('Cloud Code', () => { it('should not have user roles for anonymous calls', async () => { let beforeSaveFlag = false; let afterSaveFlag = false; - Parse.Cloud.beforeSave('SaveTriggerUserRoles', async function (req) { + Parse.Cloud.beforeSave('SaveTriggerUserRoles', async req => { expect(req.getRoles).toBeUndefined(); beforeSaveFlag = true; }); - Parse.Cloud.afterSave('SaveTriggerUserRoles', async function (req) { + Parse.Cloud.afterSave('SaveTriggerUserRoles', async req => { expect(req.getRoles).toBeUndefined(); afterSaveFlag = true; }); @@ -2064,7 +2064,7 @@ describe('cloud functions', () => { it('should have user roles', async () => { let flag = false; - Parse.Cloud.define('myFunction', async function (req) { + Parse.Cloud.define('myFunction', async req => { expect(await req.getRoles()).toEqual(['TestRole']); flag = true; }); @@ -2084,7 +2084,7 @@ describe('cloud functions', () => { it('should not have user roles for anonymous calls', async () => { let flag = false; - Parse.Cloud.define('myFunction', async function (req) { + Parse.Cloud.define('myFunction', async req => { expect(req.getRoles).toBeUndefined(); flag = true; }); From cbf3eb14fe6ff7631f29110ef75857695c1b5af2 Mon Sep 17 00:00:00 2001 From: Kartal Kaan Bozdogan Date: Wed, 25 Sep 2024 15:39:33 +0200 Subject: [PATCH 5/7] Renamed getRoles() -> getUserRoles() --- spec/CloudCode.spec.js | 12 ++++++------ src/Routers/FunctionsRouter.js | 6 +++--- src/triggers.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 291421232c..f06391d818 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1167,12 +1167,12 @@ describe('Cloud Code', () => { let beforeSaveFlag = false; let afterSaveFlag = false; Parse.Cloud.beforeSave('SaveTriggerUserRoles', async req => { - expect(await req.getRoles()).toEqual(['TestRole']); + expect(await req.getUserRoles()).toEqual(['TestRole']); beforeSaveFlag = true; }); Parse.Cloud.afterSave('SaveTriggerUserRoles', async req => { - expect(await req.getRoles()).toEqual(['TestRole']); + expect(await req.getUserRoles()).toEqual(['TestRole']); afterSaveFlag = true; }); @@ -1195,12 +1195,12 @@ describe('Cloud Code', () => { let beforeSaveFlag = false; let afterSaveFlag = false; Parse.Cloud.beforeSave('SaveTriggerUserRoles', async req => { - expect(req.getRoles).toBeUndefined(); + expect(req.getUserRoles).toBeUndefined(); beforeSaveFlag = true; }); Parse.Cloud.afterSave('SaveTriggerUserRoles', async req => { - expect(req.getRoles).toBeUndefined(); + expect(req.getUserRoles).toBeUndefined(); afterSaveFlag = true; }); @@ -2065,7 +2065,7 @@ describe('cloud functions', () => { it('should have user roles', async () => { let flag = false; Parse.Cloud.define('myFunction', async req => { - expect(await req.getRoles()).toEqual(['TestRole']); + expect(await req.getUserRoles()).toEqual(['TestRole']); flag = true; }); @@ -2085,7 +2085,7 @@ describe('cloud functions', () => { it('should not have user roles for anonymous calls', async () => { let flag = false; Parse.Cloud.define('myFunction', async req => { - expect(req.getRoles).toBeUndefined(); + expect(req.getUserRoles).toBeUndefined(); flag = true; }); diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index d02d983fa3..3c80ccb98a 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -131,11 +131,11 @@ export class FunctionsRouter extends PromiseRouter { params: params, master: req.auth && req.auth.isMaster, user: req.auth && req.auth.user, - getRoles: + getUserRoles: req.auth && req.auth.user ? async () => { - return (await req.auth.getUserRoles()).map(r => r.substr('role:'.length)); - } + return (await req.auth.getUserRoles()).map(r => r.substr('role:'.length)); + } : undefined, installationId: req.info.installationId, log: req.config.loggerController, diff --git a/src/triggers.js b/src/triggers.js index 888f6b4361..e16b645de1 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -292,7 +292,7 @@ export function getRequestObject( } if (auth.user) { request['user'] = auth.user; - request['getRoles'] = async () => { + request['getUserRoles'] = async () => { return (await auth.getUserRoles()).map(r => r.substr('role:'.length)); }; } From 0cbeba614fb1fbd14364c3cd7322ad4e145c84f1 Mon Sep 17 00:00:00 2001 From: Kartal Kaan Bozdogan Date: Wed, 25 Sep 2024 16:50:27 +0200 Subject: [PATCH 6/7] Also test that getUserRoles is recursive --- spec/CloudCode.spec.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index f06391d818..2de059f38a 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1167,12 +1167,12 @@ describe('Cloud Code', () => { let beforeSaveFlag = false; let afterSaveFlag = false; Parse.Cloud.beforeSave('SaveTriggerUserRoles', async req => { - expect(await req.getUserRoles()).toEqual(['TestRole']); + expect(new Set(await req.getUserRoles())).toEqual(new Set(['TestRole1', 'TestRole2'])); beforeSaveFlag = true; }); Parse.Cloud.afterSave('SaveTriggerUserRoles', async req => { - expect(await req.getUserRoles()).toEqual(['TestRole']); + expect(new Set(await req.getUserRoles())).toEqual(new Set(['TestRole1', 'TestRole2'])); afterSaveFlag = true; }); @@ -1181,9 +1181,12 @@ describe('Cloud Code', () => { user.set('email', 'asdf@example.com'); user.set('username', 'zxcv'); await user.signUp(); - const role = new Parse.Role('TestRole', new Parse.ACL({ '*': { read: true, write: true } })); - role.getUsers().add(user); - await role.save(); + const role1 = new Parse.Role('TestRole1', new Parse.ACL({ '*': { read: true, write: true } })); + const role2 = new Parse.Role('TestRole2', new Parse.ACL({ '*': { read: true, write: true } })); + await role1.save(); + role2.getRoles().add(role1); + role1.getUsers().add(user); + await Parse.Object.saveAll([role1, role2]); const obj = new Parse.Object('SaveTriggerUserRoles'); await obj.save(); @@ -2065,7 +2068,7 @@ describe('cloud functions', () => { it('should have user roles', async () => { let flag = false; Parse.Cloud.define('myFunction', async req => { - expect(await req.getUserRoles()).toEqual(['TestRole']); + expect(new Set(await req.getUserRoles())).toEqual(new Set(['TestRole1', 'TestRole2'])); flag = true; }); @@ -2074,9 +2077,12 @@ describe('cloud functions', () => { user.set('email', 'asdf@example.com'); user.set('username', 'zxcv'); await user.signUp(); - const role = new Parse.Role('TestRole', new Parse.ACL({ '*': { read: true, write: true } })); - role.getUsers().add(user); - await role.save(); + const role1 = new Parse.Role('TestRole1', new Parse.ACL({ '*': { read: true, write: true } })); + const role2 = new Parse.Role('TestRole2', new Parse.ACL({ '*': { read: true, write: true } })); + await role1.save(); + role2.getRoles().add(role1); + role1.getUsers().add(user); + await Parse.Object.saveAll([role1, role2]); await Parse.Cloud.run('myFunction', { sessionToken: user.getSessionToken() }); expect(flag).toBeTrue(); From dee0d1fbc3cbc3e95bb359f8a8d7715c9c8ef68e Mon Sep 17 00:00:00 2001 From: Kartal Kaan Bozdogan Date: Wed, 25 Sep 2024 17:03:38 +0200 Subject: [PATCH 7/7] Added a new util, stripACLRolePrefix --- src/Routers/FunctionsRouter.js | 3 ++- src/Utils.js | 12 ++++++++++++ src/triggers.js | 3 ++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index 3c80ccb98a..301b3cb123 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -8,6 +8,7 @@ import { promiseEnforceMasterKeyAccess, promiseEnsureIdempotency } from '../midd import { jobStatusHandler } from '../StatusHandler'; import _ from 'lodash'; import { logger } from '../logger'; +import Utils from '../Utils'; function parseObject(obj, config) { if (Array.isArray(obj)) { @@ -134,7 +135,7 @@ export class FunctionsRouter extends PromiseRouter { getUserRoles: req.auth && req.auth.user ? async () => { - return (await req.auth.getUserRoles()).map(r => r.substr('role:'.length)); + return (await req.auth.getUserRoles()).map(Utils.stripACLRolePrefix); } : undefined, installationId: req.info.installationId, diff --git a/src/Utils.js b/src/Utils.js index b77a3d85d7..1466cc1449 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -399,6 +399,18 @@ class Utils { } return obj; } + + /** + * Strips the "role:" prefix from the role name as it appears in the ACL. + * + * @param {String} entry The role name prefixed with the string "role:". + * @returns {String} The role name, without the "role": prefix. + * @example + * stripACLRolePrefix("role:myrole") // Returns "myrole" + */ + static stripACLRolePrefix(entry) { + return entry.substr(5 /* 'role:'.length */); + } } module.exports = Utils; diff --git a/src/triggers.js b/src/triggers.js index e16b645de1..3e3db04a7c 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -1,6 +1,7 @@ // triggers.js import Parse from 'parse/node'; import { logger } from './logger'; +import Utils from './Utils'; export const Types = { beforeLogin: 'beforeLogin', @@ -293,7 +294,7 @@ export function getRequestObject( if (auth.user) { request['user'] = auth.user; request['getUserRoles'] = async () => { - return (await auth.getUserRoles()).map(r => r.substr('role:'.length)); + return (await auth.getUserRoles()).map(Utils.stripACLRolePrefix); }; } if (auth.installationId) {