* Adds test for 3835 * Makes sure we run relational updates AFTER validating access to the object * Always run relation udpates last
This commit is contained in:
@@ -428,4 +428,24 @@ describe('Parse Role testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be secure (#3835)', (done) => {
|
||||||
|
const acl = new Parse.ACL();
|
||||||
|
acl.getPublicReadAccess(true);
|
||||||
|
const role = new Parse.Role('admin', acl);
|
||||||
|
role.save().then(() => {
|
||||||
|
const user = new Parse.User();
|
||||||
|
return user.signUp({username: 'hello', password: 'world'});
|
||||||
|
}).then((user) => {
|
||||||
|
role.getUsers().add(user)
|
||||||
|
return role.save();
|
||||||
|
}).then(done.fail, () => {
|
||||||
|
const query = role.getUsers().query();
|
||||||
|
return query.find({useMasterKey: true});
|
||||||
|
}).then((results) => {
|
||||||
|
expect(results.length).toBe(0);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -235,17 +235,18 @@ DatabaseController.prototype.update = function(className, query, update, {
|
|||||||
many,
|
many,
|
||||||
upsert,
|
upsert,
|
||||||
} = {}, skipSanitization = false) {
|
} = {}, skipSanitization = false) {
|
||||||
|
const originalQuery = query;
|
||||||
const originalUpdate = update;
|
const originalUpdate = update;
|
||||||
// Make a copy of the object, so we don't mutate the incoming data.
|
// Make a copy of the object, so we don't mutate the incoming data.
|
||||||
update = deepcopy(update);
|
update = deepcopy(update);
|
||||||
|
var relationUpdates = [];
|
||||||
var isMaster = acl === undefined;
|
var isMaster = acl === undefined;
|
||||||
var aclGroup = acl || [];
|
var aclGroup = acl || [];
|
||||||
return this.loadSchema()
|
return this.loadSchema()
|
||||||
.then(schemaController => {
|
.then(schemaController => {
|
||||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update'))
|
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update'))
|
||||||
.then(() => this.handleRelationUpdates(className, query.objectId, update))
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
relationUpdates = this.collectRelationUpdates(className, originalQuery.objectId, update);
|
||||||
if (!isMaster) {
|
if (!isMaster) {
|
||||||
query = this.addPointerPermissions(schemaController, className, 'update', query, aclGroup);
|
query = this.addPointerPermissions(schemaController, className, 'update', query, aclGroup);
|
||||||
}
|
}
|
||||||
@@ -295,6 +296,10 @@ DatabaseController.prototype.update = function(className, query, update, {
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'));
|
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'));
|
||||||
}
|
}
|
||||||
|
return this.handleRelationUpdates(className, originalQuery.objectId, update, relationUpdates).then(() => {
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}).then((result) => {
|
||||||
if (skipSanitization) {
|
if (skipSanitization) {
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
}
|
}
|
||||||
@@ -320,12 +325,11 @@ function sanitizeDatabaseResult(originalObject, result) {
|
|||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processes relation-updating operations from a REST-format update.
|
// Collect all relation-updating operations from a REST-format update.
|
||||||
// Returns a promise that resolves successfully when these are
|
// Returns a list of all relation updates to perform
|
||||||
// processed.
|
|
||||||
// This mutates update.
|
// This mutates update.
|
||||||
DatabaseController.prototype.handleRelationUpdates = function(className, objectId, update) {
|
DatabaseController.prototype.collectRelationUpdates = function(className, objectId, update) {
|
||||||
var pending = [];
|
var ops = [];
|
||||||
var deleteMe = [];
|
var deleteMe = [];
|
||||||
objectId = update.objectId || objectId;
|
objectId = update.objectId || objectId;
|
||||||
|
|
||||||
@@ -334,20 +338,12 @@ DatabaseController.prototype.handleRelationUpdates = function(className, objectI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (op.__op == 'AddRelation') {
|
if (op.__op == 'AddRelation') {
|
||||||
for (const object of op.objects) {
|
ops.push({key, op});
|
||||||
pending.push(this.addRelation(key, className,
|
|
||||||
objectId,
|
|
||||||
object.objectId));
|
|
||||||
}
|
|
||||||
deleteMe.push(key);
|
deleteMe.push(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (op.__op == 'RemoveRelation') {
|
if (op.__op == 'RemoveRelation') {
|
||||||
for (const object of op.objects) {
|
ops.push({key, op});
|
||||||
pending.push(this.removeRelation(key, className,
|
|
||||||
objectId,
|
|
||||||
object.objectId));
|
|
||||||
}
|
|
||||||
deleteMe.push(key);
|
deleteMe.push(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,6 +360,35 @@ DatabaseController.prototype.handleRelationUpdates = function(className, objectI
|
|||||||
for (const key of deleteMe) {
|
for (const key of deleteMe) {
|
||||||
delete update[key];
|
delete update[key];
|
||||||
}
|
}
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processes relation-updating operations from a REST-format update.
|
||||||
|
// Returns a promise that resolves when all updates have been performed
|
||||||
|
DatabaseController.prototype.handleRelationUpdates = function(className, objectId, update, ops) {
|
||||||
|
var pending = [];
|
||||||
|
objectId = update.objectId || objectId;
|
||||||
|
ops.forEach(({key, op}) => {
|
||||||
|
if (!op) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (op.__op == 'AddRelation') {
|
||||||
|
for (const object of op.objects) {
|
||||||
|
pending.push(this.addRelation(key, className,
|
||||||
|
objectId,
|
||||||
|
object.objectId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.__op == 'RemoveRelation') {
|
||||||
|
for (const object of op.objects) {
|
||||||
|
pending.push(this.removeRelation(key, className,
|
||||||
|
objectId,
|
||||||
|
object.objectId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return Promise.all(pending);
|
return Promise.all(pending);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -511,12 +536,11 @@ DatabaseController.prototype.create = function(className, object, { acl } = {})
|
|||||||
|
|
||||||
var isMaster = acl === undefined;
|
var isMaster = acl === undefined;
|
||||||
var aclGroup = acl || [];
|
var aclGroup = acl || [];
|
||||||
|
const relationUpdates = this.collectRelationUpdates(className, null, object);
|
||||||
return this.validateClassName(className)
|
return this.validateClassName(className)
|
||||||
.then(() => this.loadSchema())
|
.then(() => this.loadSchema())
|
||||||
.then(schemaController => {
|
.then(schemaController => {
|
||||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create'))
|
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create'))
|
||||||
.then(() => this.handleRelationUpdates(className, null, object))
|
|
||||||
.then(() => schemaController.enforceClassExists(className))
|
.then(() => schemaController.enforceClassExists(className))
|
||||||
.then(() => schemaController.reloadData())
|
.then(() => schemaController.reloadData())
|
||||||
.then(() => schemaController.getOneSchema(className, true))
|
.then(() => schemaController.getOneSchema(className, true))
|
||||||
@@ -525,7 +549,11 @@ DatabaseController.prototype.create = function(className, object, { acl } = {})
|
|||||||
flattenUpdateOperatorsForCreate(object);
|
flattenUpdateOperatorsForCreate(object);
|
||||||
return this.adapter.createObject(className, SchemaController.convertSchemaToAdapterSchema(schema), object);
|
return this.adapter.createObject(className, SchemaController.convertSchemaToAdapterSchema(schema), object);
|
||||||
})
|
})
|
||||||
.then(result => sanitizeDatabaseResult(originalObject, result.ops[0]));
|
.then(result => {
|
||||||
|
return this.handleRelationUpdates(className, null, object, relationUpdates).then(() => {
|
||||||
|
return sanitizeDatabaseResult(originalObject, result.ops[0])
|
||||||
|
});
|
||||||
|
});
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user