Validates permission before calling beforeSave trigger (#5546)
* Test to reproduce the problem * Validating update before calling beforeSave trigger * Fixing lint * Commenting code * Improving the code
This commit is contained in:
committed by
Arthur Cinader
parent
2cc21bf1f2
commit
90c81c1750
@@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
const Config = require('../lib/Config');
|
||||
const Parse = require('parse/node');
|
||||
const request = require('../lib/request');
|
||||
const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter')
|
||||
@@ -239,6 +240,215 @@ describe('Cloud Code', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('beforeSave should be called only if user fulfills permissions', async () => {
|
||||
const triggeruser = new Parse.User();
|
||||
triggeruser.setUsername('triggeruser');
|
||||
triggeruser.setPassword('triggeruser');
|
||||
await triggeruser.signUp();
|
||||
|
||||
const triggeruser2 = new Parse.User();
|
||||
triggeruser2.setUsername('triggeruser2');
|
||||
triggeruser2.setPassword('triggeruser2');
|
||||
await triggeruser2.signUp();
|
||||
|
||||
const triggeruser3 = new Parse.User();
|
||||
triggeruser3.setUsername('triggeruser3');
|
||||
triggeruser3.setPassword('triggeruser3');
|
||||
await triggeruser3.signUp();
|
||||
|
||||
const triggeruser4 = new Parse.User();
|
||||
triggeruser4.setUsername('triggeruser4');
|
||||
triggeruser4.setPassword('triggeruser4');
|
||||
await triggeruser4.signUp();
|
||||
|
||||
const triggeruser5 = new Parse.User();
|
||||
triggeruser5.setUsername('triggeruser5');
|
||||
triggeruser5.setPassword('triggeruser5');
|
||||
await triggeruser5.signUp();
|
||||
|
||||
const triggerroleacl = new Parse.ACL();
|
||||
triggerroleacl.setPublicReadAccess(true);
|
||||
|
||||
const triggerrole = new Parse.Role();
|
||||
triggerrole.setName('triggerrole');
|
||||
triggerrole.setACL(triggerroleacl);
|
||||
triggerrole.getUsers().add(triggeruser);
|
||||
triggerrole.getUsers().add(triggeruser3);
|
||||
await triggerrole.save();
|
||||
|
||||
const config = Config.get('test');
|
||||
const schema = await config.database.loadSchema();
|
||||
await schema.addClassIfNotExists(
|
||||
'triggerclass',
|
||||
{
|
||||
someField: { type: 'String' },
|
||||
pointerToUser: { type: 'Pointer', targetClass: '_User' },
|
||||
},
|
||||
{
|
||||
find: {
|
||||
'role:triggerrole': true,
|
||||
[triggeruser.id]: true,
|
||||
[triggeruser2.id]: true,
|
||||
},
|
||||
create: {
|
||||
'role:triggerrole': true,
|
||||
[triggeruser.id]: true,
|
||||
[triggeruser2.id]: true,
|
||||
},
|
||||
get: {
|
||||
'role:triggerrole': true,
|
||||
[triggeruser.id]: true,
|
||||
[triggeruser2.id]: true,
|
||||
},
|
||||
update: {
|
||||
'role:triggerrole': true,
|
||||
[triggeruser.id]: true,
|
||||
[triggeruser2.id]: true,
|
||||
},
|
||||
addField: {
|
||||
'role:triggerrole': true,
|
||||
[triggeruser.id]: true,
|
||||
[triggeruser2.id]: true,
|
||||
},
|
||||
delete: {
|
||||
'role:triggerrole': true,
|
||||
[triggeruser.id]: true,
|
||||
[triggeruser2.id]: true,
|
||||
},
|
||||
readUserFields: ['pointerToUser'],
|
||||
writeUserFields: ['pointerToUser'],
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
let called = 0;
|
||||
Parse.Cloud.beforeSave('triggerclass', () => {
|
||||
called++;
|
||||
});
|
||||
|
||||
const triggerobject = new Parse.Object('triggerclass');
|
||||
triggerobject.set('someField', 'someValue');
|
||||
triggerobject.set('someField2', 'someValue');
|
||||
const triggerobjectacl = new Parse.ACL();
|
||||
triggerobjectacl.setPublicReadAccess(false);
|
||||
triggerobjectacl.setPublicWriteAccess(false);
|
||||
triggerobjectacl.setRoleReadAccess(triggerrole, true);
|
||||
triggerobjectacl.setRoleWriteAccess(triggerrole, true);
|
||||
triggerobjectacl.setReadAccess(triggeruser.id, true);
|
||||
triggerobjectacl.setWriteAccess(triggeruser.id, true);
|
||||
triggerobjectacl.setReadAccess(triggeruser2.id, true);
|
||||
triggerobjectacl.setWriteAccess(triggeruser2.id, true);
|
||||
triggerobject.setACL(triggerobjectacl);
|
||||
|
||||
await triggerobject.save(undefined, {
|
||||
sessionToken: triggeruser.getSessionToken(),
|
||||
});
|
||||
expect(called).toBe(1);
|
||||
await triggerobject.save(undefined, {
|
||||
sessionToken: triggeruser.getSessionToken(),
|
||||
});
|
||||
expect(called).toBe(2);
|
||||
await triggerobject.save(undefined, {
|
||||
sessionToken: triggeruser2.getSessionToken(),
|
||||
});
|
||||
expect(called).toBe(3);
|
||||
await triggerobject.save(undefined, {
|
||||
sessionToken: triggeruser3.getSessionToken(),
|
||||
});
|
||||
expect(called).toBe(4);
|
||||
|
||||
const triggerobject2 = new Parse.Object('triggerclass');
|
||||
triggerobject2.set('someField', 'someValue');
|
||||
triggerobject2.set('someField22', 'someValue');
|
||||
const triggerobjectacl2 = new Parse.ACL();
|
||||
triggerobjectacl2.setPublicReadAccess(false);
|
||||
triggerobjectacl2.setPublicWriteAccess(false);
|
||||
triggerobjectacl2.setReadAccess(triggeruser.id, true);
|
||||
triggerobjectacl2.setWriteAccess(triggeruser.id, true);
|
||||
triggerobjectacl2.setReadAccess(triggeruser2.id, true);
|
||||
triggerobjectacl2.setWriteAccess(triggeruser2.id, true);
|
||||
triggerobjectacl2.setReadAccess(triggeruser5.id, true);
|
||||
triggerobjectacl2.setWriteAccess(triggeruser5.id, true);
|
||||
triggerobject2.setACL(triggerobjectacl2);
|
||||
|
||||
await triggerobject2.save(undefined, {
|
||||
sessionToken: triggeruser2.getSessionToken(),
|
||||
});
|
||||
expect(called).toBe(5);
|
||||
await triggerobject2.save(undefined, {
|
||||
sessionToken: triggeruser2.getSessionToken(),
|
||||
});
|
||||
expect(called).toBe(6);
|
||||
await triggerobject2.save(undefined, {
|
||||
sessionToken: triggeruser.getSessionToken(),
|
||||
});
|
||||
expect(called).toBe(7);
|
||||
|
||||
let catched = false;
|
||||
try {
|
||||
await triggerobject2.save(undefined, {
|
||||
sessionToken: triggeruser3.getSessionToken(),
|
||||
});
|
||||
} catch (e) {
|
||||
catched = true;
|
||||
expect(e.code).toBe(101);
|
||||
}
|
||||
expect(catched).toBe(true);
|
||||
expect(called).toBe(7);
|
||||
|
||||
catched = false;
|
||||
try {
|
||||
await triggerobject2.save(undefined, {
|
||||
sessionToken: triggeruser4.getSessionToken(),
|
||||
});
|
||||
} catch (e) {
|
||||
catched = true;
|
||||
expect(e.code).toBe(101);
|
||||
}
|
||||
expect(catched).toBe(true);
|
||||
expect(called).toBe(7);
|
||||
|
||||
catched = false;
|
||||
try {
|
||||
await triggerobject2.save(undefined, {
|
||||
sessionToken: triggeruser5.getSessionToken(),
|
||||
});
|
||||
} catch (e) {
|
||||
catched = true;
|
||||
expect(e.code).toBe(101);
|
||||
}
|
||||
expect(catched).toBe(true);
|
||||
expect(called).toBe(7);
|
||||
|
||||
const triggerobject3 = new Parse.Object('triggerclass');
|
||||
triggerobject3.set('someField', 'someValue');
|
||||
triggerobject3.set('someField33', 'someValue');
|
||||
|
||||
catched = false;
|
||||
try {
|
||||
await triggerobject3.save(undefined, {
|
||||
sessionToken: triggeruser4.getSessionToken(),
|
||||
});
|
||||
} catch (e) {
|
||||
catched = true;
|
||||
expect(e.code).toBe(119);
|
||||
}
|
||||
expect(catched).toBe(true);
|
||||
expect(called).toBe(7);
|
||||
|
||||
catched = false;
|
||||
try {
|
||||
await triggerobject3.save(undefined, {
|
||||
sessionToken: triggeruser5.getSessionToken(),
|
||||
});
|
||||
} catch (e) {
|
||||
catched = true;
|
||||
expect(e.code).toBe(119);
|
||||
}
|
||||
expect(catched).toBe(true);
|
||||
expect(called).toBe(7);
|
||||
});
|
||||
|
||||
it('test afterSave ran and created an object', function(done) {
|
||||
Parse.Cloud.afterSave('AfterSaveTest', function(req) {
|
||||
const obj = new Parse.Object('AfterSaveProof');
|
||||
|
||||
@@ -476,7 +476,8 @@ class DatabaseController {
|
||||
query: any,
|
||||
update: any,
|
||||
{ acl, many, upsert }: FullQueryOptions = {},
|
||||
skipSanitization: boolean = false
|
||||
skipSanitization: boolean = false,
|
||||
validateOnly: boolean = false
|
||||
): Promise<any> {
|
||||
const originalQuery = query;
|
||||
const originalUpdate = update;
|
||||
@@ -557,6 +558,19 @@ class DatabaseController {
|
||||
}
|
||||
update = transformObjectACL(update);
|
||||
transformAuthData(className, update, schema);
|
||||
if (validateOnly) {
|
||||
return this.adapter
|
||||
.find(className, schema, query, {})
|
||||
.then(result => {
|
||||
if (!result || !result.length) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found.'
|
||||
);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}
|
||||
if (many) {
|
||||
return this.adapter.updateObjectsByQuery(
|
||||
className,
|
||||
@@ -588,6 +602,9 @@ class DatabaseController {
|
||||
'Object not found.'
|
||||
);
|
||||
}
|
||||
if (validateOnly) {
|
||||
return result;
|
||||
}
|
||||
return this.handleRelationUpdates(
|
||||
className,
|
||||
originalQuery.objectId,
|
||||
@@ -802,7 +819,8 @@ class DatabaseController {
|
||||
create(
|
||||
className: string,
|
||||
object: any,
|
||||
{ acl }: QueryOptions = {}
|
||||
{ acl }: QueryOptions = {},
|
||||
validateOnly: boolean = false
|
||||
): Promise<any> {
|
||||
// Make a copy of the object, so we don't mutate the incoming data.
|
||||
const originalObject = object;
|
||||
@@ -831,6 +849,9 @@ class DatabaseController {
|
||||
.then(schema => {
|
||||
transformAuthData(className, object, schema);
|
||||
flattenUpdateOperatorsForCreate(object);
|
||||
if (validateOnly) {
|
||||
return {};
|
||||
}
|
||||
return this.adapter.createObject(
|
||||
className,
|
||||
SchemaController.convertSchemaToAdapterSchema(schema),
|
||||
@@ -838,6 +859,9 @@ class DatabaseController {
|
||||
);
|
||||
})
|
||||
.then(result => {
|
||||
if (validateOnly) {
|
||||
return originalObject;
|
||||
}
|
||||
return this.handleRelationUpdates(
|
||||
className,
|
||||
object.objectId,
|
||||
|
||||
@@ -220,6 +220,38 @@ RestWrite.prototype.runBeforeSaveTrigger = function() {
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
// Before calling the trigger, validate the permissions for the save operation
|
||||
let databasePromise = null;
|
||||
if (this.query) {
|
||||
// Validate for updating
|
||||
databasePromise = this.config.database.update(
|
||||
this.className,
|
||||
this.query,
|
||||
this.data,
|
||||
this.runOptions,
|
||||
false,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
// Validate for creating
|
||||
databasePromise = this.config.database.create(
|
||||
this.className,
|
||||
this.data,
|
||||
this.runOptions,
|
||||
true
|
||||
);
|
||||
}
|
||||
// In the case that there is no permission for the operation, it throws an error
|
||||
return databasePromise.then(result => {
|
||||
if (!result || result.length <= 0) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found.'
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return triggers.maybeRunTrigger(
|
||||
triggers.Types.beforeSave,
|
||||
|
||||
Reference in New Issue
Block a user