Merge branch 'master' into flovilmart.dynamicConfigMount

This commit is contained in:
Drew
2016-03-30 20:29:30 -07:00
7 changed files with 176 additions and 106 deletions

View File

@@ -1,3 +1,5 @@
Check out [this issue](https://github.com/ParsePlatform/parse-server/issues/1271) for an ideal bug report. The closer your issue report is to that one, the more likely we are to be able to help, and the more likely we will be to fix the issue quickly!
For implementation related questions or technical support, please refer to the [Stack Overflow](http://stackoverflow.com/questions/tagged/parse.com) and [Server Fault](https://serverfault.com/tags/parse) communities. For implementation related questions or technical support, please refer to the [Stack Overflow](http://stackoverflow.com/questions/tagged/parse.com) and [Server Fault](https://serverfault.com/tags/parse) communities.
Make sure these boxes are checked before submitting your issue -- thanks for reporting issues back to Parse Server! Make sure these boxes are checked before submitting your issue -- thanks for reporting issues back to Parse Server!

View File

@@ -26,6 +26,7 @@
"commander": "^2.9.0", "commander": "^2.9.0",
"deepcopy": "^0.6.1", "deepcopy": "^0.6.1",
"express": "^4.13.4", "express": "^4.13.4",
"intersect": "^1.0.1",
"lru-cache": "^4.0.0", "lru-cache": "^4.0.0",
"mailgun-js": "^0.7.7", "mailgun-js": "^0.7.7",
"mime": "^1.3.4", "mime": "^1.3.4",

View File

@@ -2209,4 +2209,41 @@ describe('Parse.Query testing', () => {
}) })
}) })
it('query with two OR subqueries (regression test #1259)', done => {
let relatedObject = new Parse.Object('Class2');
relatedObject.save().then(relatedObject => {
let anObject = new Parse.Object('Class1');
let relation = anObject.relation('relation');
relation.add(relatedObject);
return anObject.save();
}).then(anObject => {
let q1 = anObject.relation('relation').query();
q1.doesNotExist('nonExistantKey1');
let q2 = anObject.relation('relation').query();
q2.doesNotExist('nonExistantKey2');
let orQuery = Parse.Query.or(q1, q2).find().then(results => {
expect(results.length).toEqual(1);
expect(results[0].objectId).toEqual(q1.objectId);
done();
});
});
});
it('objectId containedIn with multiple large array', done => {
let obj = new Parse.Object('MyClass');
obj.save().then(obj => {
let longListOfStrings = [];
for (let i = 0; i < 130; i++) {
longListOfStrings.push(i.toString());
}
longListOfStrings.push(obj.id);
let q = new Parse.Query('MyClass');
q.containedIn('objectId', longListOfStrings);
q.containedIn('objectId', longListOfStrings);
return q.find();
}).then(results => {
expect(results.length).toEqual(1);
done();
});
});
}); });

View File

