Skip to content

Commit b5a2042

Browse files
authored
Fixes issue #3835 affecting relation updates (#3836)
* Adds test for 3835 * Makes sure we run relational updates AFTER validating access to the object * Always run relation udpates last
1 parent 03b6449 commit b5a2042

File tree

2 files changed

+68
-20
lines changed

2 files changed

+68
-20
lines changed

spec/ParseRole.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,4 +428,24 @@ describe('Parse Role testing', () => {
428428
});
429429
});
430430

431+
it('should be secure (#3835)', (done) => {
432+
const acl = new Parse.ACL();
433+
acl.getPublicReadAccess(true);
434+
const role = new Parse.Role('admin', acl);
435+
role.save().then(() => {
436+
const user = new Parse.User();
437+
return user.signUp({username: 'hello', password: 'world'});
438+
}).then((user) => {
439+
role.getUsers().add(user)
440+
return role.save();
441+
}).then(done.fail, () => {
442+
const query = role.getUsers().query();
443+
return query.find({useMasterKey: true});
444+
}).then((results) => {
445+
expect(results.length).toBe(0);
446+
done();
447+
})
448+
.catch(done.fail);
449+
});
450+
431451
});

src/Controllers/DatabaseController.js

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -235,17 +235,18 @@ DatabaseController.prototype.update = function(className, query, update, {
235235
many,
236236
upsert,
237237
} = {}, skipSanitization = false) {
238+
const originalQuery = query;
238239
const originalUpdate = update;
239240
// Make a copy of the object, so we don't mutate the incoming data.
240241
update = deepcopy(update);
241-
242+
var relationUpdates = [];
242243
var isMaster = acl === undefined;
243244
var aclGroup = acl || [];
244245
return this.loadSchema()
245246
.then(schemaController => {
246247
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update'))
247-
.then(() => this.handleRelationUpdates(className, query.objectId, update))
248248
.then(() => {
249+
relationUpdates = this.collectRelationUpdates(className, originalQuery.objectId, update);
249250
if (!isMaster) {
250251
query = this.addPointerPermissions(schemaController, className, 'update', query, aclGroup);
251252
}
@@ -295,6 +296,10 @@ DatabaseController.prototype.update = function(className, query, update, {
295296
if (!result) {
296297
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'));
297298
}
299+
return this.handleRelationUpdates(className, originalQuery.objectId, update, relationUpdates).then(() => {
300+
return result;
301+
});
302+
}).then((result) => {
298303
if (skipSanitization) {
299304
return Promise.resolve(result);
300305
}
@@ -320,12 +325,11 @@ function sanitizeDatabaseResult(originalObject, result) {
320325
return Promise.resolve(response);
321326
}
322327

323-
// Processes relation-updating operations from a REST-format update.
324-
// Returns a promise that resolves successfully when these are
325-
// processed.
328+
// Collect all relation-updating operations from a REST-format update.
329+
// Returns a list of all relation updates to perform
326330
// This mutates update.
327-
DatabaseController.prototype.handleRelationUpdates = function(className, objectId, update) {
328-
var pending = [];
331+
DatabaseController.prototype.collectRelationUpdates = function(className, objectId, update) {
332+
var ops = [];
329333
var deleteMe = [];
330334
objectId = update.objectId || objectId;
331335

@@ -334,20 +338,12 @@ DatabaseController.prototype.handleRelationUpdates = function(className, objectI
334338
return;
335339
}
336340
if (op.__op == 'AddRelation') {
337-
for (const object of op.objects) {
338-
pending.push(this.addRelation(key, className,
339-
objectId,
340-
object.objectId));
341-
}
341+
ops.push({key, op});
342342
deleteMe.push(key);
343343
}
344344

345345
if (op.__op == 'RemoveRelation') {
346-
for (const object of op.objects) {
347-
pending.push(this.removeRelation(key, className,
348-
objectId,
349-
object.objectId));
350-
}
346+
ops.push({key, op});
351347
deleteMe.push(key);
352348
}
353349

@@ -364,6 +360,35 @@ DatabaseController.prototype.handleRelationUpdates = function(className, objectI
364360
for (const key of deleteMe) {
365361
delete update[key];
366362
}
363+
return ops;
364+
}
365+
366+
// Processes relation-updating operations from a REST-format update.
367+
// Returns a promise that resolves when all updates have been performed
368+
DatabaseController.prototype.handleRelationUpdates = function(className, objectId, update, ops) {
369+
var pending = [];
370+
objectId = update.objectId || objectId;
371+
ops.forEach(({key, op}) => {
372+
if (!op) {
373+
return;
374+
}
375+
if (op.__op == 'AddRelation') {
376+
for (const object of op.objects) {
377+
pending.push(this.addRelation(key, className,
378+
objectId,
379+
object.objectId));
380+
}
381+
}
382+
383+
if (op.__op == 'RemoveRelation') {
384+
for (const object of op.objects) {
385+
pending.push(this.removeRelation(key, className,
386+
objectId,
387+
object.objectId));
388+
}
389+
}
390+
});
391+
367392
return Promise.all(pending);
368393
};
369394

@@ -511,12 +536,11 @@ DatabaseController.prototype.create = function(className, object, { acl } = {})
511536

512537
var isMaster = acl === undefined;
513538
var aclGroup = acl || [];
514-
539+
const relationUpdates = this.collectRelationUpdates(className, null, object);
515540
return this.validateClassName(className)
516541
.then(() => this.loadSchema())
517542
.then(schemaController => {
518543
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create'))
519-
.then(() => this.handleRelationUpdates(className, null, object))
520544
.then(() => schemaController.enforceClassExists(className))
521545
.then(() => schemaController.reloadData())
522546
.then(() => schemaController.getOneSchema(className, true))
@@ -525,7 +549,11 @@ DatabaseController.prototype.create = function(className, object, { acl } = {})
525549
flattenUpdateOperatorsForCreate(object);
526550
return this.adapter.createObject(className, SchemaController.convertSchemaToAdapterSchema(schema), object);
527551
})
528-
.then(result => sanitizeDatabaseResult(originalObject, result.ops[0]));
552+
.then(result => {
553+
return this.handleRelationUpdates(className, null, object, relationUpdates).then(() => {
554+
return sanitizeDatabaseResult(originalObject, result.ops[0])
555+
});
556+
});
529557
})
530558
};
531559

0 commit comments

Comments
 (0)