diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b5fc868a1..67b1e053a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ### master [Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.0...master) +#### Improvements: +* Fixes issue that would prevent users with large number of roles to resolve all of them [@Moumouls]() (#5131, #5132) + + ### 3.1.0 [Full Changelog](https://github.com/parse-community/parse-server/compare/3.0.0...3.1.0) diff --git a/spec/Auth.spec.js b/spec/Auth.spec.js index 6dc34e36f1..0b44ca95b0 100644 --- a/spec/Auth.spec.js +++ b/spec/Auth.spec.js @@ -149,4 +149,56 @@ describe('Auth', () => { expect(userAuth.user instanceof Parse.User).toBe(true); expect(userAuth.user.id).toBe(user.id); }); + + describe('getRolesForUser', () => { + + const rolesNumber = 300; + + it('should load all roles without config', async () => { + const user = new Parse.User(); + await user.signUp({ + username: 'hello', + password: 'password', + }); + expect(user.getSessionToken()).not.toBeUndefined(); + const userAuth = await getAuthForSessionToken({ + sessionToken: user.getSessionToken(), + }); + const roles = []; + for(let i = 0; i < rolesNumber;i++){ + const acl = new Parse.ACL(); + const role = new Parse.Role("roleloadtest" + i, acl); + role.getUsers().add([user]); + roles.push(role.save()) + } + const savedRoles = await Promise.all(roles); + expect(savedRoles.length).toBe(rolesNumber); + const cloudRoles = await userAuth.getRolesForUser(); + expect(cloudRoles.length).toBe(rolesNumber); + }); + + it('should load all roles with config', async () => { + const user = new Parse.User(); + await user.signUp({ + username: 'hello', + password: 'password', + }); + expect(user.getSessionToken()).not.toBeUndefined(); + const userAuth = await getAuthForSessionToken({ + sessionToken: user.getSessionToken(), + config: Config.get('test'), + }); + const roles = []; + for(let i = 0; i < rolesNumber;i++){ + const acl = new Parse.ACL(); + const role = new Parse.Role("roleloadtest" + i, acl); + role.getUsers().add([user]); + roles.push(role.save()) + } + const savedRoles = await Promise.all(roles); + expect(savedRoles.length).toBe(rolesNumber); + const cloudRoles = await userAuth.getRolesForUser(); + expect(cloudRoles.length).toBe(rolesNumber); + }); + }); }); diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 6def159240..934cf46722 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -1307,6 +1307,16 @@ describe('ParseLiveQueryServer', function() { liveQueryRole.id = 'abcdef1234'; return Promise.resolve([liveQueryRole]); }, + each(callback) { + //Return a role with the name "liveQueryRead" as that is what was set on the ACL + const liveQueryRole = new Parse.Role( + 'liveQueryRead', + new Parse.ACL() + ); + liveQueryRole.id = 'abcdef1234'; + callback(liveQueryRole) + return Promise.resolve(); + }, }; }); @@ -1316,13 +1326,6 @@ describe('ParseLiveQueryServer', function() { expect(isMatched).toBe(true); done(); }); - - parseLiveQueryServer - ._matchesACL(acl, client, requestId) - .then(function(isMatched) { - expect(isMatched).toBe(true); - done(); - }); }); describe('class level permissions', () => { diff --git a/spec/RestQuery.spec.js b/spec/RestQuery.spec.js index 18f9f72657..e17af81b8d 100644 --- a/spec/RestQuery.spec.js +++ b/spec/RestQuery.spec.js @@ -3,6 +3,7 @@ const auth = require('../lib/Auth'); const Config = require('../lib/Config'); const rest = require('../lib/rest'); +const RestQuery = require('../lib/RestQuery'); const request = require('../lib/request'); const querystring = require('querystring'); @@ -335,3 +336,31 @@ describe('rest query', () => { ); }); }); + +describe('RestQuery.each', () => { + it('should run each', async () => { + const objects = []; + while (objects.length != 10) { + objects.push(new Parse.Object('Object', { value: objects.length })); + } + const config = Config.get('test'); + await Parse.Object.saveAll(objects); + const query = new RestQuery( + config, + auth.master(config), + 'Object', + { value: { $gt: 2 } }, + { limit: 2 } + ); + const spy = spyOn(query, 'execute').and.callThrough(); + const classSpy = spyOn(RestQuery.prototype, 'execute').and.callThrough(); + const results = []; + await query.each(result => { + expect(result.value).toBeGreaterThan(2); + results.push(result); + }); + expect(spy.calls.count()).toBe(0); + expect(classSpy.calls.count()).toBe(4); + expect(results.length).toBe(7); + }); +}); diff --git a/src/Auth.js b/src/Auth.js index 64c69cfd9c..ebb5debfd2 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -184,7 +184,9 @@ Auth.prototype.getUserRoles = function() { return this.rolePromise; }; -Auth.prototype.getRolesForUser = function() { +Auth.prototype.getRolesForUser = async function() { + //Stack all Parse.Role + const results = []; if (this.config) { const restWhere = { users: { @@ -193,20 +195,19 @@ Auth.prototype.getRolesForUser = function() { objectId: this.user.id, }, }; - const query = new RestQuery( + await new RestQuery( this.config, master(this.config), '_Role', restWhere, {} - ); - return query.execute().then(({ results }) => results); + ).each(result => results.push(result)); + } else { + await new Parse.Query(Parse.Role) + .equalTo('users', this.user) + .each(result => results.push(result.toJSON()), { useMasterKey: true }); } - - return new Parse.Query(Parse.Role) - .equalTo('users', this.user) - .find({ useMasterKey: true }) - .then(results => results.map(obj => obj.toJSON())); + return results; }; // Iterates through the role tree and compiles a user's roles @@ -262,19 +263,11 @@ Auth.prototype.cacheRoles = function() { return true; }; -Auth.prototype.getRolesByIds = function(ins) { - const roles = ins.map(id => { - return { - __type: 'Pointer', - className: '_Role', - objectId: id, - }; - }); - const restWhere = { roles: { $in: roles } }; - +Auth.prototype.getRolesByIds = async function(ins) { + const results = []; // Build an OR query across all parentRoles if (!this.config) { - return new Parse.Query(Parse.Role) + await new Parse.Query(Parse.Role) .containedIn( 'roles', ins.map(id => { @@ -283,13 +276,25 @@ Auth.prototype.getRolesByIds = function(ins) { return role; }) ) - .find({ useMasterKey: true }) - .then(results => results.map(obj => obj.toJSON())); + .each(result => results.push(result.toJSON()), { useMasterKey: true }); + } else { + const roles = ins.map(id => { + return { + __type: 'Pointer', + className: '_Role', + objectId: id, + }; + }); + const restWhere = { roles: { $in: roles } }; + await new RestQuery( + this.config, + master(this.config), + '_Role', + restWhere, + {} + ).each(result => results.push(result)); } - - return new RestQuery(this.config, master(this.config), '_Role', restWhere, {}) - .execute() - .then(({ results }) => results); + return results; }; // Given a list of roleIds, find all the parent roles, returns a promise with all names diff --git a/src/RestQuery.js b/src/RestQuery.js index e39d52804f..aa53c0ebf0 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -4,7 +4,7 @@ var SchemaController = require('./Controllers/SchemaController'); var Parse = require('parse/node').Parse; const triggers = require('./triggers'); - +const { continueWhile } = require('parse/lib/node/promiseUtils'); const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL']; // restOptions can include: // skip @@ -199,6 +199,36 @@ RestQuery.prototype.execute = function(executeOptions) { }); }; +RestQuery.prototype.each = function(callback) { + const { config, auth, className, restWhere, restOptions, clientSDK } = this; + // if the limit is set, use it + restOptions.limit = restOptions.limit || 100; + restOptions.order = 'objectId'; + let finished = false; + + return continueWhile( + () => { + return !finished; + }, + async () => { + const query = new RestQuery( + config, + auth, + className, + restWhere, + restOptions, + clientSDK + ); + const { results } = await query.execute(); + results.forEach(callback); + finished = results.length < restOptions.limit; + if (!finished) { + restWhere.objectId = { $gt: results[results.length - 1].objectId }; + } + } + ); +}; + RestQuery.prototype.buildRestWhere = function() { return Promise.resolve() .then(() => {