@@ -248,46 +248,50 @@ describe('Parse.Relation testing', () => {
}); });
}); });
it("queries on relation fields with multiple ins", (done) => { it("queries on relation fields with multiple containedIn (regression test for #1271)", (done) => {
var ChildObject = Parse.Object.extend("ChildObject"); let ChildObject = Parse.Object.extend("ChildObject");
var childObjects = []; let childObjects = [];
for (var i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i})); childObjects.push(new ChildObject({x: i}));
} }
Parse.Object.saveAll(childObjects).then(() => { Parse.Object.saveAll(childObjects).then(() => {
var ParentObject = Parse.Object.extend("ParentObject"); let ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject(); let parent = new ParentObject();
parent.set("x", 4); parent.set("x", 4);
var relation = parent.relation("child"); let parent1Children = parent.relation("child");
relation.add(childObjects[0]); parent1Children.add(childObjects[0]);
relation.add(childObjects[1]); parent1Children.add(childObjects[1]);
relation.add(childObjects[2]); parent1Children.add(childObjects[2]);
var parent2 = new ParentObject(); let parent2 = new ParentObject();
parent2.set("x", 3); parent2.set("x", 3);
var relation2 = parent2.relation("child"); let parent2Children = parent2.relation("child");
relation2.add(childObjects[4]); parent2Children.add(childObjects[4]);
relation2.add(childObjects[5]); parent2Children.add(childObjects[5]);
relation2.add(childObjects[6]); parent2Children.add(childObjects[6]);
var otherChild2 = parent2.relation("otherChild"); let parent2OtherChildren = parent2.relation("otherChild");
otherChild2.add(childObjects[0]); parent2OtherChildren.add(childObjects[0]);
otherChild2.add(childObjects[1]); parent2OtherChildren.add(childObjects[1]);
otherChild2.add(childObjects[2]); parent2OtherChildren.add(childObjects[2]);
var parents = []; return Parse.Object.saveAll([parent, parent2]);
parents.push(parent);
parents.push(parent2);
return Parse.Object.saveAll(parents);
}).then(() => { }).then(() => {
var query = new Parse.Query(ParentObject); let objectsWithChild0InBothChildren = new Parse.Query(ParentObject);
var objects = []; objectsWithChild0InBothChildren.containedIn("child", [childObjects[0]]);
objects.push(childObjects[0]); objectsWithChild0InBothChildren.containedIn("otherChild", [childObjects[0]]);
query.containedIn("child", objects); return objectsWithChild0InBothChildren.find();
query.containedIn("otherChild", [childObjects[0]]); }).then(objectsWithChild0InBothChildren => {
return query.find(); //No parent has child 0 in both it's "child" and "otherChild" field;
}).then((list) => { expect(objectsWithChild0InBothChildren.length).toEqual(0);
equal(list.length, 2, "There should be 2 results"); }).then(() => {
let objectsWithChild4andOtherChild1 = new Parse.Query(ParentObject);
objectsWithChild4andOtherChild1.containedIn("child", [childObjects[4]]);
objectsWithChild4andOtherChild1.containedIn("otherChild", [childObjects[1]]);
return objectsWithChild4andOtherChild1.find();
}).then(objects => {
// parent2 has child 4 and otherChild 1
expect(objects.length).toEqual(1);
done(); done();
}); });
}); });

View File

