Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: parse-community/parse-server
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3.1.0
Choose a base ref
...
head repository: parse-community/parse-server
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 3.1.1
Choose a head ref
  • 5 commits
  • 12 files changed
  • 4 contributors

Commits on Oct 19, 2018

  1. Update pg-promise to the latest version 🚀 (#5134)

    * fix(package): update pg-promise to version 8.5.1
    
    * chore(package): update lockfile package-lock.json
    greenkeeper[bot] authored and peril-parse-community[bot] committed Oct 19, 2018
    Copy the full SHA
    aa9580e View commit details

Commits on Oct 20, 2018

  1. Ensure all roles are properly loaded #5131 (#5132)

    * Fix Limitation Role #5131
    
    Allow to manage Live Query with User that have more than 100 Parse.Roles
    
    * Clean Up
    
    * Add Custom Config Support and Test
    
    * Fix Auth Test
    
    * Switch to Async Function
    
    * Fix restWhere
    
    * Fix Test
    
    * Clean Final Commit
    
    * Lint Fix
    
    * Need to Fix Test Callback
    
    * Fixes broken test
    
    * Restore find() method in spy
    
    * adds restquery-each
    
    * small nit
    
    * adds changelog
    Moumouls authored and flovilmart committed Oct 20, 2018
    Copy the full SHA
    de79b70 View commit details

Commits on Oct 22, 2018

  1. "Object not found." instead of "Insufficient auth." when using master…

    … key (#5133)
    
    * add additional isMaster check
    
    * adding some tests
    
    * nits
    
    * covering all basis
    georgesjamous authored and flovilmart committed Oct 22, 2018
    Copy the full SHA
    961abda View commit details

Commits on Oct 26, 2018

  1. Support Distinct for special fields (#5144)

    * Support Distinct for special fields
    
    * update changelog
    dplewis authored Oct 26, 2018
    Copy the full SHA
    daab378 View commit details

Commits on Oct 27, 2018

  1. Release 3.1.1 🚀 (#5149)

    * Release 3.1.1
    
    * Requested Changes
    Moumouls authored and flovilmart committed Oct 27, 2018
    Copy the full SHA
    d356ef5 View commit details
Showing with 283 additions and 72 deletions.
  1. +10 −1 CHANGELOG.md
  2. +43 −26 package-lock.json
  3. +2 −2 package.json
  4. +52 −0 spec/Auth.spec.js
  5. +10 −7 spec/ParseLiveQueryServer.spec.js
  6. +30 −0 spec/ParseQuery.Aggregate.spec.js
  7. +32 −1 spec/ParseUser.spec.js
  8. +29 −0 spec/RestQuery.spec.js
  9. +7 −6 src/Adapters/Storage/Mongo/MongoStorageAdapter.js
  10. +31 −26 src/Auth.js
  11. +31 −1 src/RestQuery.js
  12. +6 −2 src/rest.js
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
## Parse Server Changelog

### master
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.0...master)
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.1...master)


### 3.1.1
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.0...3.1.1)

#### Improvements:
* Fixes issue that would prevent users with large number of roles to resolve all of them [Antoine Cormouls](https://github.com/Moumouls) (#5131, #5132)
* Fixes distinct query on special fields ([#5144](https://github.com/parse-community/parse-server/pull/5144))


### 3.1.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.0.0...3.1.0)
69 changes: 43 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "3.1.0",
"version": "3.1.1",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
@@ -35,7 +35,7 @@
"mime": "2.3.1",
"mongodb": "3.1.8",
"parse": "2.1.0",
"pg-promise": "8.5.0",
"pg-promise": "8.5.1",
"redis": "2.8.0",
"semver": "5.6.0",
"tv4": "1.3.0",
52 changes: 52 additions & 0 deletions spec/Auth.spec.js
Original file line number Diff line number Diff line change
@@ -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
@@ -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', () => {
30 changes: 30 additions & 0 deletions spec/ParseQuery.Aggregate.spec.js
Original file line number Diff line number Diff line change
@@ -1098,6 +1098,36 @@ describe('Parse.Query Aggregate testing', () => {
.catch(done.fail);
});

it('distinct objectId', async () => {
const query = new Parse.Query(TestObject);
const results = await query.distinct('objectId');
expect(results.length).toBe(4);
});

it('distinct createdAt', async () => {
const object1 = new TestObject({ createdAt_test: true });
await object1.save();
const object2 = new TestObject({ createdAt_test: true });
await object2.save();
const query = new Parse.Query(TestObject);
query.equalTo('createdAt_test', true);
const results = await query.distinct('createdAt');
expect(results.length).toBe(2);
});

it('distinct updatedAt', async () => {
const object1 = new TestObject({ updatedAt_test: true });
await object1.save();
const object2 = new TestObject();
await object2.save();
object2.set('updatedAt_test', true);
await object2.save();
const query = new Parse.Query(TestObject);
query.equalTo('updatedAt_test', true);
const results = await query.distinct('updatedAt');
expect(results.length).toBe(2);
});

it('distinct null field', done => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'distinctField' },
33 changes: 32 additions & 1 deletion spec/ParseUser.spec.js
Original file line number Diff line number Diff line change
@@ -3314,7 +3314,9 @@ describe('Parse.User testing', () => {
done();
});
});
}).pend('this test fails. See: https://github.com/parse-community/parse-server/issues/5097');
}).pend(
'this test fails. See: https://github.com/parse-community/parse-server/issues/5097'
);

