diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 99ec4910d1..2de059f38a 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1163,6 +1163,56 @@ describe('Cloud Code', () => { ); }); + it('test save triggers get user roles', async () => { + let beforeSaveFlag = false; + let afterSaveFlag = false; + Parse.Cloud.beforeSave('SaveTriggerUserRoles', async req => { + expect(new Set(await req.getUserRoles())).toEqual(new Set(['TestRole1', 'TestRole2'])); + beforeSaveFlag = true; + }); + + Parse.Cloud.afterSave('SaveTriggerUserRoles', async req => { + expect(new Set(await req.getUserRoles())).toEqual(new Set(['TestRole1', 'TestRole2'])); + 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 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(); + expect(beforeSaveFlag).toBeTrue(); + expect(afterSaveFlag).toBeTrue(); + }); + + it('should not have user roles for anonymous calls', async () => { + let beforeSaveFlag = false; + let afterSaveFlag = false; + Parse.Cloud.beforeSave('SaveTriggerUserRoles', async req => { + expect(req.getUserRoles).toBeUndefined(); + beforeSaveFlag = true; + }); + + Parse.Cloud.afterSave('SaveTriggerUserRoles', async req => { + expect(req.getUserRoles).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 +2064,40 @@ describe('cloud functions', () => { Parse.Cloud.run('myFunction', {}).then(() => done()); }); + + it('should have user roles', async () => { + let flag = false; + Parse.Cloud.define('myFunction', async req => { + expect(new Set(await req.getUserRoles())).toEqual(new Set(['TestRole1', 'TestRole2'])); + 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 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(); + }); + + it('should not have user roles for anonymous calls', async () => { + let flag = false; + Parse.Cloud.define('myFunction', async req => { + expect(req.getUserRoles).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..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)) { @@ -131,6 +132,12 @@ export class FunctionsRouter extends PromiseRouter { params: params, master: req.auth && req.auth.isMaster, user: req.auth && req.auth.user, + getUserRoles: + req.auth && req.auth.user + ? async () => { + return (await req.auth.getUserRoles()).map(Utils.stripACLRolePrefix); + } + : undefined, installationId: req.info.installationId, log: req.config.loggerController, headers: req.config.headers, 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 e34c5fd3a8..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', @@ -292,6 +293,9 @@ export function getRequestObject( } if (auth.user) { request['user'] = auth.user; + request['getUserRoles'] = async () => { + return (await auth.getUserRoles()).map(Utils.stripACLRolePrefix); + }; } if (auth.installationId) { request['installationId'] = auth.installationId;