Skip to content

Fix Limitation Role #5131 #5132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Oct 20, 2018
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
52 changes: 52 additions & 0 deletions spec/Auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
17 changes: 10 additions & 7 deletions spec/ParseLiveQueryServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
},
};
});

Expand All @@ -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', () => {
Expand Down
29 changes: 29 additions & 0 deletions spec/RestQuery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
});
});
57 changes: 31 additions & 26 deletions src/Auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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
Expand Down Expand Up @@ -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 => {
Expand All @@ -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
Expand Down
32 changes: 31 additions & 1 deletion src/RestQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(() => {
Expand Down