it('should be able to update user with authData passed', done => {
let objectId;
@@ -3686,6 +3688,35 @@ describe('Parse.User testing', () => {
.then(done, done.fail);
});

it('should throw OBJECT_NOT_FOUND instead of SESSION_MISSING when using masterKey', async () => {
// create a fake user (just so we simulate an object not found)
const non_existent_user = Parse.User.createWithoutData('fake_id');
try {
await non_existent_user.destroy({ useMasterKey: true });
throw '';
} catch (e) {
expect(e.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
}
try {
await non_existent_user.save({}, { useMasterKey: true });
throw '';
} catch (e) {
expect(e.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
}
try {
await non_existent_user.save();
throw '';
} catch (e) {
expect(e.code).toBe(Parse.Error.SESSION_MISSING);
}
try {
await non_existent_user.destroy();
throw '';
} catch (e) {
expect(e.code).toBe(Parse.Error.SESSION_MISSING);
}
});

describe('issue #4897', () => {
it_only_db('mongo')(
'should be able to login with a legacy user (no ACL)',
29 changes: 29 additions & 0 deletions spec/RestQuery.spec.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
13 changes: 7 additions & 6 deletions src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Original file line number Diff line number Diff line change
@@ -710,19 +710,20 @@ export class MongoStorageAdapter implements StorageAdapter {
schema = convertParseSchemaToMongoSchema(schema);
const isPointerField =
schema.fields[fieldName] && schema.fields[fieldName].type === 'Pointer';
if (isPointerField) {
fieldName = `_p_${fieldName}`;
}
const transformField = transformKey(className, fieldName, schema);

return this._adaptiveCollection(className)
.then(collection =>
collection.distinct(fieldName, transformWhere(className, query, schema))
collection.distinct(
transformField,
transformWhere(className, query, schema)
)
)
.then(objects => {
objects = objects.filter(obj => obj != null);
return objects.map(object => {
if (isPointerField) {
const field = fieldName.substring(3);
return transformPointerString(schema, field, object);
return transformPointerString(schema, fieldName, object);
}
return mongoObjectToParseObject(className, object, schema);
});
57 changes: 31 additions & 26 deletions src/Auth.js
Original file line number Diff line number Diff line change
@@ -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
32 changes: 31 additions & 1 deletion src/RestQuery.js
Original file line number Diff line number Diff line change
@@ -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(() => {
8 changes: 6 additions & 2 deletions src/rest.js
Original file line number Diff line number Diff line change
@@ -250,9 +250,13 @@ function update(config, auth, className, restWhere, restObject, clientSDK) {
});
}

function handleSessionMissingError(error, className) {
function handleSessionMissingError(error, className, auth) {
// If we're trying to update a user without / with bad session token
if (className === '_User' && error.code === Parse.Error.OBJECT_NOT_FOUND) {
if (
className === '_User' &&
error.code === Parse.Error.OBJECT_NOT_FOUND &&
!auth.isMaster
) {
throw new Parse.Error(Parse.Error.SESSION_MISSING, 'Insufficient auth.');
}
throw error;