@@ -981,7 +981,7 @@ describe('schemas', () => {
}); });
}); });
}); });
it('should not be able to add a field', done => { it('should not be able to add a field', done => {
request.post({ request.post({
url: 'http://localhost:8378/1/schemas/AClass', url: 'http://localhost:8378/1/schemas/AClass',
@@ -1010,7 +1010,7 @@ describe('schemas', () => {
}) })
}) })
}); });
it('should not be able to add a field', done => { it('should not be able to add a field', done => {
request.post({ request.post({
url: 'http://localhost:8378/1/schemas/AClass', url: 'http://localhost:8378/1/schemas/AClass',
@@ -1038,7 +1038,7 @@ describe('schemas', () => {
}) })
}) })
}); });
it('should throw with invalid userId (>10 chars)', done => { it('should throw with invalid userId (>10 chars)', done => {
request.post({ request.post({
url: 'http://localhost:8378/1/schemas/AClass', url: 'http://localhost:8378/1/schemas/AClass',
@@ -1056,7 +1056,7 @@ describe('schemas', () => {
done(); done();
}) })
}); });
it('should throw with invalid userId (<10 chars)', done => { it('should throw with invalid userId (<10 chars)', done => {
request.post({ request.post({
url: 'http://localhost:8378/1/schemas/AClass', url: 'http://localhost:8378/1/schemas/AClass',
@@ -1074,7 +1074,7 @@ describe('schemas', () => {
done(); done();
}) })
}); });
it('should throw with invalid userId (invalid char)', done => { it('should throw with invalid userId (invalid char)', done => {
request.post({ request.post({
url: 'http://localhost:8378/1/schemas/AClass', url: 'http://localhost:8378/1/schemas/AClass',
@@ -1092,7 +1092,7 @@ describe('schemas', () => {
done(); done();
}) })
}); });
it('should throw with invalid * (spaces)', done => { it('should throw with invalid * (spaces)', done => {
request.post({ request.post({
url: 'http://localhost:8378/1/schemas/AClass', url: 'http://localhost:8378/1/schemas/AClass',
@@ -1110,7 +1110,7 @@ describe('schemas', () => {
done(); done();
}) })
}); });
it('should throw with invalid * (spaces)', done => { it('should throw with invalid * (spaces)', done => {
request.post({ request.post({
url: 'http://localhost:8378/1/schemas/AClass', url: 'http://localhost:8378/1/schemas/AClass',
@@ -1128,7 +1128,7 @@ describe('schemas', () => {
done(); done();
}) })
}); });
it('should throw with invalid value', done => { it('should throw with invalid value', done => {
request.post({ request.post({
url: 'http://localhost:8378/1/schemas/AClass', url: 'http://localhost:8378/1/schemas/AClass',
@@ -1146,7 +1146,7 @@ describe('schemas', () => {
done(); done();
}) })
}); });
it('should throw with invalid value', done => { it('should throw with invalid value', done => {
request.post({ request.post({
url: 'http://localhost:8378/1/schemas/AClass', url: 'http://localhost:8378/1/schemas/AClass',
@@ -1164,10 +1164,10 @@ describe('schemas', () => {
done(); done();
}) })
}); });
function setPermissionsOnClass(className, permissions, doPut) { function setPermissionsOnClass(className, permissions, doPut) {
let op = request.post; let op = request.post;
if (doPut) if (doPut)
{ {
op = request.put; op = request.put;
} }
@@ -1190,18 +1190,18 @@ describe('schemas', () => {
}) })
}); });
} }
it('validate CLP 1', done => { it('validate CLP 1', done => {
let user = new Parse.User(); let user = new Parse.User();
user.setUsername('user'); user.setUsername('user');
user.setPassword('user'); user.setPassword('user');
let admin = new Parse.User(); let admin = new Parse.User();
admin.setUsername('admin'); admin.setUsername('admin');
admin.setPassword('admin'); admin.setPassword('admin');
let role = new Parse.Role('admin', new Parse.ACL()); let role = new Parse.Role('admin', new Parse.ACL());
setPermissionsOnClass('AClass', { setPermissionsOnClass('AClass', {
'find': { 'find': {
'role:admin': true 'role:admin': true
@@ -1239,18 +1239,18 @@ describe('schemas', () => {
done(); done();
}) })
}); });
it('validate CLP 2', done => { it('validate CLP 2', done => {
let user = new Parse.User(); let user = new Parse.User();
user.setUsername('user'); user.setUsername('user');
user.setPassword('user'); user.setPassword('user');
let admin = new Parse.User(); let admin = new Parse.User();
admin.setUsername('admin'); admin.setUsername('admin');
admin.setPassword('admin'); admin.setPassword('admin');
let role = new Parse.Role('admin', new Parse.ACL()); let role = new Parse.Role('admin', new Parse.ACL());
setPermissionsOnClass('AClass', { setPermissionsOnClass('AClass', {
'find': { 'find': {
'role:admin': true 'role:admin': true
@@ -1304,18 +1304,18 @@ describe('schemas', () => {
done(); done();
}) })
}); });
it('validate CLP 3', done => { it('validate CLP 3', done => {
let user = new Parse.User(); let user = new Parse.User();
user.setUsername('user'); user.setUsername('user');
user.setPassword('user'); user.setPassword('user');
let admin = new Parse.User(); let admin = new Parse.User();
admin.setUsername('admin'); admin.setUsername('admin');
admin.setPassword('admin'); admin.setPassword('admin');
let role = new Parse.Role('admin', new Parse.ACL()); let role = new Parse.Role('admin', new Parse.ACL());
setPermissionsOnClass('AClass', { setPermissionsOnClass('AClass', {
'find': { 'find': {
'role:admin': true 'role:admin': true
@@ -1362,18 +1362,18 @@ describe('schemas', () => {
done(); done();
}); });
}); });
it('validate CLP 4', done => { it('validate CLP 4', done => {
let user = new Parse.User(); let user = new Parse.User();
user.setUsername('user'); user.setUsername('user');
user.setPassword('user'); user.setPassword('user');
let admin = new Parse.User(); let admin = new Parse.User();
admin.setUsername('admin'); admin.setUsername('admin');
admin.setPassword('admin'); admin.setPassword('admin');
let role = new Parse.Role('admin', new Parse.ACL()); let role = new Parse.Role('admin', new Parse.ACL());
setPermissionsOnClass('AClass', { setPermissionsOnClass('AClass', {
'find': { 'find': {
'role:admin': true 'role:admin': true
@@ -1400,7 +1400,7 @@ describe('schemas', () => {
// borked CLP should not affec security // borked CLP should not affec security
return setPermissionsOnClass('AClass', { return setPermissionsOnClass('AClass', {
'found': { 'found': {
'role:admin': true 'role:admin': true
} }
}, true).then(() => { }, true).then(() => {
fail("Should not be able to save a borked CLP"); fail("Should not be able to save a borked CLP");
@@ -1430,21 +1430,21 @@ describe('schemas', () => {
done(); done();
}) })
}); });
it('validate CLP 5', done => { it('validate CLP 5', done => {
let user = new Parse.User(); let user = new Parse.User();
user.setUsername('user'); user.setUsername('user');
user.setPassword('user'); user.setPassword('user');
let user2 = new Parse.User(); let user2 = new Parse.User();
user2.setUsername('user2'); user2.setUsername('user2');
user2.setPassword('user2'); user2.setPassword('user2');
let admin = new Parse.User(); let admin = new Parse.User();
admin.setUsername('admin'); admin.setUsername('admin');
admin.setPassword('admin'); admin.setPassword('admin');
let role = new Parse.Role('admin', new Parse.ACL()); let role = new Parse.Role('admin', new Parse.ACL());
Promise.resolve().then(() => { Promise.resolve().then(() => {
return Parse.Object.saveAll([user, user2, admin, role], {useMasterKey: true}); return Parse.Object.saveAll([user, user2, admin, role], {useMasterKey: true});
}).then(()=> { }).then(()=> {
@@ -1495,5 +1495,21 @@ describe('schemas', () => {
}).then(() => { }).then(() => {
done(); done();
}); });
}); });
it('can add field as master (issue #1257)', (done) => {
setPermissionsOnClass('AClass', {
'addField': {}
}).then(() => {
var obj = new Parse.Object('AClass');
obj.set('key', 'value');
return obj.save(null, {useMasterKey: true})
}).then((obj) => {
expect(obj.get('key')).toEqual('value');
done();
}, (err) => {
fail('should not fail');
done();
});
})
}); });

View File

@@ -1,6 +1,8 @@
// A database adapter that works with data exported from the hosted // A database adapter that works with data exported from the hosted
// Parse database. // Parse database.
import intersect from 'intersect';
var mongodb = require('mongodb'); var mongodb = require('mongodb');
var Parse = require('parse/node').Parse; var Parse = require('parse/node').Parse;
@@ -103,9 +105,14 @@ DatabaseController.prototype.redirectClassNameForKey = function(className, key)
// batch request, that could confuse other users of the schema. // batch request, that could confuse other users of the schema.
DatabaseController.prototype.validateObject = function(className, object, query, options) { DatabaseController.prototype.validateObject = function(className, object, query, options) {
let schema; let schema;
let isMaster = !('acl' in options);
var aclGroup = options.acl || [];
return this.loadSchema().then(s => { return this.loadSchema().then(s => {
schema = s; schema = s;
return this.canAddField(schema, className, object, options.acl || []); if (isMaster) {
return Promise.resolve();
}
return this.canAddField(schema, className, object, aclGroup);
}).then(() => { }).then(() => {
return schema.validateObject(className, object, query); return schema.validateObject(className, object, query);
}); });
@@ -487,18 +494,28 @@ DatabaseController.prototype.reduceRelationKeys = function(className, query) {
} }
}; };
DatabaseController.prototype.addInObjectIdsIds = function(ids, query) { DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
if (typeof query.objectId == 'string') { let idsFromString = typeof query.objectId === 'string' ? [query.objectId] : null;
// Add equality op as we are sure let idsFromEq = query.objectId && query.objectId['$eq'] ? [query.objectId['$eq']] : null;
// we had a constraint on that one let idsFromIn = query.objectId && query.objectId['$in'] ? query.objectId['$in'] : null;
query.objectId = {'$eq': query.objectId};
let allIds = [idsFromString, idsFromEq, idsFromIn, ids].filter(list => list !== null);
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
let idsIntersection = [];
if (totalLength > 125) {
idsIntersection = intersect.big(allIds);
} else {
idsIntersection = intersect(allIds);
} }
query.objectId = query.objectId || {};
let queryIn = [].concat(query.objectId['$in'] || [], ids || []); // Need to make sure we don't clobber existing $lt or other constraints on objectId.
// make a set and spread to remove duplicates // Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
// replace the $in operator as other constraints // is expected though.
// may be set if (!('objectId' in query) || typeof query.objectId === 'string') {
query.objectId['$in'] = [...new Set(queryIn)]; query.objectId = {};
}
query.objectId['$in'] = idsIntersection;
return query; return query;
} }
@@ -518,7 +535,7 @@ DatabaseController.prototype.addInObjectIdsIds = function(ids, query) {
// anything about users, ideally. Then, improve the format of the ACL // anything about users, ideally. Then, improve the format of the ACL
// arg to work like the others. // arg to work like the others.
DatabaseController.prototype.find = function(className, query, options = {}) { DatabaseController.prototype.find = function(className, query, options = {}) {
var mongoOptions = {}; let mongoOptions = {};
if (options.skip) { if (options.skip) {
mongoOptions.skip = options.skip; mongoOptions.skip = options.skip;
} }
@@ -526,45 +543,39 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
mongoOptions.limit = options.limit; mongoOptions.limit = options.limit;
} }
var isMaster = !('acl' in options); let isMaster = !('acl' in options);
var aclGroup = options.acl || []; let aclGroup = options.acl || [];
var acceptor = function(schema) { let acceptor = schema => schema.hasKeys(className, keysForQuery(query))
return schema.hasKeys(className, keysForQuery(query)); let schema = null;
}; return this.loadSchema(acceptor).then(s => {
var schema;
return this.loadSchema(acceptor).then((s) => {
schema = s; schema = s;
if (options.sort) { if (options.sort) {
mongoOptions.sort = {}; mongoOptions.sort = {};
for (var key in options.sort) { for (let key in options.sort) {
var mongoKey = transform.transformKey(schema, className, key); let mongoKey = transform.transformKey(schema, className, key);
mongoOptions.sort[mongoKey] = options.sort[key]; mongoOptions.sort[mongoKey] = options.sort[key];
} }
} }
if (!isMaster) { if (!isMaster) {
var op = 'find'; let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ?
var k = Object.keys(query); 'get' :
if (k.length == 1 && typeof query.objectId == 'string') { 'find';
op = 'get';
}
return schema.validatePermission(className, aclGroup, op); return schema.validatePermission(className, aclGroup, op);
} }
return Promise.resolve(); return Promise.resolve();
}).then(() => { })
return this.reduceRelationKeys(className, query); .then(() => this.reduceRelationKeys(className, query))
}).then(() => { .then(() => this.reduceInRelation(className, query, schema))
return this.reduceInRelation(className, query, schema); .then(() => this.adaptiveCollection(className))
}).then(() => { .then(collection => {
return this.adaptiveCollection(className); let mongoWhere = transform.transformWhere(schema, className, query);
}).then(collection => {
var mongoWhere = transform.transformWhere(schema, className, query);
if (!isMaster) { if (!isMaster) {
var orParts = [ let orParts = [
{"_rperm" : { "$exists": false }}, {"_rperm" : { "$exists": false }},
{"_rperm" : { "$in" : ["*"]}} {"_rperm" : { "$in" : ["*"]}}
]; ];
for (var acl of aclGroup) { for (let acl of aclGroup) {
orParts.push({"_rperm" : { "$in" : [acl]}}); orParts.push({"_rperm" : { "$in" : [acl]}});
} }
mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]}; mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]};

View File

@@ -187,13 +187,12 @@ export function transformKeyValue(schema, className, restKey, restValue, options
// Returns the mongo form of the query. // Returns the mongo form of the query.
// Throws a Parse.Error if the input query is invalid. // Throws a Parse.Error if the input query is invalid.
function transformWhere(schema, className, restWhere) { function transformWhere(schema, className, restWhere) {
var mongoWhere = {}; let mongoWhere = {};
if (restWhere['ACL']) { if (restWhere['ACL']) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
'Cannot query on ACL.');
} }
for (var restKey in restWhere) { for (let restKey in restWhere) {
var out = transformKeyValue(schema, className, restKey, restWhere[restKey], let out = transformKeyValue(schema, className, restKey, restWhere[restKey],
{query: true, validate: true}); {query: true, validate: true});
mongoWhere[out.key] = out.value; mongoWhere[out.key] = out.value;
} }