Sanitizes RestWrite.data before passing to inflated object

This commit is contained in:
Florent Vilmart
2016-03-11 23:03:47 -05:00
parent 49531e7efe
commit cadd6fe406
2 changed files with 53 additions and 23 deletions

View File

@@ -905,7 +905,7 @@ describe('Parse.User testing', () => {
} }
}; };
}; };
var getMockMyOauthProvider = function() { var getMockMyOauthProvider = function() {
return { return {
authData: { authData: {
@@ -1329,7 +1329,7 @@ describe('Parse.User testing', () => {
} }
}); });
}); });
it("link multiple providers", (done) => { it("link multiple providers", (done) => {
var provider = getMockFacebookProvider(); var provider = getMockFacebookProvider();
var mockProvider = getMockMyOauthProvider(); var mockProvider = getMockMyOauthProvider();
@@ -1351,7 +1351,7 @@ describe('Parse.User testing', () => {
ok(model._isLinked("facebook"), "User should be linked to facebook"); ok(model._isLinked("facebook"), "User should be linked to facebook");
ok(model._isLinked("myoauth"), "User should be linked to myoauth"); ok(model._isLinked("myoauth"), "User should be linked to myoauth");
done(); done();
}, },
error: function(error) { error: function(error) {
console.error(error); console.error(error);
fail('SHould not fail'); fail('SHould not fail');
@@ -1437,9 +1437,9 @@ describe('Parse.User testing', () => {
} }
}); });
}); });
it('should have authData in beforeSave and afterSave', (done) => { it('should have authData in beforeSave and afterSave', (done) => {
Parse.Cloud.beforeSave('_User', (request, response) => { Parse.Cloud.beforeSave('_User', (request, response) => {
let authData = request.object.get('authData'); let authData = request.object.get('authData');
expect(authData).not.toBeUndefined(); expect(authData).not.toBeUndefined();
@@ -1451,7 +1451,7 @@ describe('Parse.User testing', () => {
} }
response.success(); response.success();
}); });
Parse.Cloud.afterSave('_User', (request, response) => { Parse.Cloud.afterSave('_User', (request, response) => {
let authData = request.object.get('authData'); let authData = request.object.get('authData');
expect(authData).not.toBeUndefined(); expect(authData).not.toBeUndefined();
@@ -1463,7 +1463,7 @@ describe('Parse.User testing', () => {
} }
response.success(); response.success();
}); });
var provider = getMockFacebookProvider(); var provider = getMockFacebookProvider();
Parse.User._registerAuthenticationProvider(provider); Parse.User._registerAuthenticationProvider(provider);
Parse.User._logInWith("facebook", { Parse.User._logInWith("facebook", {
@@ -1970,9 +1970,9 @@ describe('Parse.User testing', () => {
} }
}); });
}); });
// Sometimes the authData still has null on that keys // Sometimes the authData still has null on that keys
// https://github.com/ParsePlatform/parse-server/issues/935 // https://github.com/ParsePlatform/parse-server/issues/935
it('should cleanup null authData keys', (done) => { it('should cleanup null authData keys', (done) => {
let database = new Config(Parse.applicationId).database; let database = new Config(Parse.applicationId).database;
database.create('_User', { database.create('_User', {
@@ -2003,8 +2003,26 @@ describe('Parse.User testing', () => {
done(); done();
}).catch((err) => { }).catch((err) => {
fail('this should not fail'); fail('this should not fail');
done(); done();
}) })
}); });
});
it('should aftersave with full object', (done) => {
var hit = 0;
Parse.Cloud.afterSave('_User', (req, res) => {
hit++;
expect(req.object.get('username')).toEqual('User');
res.success();
});
let user = new Parse.User()
user.setUsername('User');
user.setPassword('pass');
user.signUp().then(()=> {
user.set('hello', 'world');
return user.save();
}).then(() => {
Parse.Cloud._removeHook('Triggers', 'afterSave', '_User');
done();
});
})
});

View File

