Skip to content
Closed
67 changes: 61 additions & 6 deletions spec/CloudCode.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,12 @@ describe('Cloud Code', () => {
it('test afterSave ran on created object and returned a promise', function(done) {
Parse.Cloud.afterSave('AfterSaveTest2', function(req) {
const obj = req.object;
if(!obj.existed())
if (!obj.existed())
{
const promise = new Parse.Promise();
setTimeout(function(){
setTimeout(function() {
obj.set('proof', obj.id);
obj.save().then(function(){
obj.save().then(function() {
promise.resolve();
});
}, 1000);
Expand All @@ -196,7 +196,7 @@ describe('Cloud Code', () => {
});

const obj = new Parse.Object('AfterSaveTest2');
obj.save().then(function(){
obj.save().then(function() {
const query = new Parse.Query('AfterSaveTest2');
query.equalTo('proof', obj.id);
query.find().then(function(results) {
Expand Down Expand Up @@ -1778,19 +1778,74 @@ describe('afterFind hooks', () => {
})
.then(() => done());
});
});

describe('Trigger Testing', () => {
it('should validate triggers correctly', () => {
expect(() => {
Parse.Cloud.beforeFind('_Session', () => {});
}).toThrow('beforeFind and afterFind triggers are not allowed for _Session class.');
expect(() => {
Parse.Cloud.afterFind('_Session', () => {});
}).toThrow('beforeFind and afterFind triggers are not allowed for _Session class.');
expect(() => {
Parse.Cloud.beforeSave('_Session', () => {});
}).toThrow('Triggers are not supported for _Session class.');
}).not.toThrow('Triggers are not supported for _Session class.');
expect(() => {
Parse.Cloud.afterSave('_Session', () => {});
}).toThrow('Triggers are not supported for _Session class.');
}).not.toThrow('Triggers are not supported for _Session class.');
expect(() => {
Parse.Cloud.beforeSave('_PushStatus', () => {});
}).toThrow('Only afterSave is allowed on _PushStatus');
expect(() => {
Parse.Cloud.afterSave('_PushStatus', () => {});
}).not.toThrow();
});

it('beforeSave _Session should not modify class', (done) => {
var hasCalled = false;
Parse.Cloud.beforeSave('_Session', (req, res) => {
req.object.set('foo', 'bing');
expect(() => {
req.object.set('createdWith', 'test')
}).toThrow();
expect(() => {
req.object.set('expiresAt', new Date())
}).toThrow();
expect(() => {
req.object.set('installationId', 'test')
}).toThrow();
expect(() => {
req.object.set('restricted', 'test')
}).toThrow();
expect(() => {
req.object.set('sessionToken', 'test')
}).toThrow();
expect(() => {
req.object.set('user', null)
}).toThrow();

hasCalled = true;
res.success();
});

var user = new Parse.User();
user.set("username", "zxcv");
user.set("email", "[email protected]");
user.set("password", "asdf");
user.signUp(null, {
success: function() {
Parse.Session.current().then((result) => {
expect(hasCalled).toBe(true);
expect(result).toBeDefined();
expect(result.get('foo')).toBeUndefined();
done();
});
},
error: function() {
fail('Failed to save user');
done();
}
});
});
});
15 changes: 10 additions & 5 deletions src/RestWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ RestWrite.prototype.runBeforeTrigger = function() {
return Promise.resolve().then(() => {
return triggers.maybeRunTrigger(triggers.Types.beforeSave, this.auth, updatedObject, originalObject, this.config);
}).then((response) => {
if (response && response.object) {
if (this.className !== '_Session' && response && response.object) {
this.storage.fieldsChangedByTrigger = _.reduce(response.object, (result, value, key) => {
if (!_.isEqual(this.data[key], value)) {
result.push(key);
Expand Down Expand Up @@ -1161,19 +1161,24 @@ RestWrite.prototype.objectId = function() {
};

// Returns a copy of the data and delete bad keys (_auth_data, _hashed_password...)
RestWrite.prototype.sanitizedData = function() {
const data = Object.keys(this.data).reduce((data, key) => {
RestWrite.prototype.sanitizeData = function() {
Object.keys(this.data).reduce((data, key) => {
// Regexp comes from Parse.Object.prototype.validate
if (!(/^[A-Za-z][0-9A-Za-z_]*$/).test(key)) {
delete data[key];
}
return data;
}, deepcopy(this.data));
return Parse._decode(undefined, data);

return this.data;
}

// Returns an updated copy of the object
RestWrite.prototype.buildUpdatedObject = function (extraData) {
if (extraData.className == '_Session') {
return triggers.inflate(extraData, this.sanitizeData());
}

const updatedObject = triggers.inflate(extraData, this.originalData);
Object.keys(this.data).reduce(function (data, key) {
if (key.indexOf(".") > 0) {
Expand All @@ -1191,7 +1196,7 @@ RestWrite.prototype.buildUpdatedObject = function (extraData) {
return data;
}, deepcopy(this.data));

updatedObject.set(this.sanitizedData());
updatedObject.set(Parse._decode(undefined, this.sanitizeData()));
return updatedObject;
};

Expand Down
2 changes: 1 addition & 1 deletion src/Routers/PublicAPIRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class PublicAPIRouter extends PromiseRouter {
const appId = req.params.appId;
const config = Config.get(appId);

if(!config){
if(!config) {
this.invalidRequest();
}

Expand Down
9 changes: 4 additions & 5 deletions src/triggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ const baseStore = function() {
};

function validateClassNameForTriggers(className, type) {
const restrictedClassNames = [ '_Session' ];
if (restrictedClassNames.indexOf(className) != -1) {
throw `Triggers are not supported for ${className} class.`;
if ((type == Types.beforeFind || type == Types.afterFind) && className === '_Session') {
throw 'beforeFind and afterFind triggers are not allowed for _Session class.';
}
if (type == Types.beforeSave && className === '_PushStatus') {
else if (type == Types.beforeSave && className === '_PushStatus') {
// _PushStatus uses undocumented nested key increment ops
// allowing beforeSave would mess up the objects big time
// TODO: Allow proper documented way of using nested increment ops
Expand Down Expand Up @@ -203,7 +202,7 @@ export function getResponseObject(request, resolve, reject) {
return {
success: function(response) {
if (request.triggerName === Types.afterFind) {
if(!response){
if(!response) {
response = request.objects;
}
response = response.map(object => {
Expand Down