@@ -32,7 +32,7 @@ function RestWrite(config, auth, className, query, data, originalData) {
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId ' + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId ' +
'is an invalid field name.'); 'is an invalid field name.');
} }
// When the operation is complete, this.response may have several // When the operation is complete, this.response may have several
// fields. // fields.
// response: the actual data to be returned // response: the actual data to be returned
@@ -136,7 +136,7 @@ RestWrite.prototype.runBeforeTrigger = function() {
if (this.response) { if (this.response) {
return; return;
} }
// Avoid doing any setup for triggers if there is no 'beforeSave' trigger for this class. // Avoid doing any setup for triggers if there is no 'beforeSave' trigger for this class.
if (!triggers.triggerExists(this.className, triggers.Types.beforeSave, this.config.applicationId)) { if (!triggers.triggerExists(this.className, triggers.Types.beforeSave, this.config.applicationId)) {
return Promise.resolve(); return Promise.resolve();
@@ -154,7 +154,7 @@ RestWrite.prototype.runBeforeTrigger = function() {
// This is an update for existing object. // This is an update for existing object.
originalObject = triggers.inflate(extraData, this.originalData); originalObject = triggers.inflate(extraData, this.originalData);
} }
updatedObject.set(Parse._decode(undefined, this.data)); updatedObject.set(this.sanitizedData());
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
return triggers.maybeRunTrigger(triggers.Types.beforeSave, this.auth, updatedObject, originalObject, this.config.applicationId); return triggers.maybeRunTrigger(triggers.Types.beforeSave, this.auth, updatedObject, originalObject, this.config.applicationId);
@@ -254,14 +254,14 @@ RestWrite.prototype.findUsersWithAuthData = function(authData) {
}, []).filter((q) => { }, []).filter((q) => {
return typeof q !== undefined; return typeof q !== undefined;
}); });
let findPromise = Promise.resolve([]); let findPromise = Promise.resolve([]);
if (query.length > 0) { if (query.length > 0) {
findPromise = this.config.database.find( findPromise = this.config.database.find(
this.className, this.className,
{'$or': query}, {}) {'$or': query}, {})
} }
return findPromise; return findPromise;
} }
@@ -276,9 +276,9 @@ RestWrite.prototype.handleAuthData = function(authData) {
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
'this auth is already used'); 'this auth is already used');
} }
this.storage['authProvider'] = Object.keys(authData).join(','); this.storage['authProvider'] = Object.keys(authData).join(',');
if (results.length == 0) { if (results.length == 0) {
this.data.username = cryptoUtils.newToken(); this.data.username = cryptoUtils.newToken();
} else if (!this.query) { } else if (!this.query) {
@@ -404,7 +404,7 @@ RestWrite.prototype.transformUser = function() {
// Handles any followup logic // Handles any followup logic
RestWrite.prototype.handleFollowup = function() { RestWrite.prototype.handleFollowup = function() {
if (this.storage && this.storage['clearSessions']) { if (this.storage && this.storage['clearSessions']) {
var sessionQuery = { var sessionQuery = {
user: { user: {
@@ -417,7 +417,7 @@ RestWrite.prototype.handleFollowup = function() {
this.config.database.destroy('_Session', sessionQuery) this.config.database.destroy('_Session', sessionQuery)
.then(this.handleFollowup.bind(this)); .then(this.handleFollowup.bind(this));
} }
if (this.storage && this.storage['sendVerificationEmail']) { if (this.storage && this.storage['sendVerificationEmail']) {
delete this.storage['sendVerificationEmail']; delete this.storage['sendVerificationEmail'];
// Fire and forget! // Fire and forget!
@@ -695,7 +695,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
throw new Parse.Error(Parse.Error.SESSION_MISSING, throw new Parse.Error(Parse.Error.SESSION_MISSING,
'cannot modify user ' + this.query.objectId); 'cannot modify user ' + this.query.objectId);
} }
if (this.className === '_Product' && this.data.download) { if (this.className === '_Product' && this.data.download) {
this.data.downloadName = this.data.download.name; this.data.downloadName = this.data.download.name;
} }
@@ -722,7 +722,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
ACL[this.data.objectId] = { read: true, write: true }; ACL[this.data.objectId] = { read: true, write: true };
ACL['*'] = { read: true, write: false }; ACL['*'] = { read: true, write: false };
this.data.ACL = ACL; this.data.ACL = ACL;
} }
// Run a create // Run a create
return this.config.database.create(this.className, this.data, this.runOptions) return this.config.database.create(this.className, this.data, this.runOptions)
.then(() => { .then(() => {
@@ -770,7 +770,7 @@ RestWrite.prototype.runAfterTrigger = function() {
// Build the inflated object, different from beforeSave, originalData is not empty // Build the inflated object, different from beforeSave, originalData is not empty
// since developers can change data in the beforeSave. // since developers can change data in the beforeSave.
let updatedObject = triggers.inflate(extraData, this.originalData); let updatedObject = triggers.inflate(extraData, this.originalData);
updatedObject.set(Parse._decode(undefined, this.data)); updatedObject.set(this.sanitizedData());
updatedObject._handleSaveResponse(this.response.response, this.response.status || 200); updatedObject._handleSaveResponse(this.response.response, this.response.status || 200);
triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config.applicationId); triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config.applicationId);
@@ -789,5 +789,17 @@ RestWrite.prototype.objectId = function() {
return this.data.objectId || this.query.objectId; return this.data.objectId || this.query.objectId;
}; };
// Returns a copy of the data and delete bad keys (_auth_data, _hashed_password...)
RestWrite.prototype.sanitizedData = function() {
let data = 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);
}
export default RestWrite; export default RestWrite;
module.exports = RestWrite; module.exports = RestWrite;