2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Fix server-side validation

Closes #3122
-Fix validation so that all values are validated instead
 of just values that evaluate to true.
-Ensure validation methods consistently return promises
 and switch error handling from try/catch to promise.catch
 to get rid of unhandled rejection warnings.
-Add 0 and 1 to list of acceptable values in boolean validation.
This commit is contained in:
Jason Williams 2014-06-26 04:52:04 +00:00
parent f114f4f2f6
commit 13229fb6a4
5 changed files with 22 additions and 17 deletions

View file

@ -24,7 +24,7 @@ var DebugController = Ember.Controller.extend(Ember.Evented, {
}).then(function () { }).then(function () {
self.notifications.showSuccess('Import successful.'); self.notifications.showSuccess('Import successful.');
}).catch(function (response) { }).catch(function (response) {
self.notifications.showErrors(response); self.notifications.showAPIError(response);
}).finally(function () { }).finally(function () {
self.set('uploadButtonText', 'Import'); self.set('uploadButtonText', 'Import');
self.trigger('reset'); self.trigger('reset');

View file

@ -101,16 +101,14 @@ db = {
_.each(_.keys(importData.data), function (tableName) { _.each(_.keys(importData.data), function (tableName) {
_.each(importData.data[tableName], function (importValues) { _.each(importData.data[tableName], function (importValues) {
try { validation.validateSchema(tableName, importValues).catch(function (err) {
validation.validateSchema(tableName, importValues); error += error !== '' ? '<br>' : '';
} catch (err) {
error += error !== "" ? "<br>" : "";
error += err.message; error += err.message;
} });
}); });
}); });
if (error !== "") { if (error !== '') {
return when.reject(new Error(error)); return when.reject(new Error(error));
} }
// Import for the current version // Import for the current version
@ -119,7 +117,7 @@ db = {
}).then(function importSuccess() { }).then(function importSuccess() {
return api.settings.updateSettingsCache(); return api.settings.updateSettingsCache();
}).then(function () { }).then(function () {
return when.resolve({ message: 'Import successful', db: [] }); return when.resolve({ db: [] });
}).otherwise(function importFailure(error) { }).otherwise(function importFailure(error) {
return when.reject(new errors.InternalServerError(error.message || error)); return when.reject(new errors.InternalServerError(error.message || error));
}).finally(function () { }).finally(function () {

View file

@ -7,8 +7,8 @@ var db = {
markdown: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true}, markdown: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true},
html: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true}, html: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true},
image: {type: 'text', maxlength: 2000, nullable: true}, image: {type: 'text', maxlength: 2000, nullable: true},
featured: {type: 'bool', nullable: false, defaultTo: false, validations: {'isIn': [[false, true]]}}, featured: {type: 'bool', nullable: false, defaultTo: false, validations: {'isIn': [[0, 1, false, true]]}},
page: {type: 'bool', nullable: false, defaultTo: false, validations: {'isIn': [[false, true]]}}, page: {type: 'bool', nullable: false, defaultTo: false, validations: {'isIn': [[0, 1, false, true]]}},
status: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'draft'}, status: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'draft'},
language: {type: 'string', maxlength: 6, nullable: false, defaultTo: 'en_US'}, language: {type: 'string', maxlength: 6, nullable: false, defaultTo: 'en_US'},
meta_title: {type: 'string', maxlength: 150, nullable: true}, meta_title: {type: 'string', maxlength: 150, nullable: true},
@ -118,7 +118,7 @@ var db = {
}, },
apps: { apps: {
id: {type: 'increments', nullable: false, primary: true}, id: {type: 'increments', nullable: false, primary: true},
uuid: {type: 'string', maxlength: 36, nullable: false}, uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
name: {type: 'string', maxlength: 150, nullable: false, unique: true}, name: {type: 'string', maxlength: 150, nullable: false, unique: true},
slug: {type: 'string', maxlength: 150, nullable: false, unique: true}, slug: {type: 'string', maxlength: 150, nullable: false, unique: true},
version: {type: 'string', maxlength: 150, nullable: false}, version: {type: 'string', maxlength: 150, nullable: false},
@ -130,7 +130,7 @@ var db = {
}, },
app_settings: { app_settings: {
id: {type: 'increments', nullable: false, primary: true}, id: {type: 'increments', nullable: false, primary: true},
uuid: {type: 'string', maxlength: 36, nullable: false}, uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
key: {type: 'string', maxlength: 150, nullable: false, unique: true}, key: {type: 'string', maxlength: 150, nullable: false, unique: true},
value: {type: 'text', maxlength: 65535, nullable: true}, value: {type: 'text', maxlength: 65535, nullable: true},
app_id: {type: 'integer', nullable: false, unsigned: true, references: 'apps.id'}, app_id: {type: 'integer', nullable: false, unsigned: true, references: 'apps.id'},
@ -141,7 +141,7 @@ var db = {
}, },
app_fields: { app_fields: {
id: {type: 'increments', nullable: false, primary: true}, id: {type: 'increments', nullable: false, primary: true},
uuid: {type: 'string', maxlength: 36, nullable: false}, uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
key: {type: 'string', maxlength: 150, nullable: false}, key: {type: 'string', maxlength: 150, nullable: false},
value: {type: 'text', maxlength: 65535, nullable: true}, value: {type: 'text', maxlength: 65535, nullable: true},
type: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'html'}, type: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'html'},

View file

@ -27,6 +27,7 @@ validateSchema = function (tableName, model) {
_.each(columns, function (columnKey) { _.each(columns, function (columnKey) {
var message = ''; var message = '';
// check nullable // check nullable
if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable') if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable')
&& schema[tableName][columnKey].nullable !== true) { && schema[tableName][columnKey].nullable !== true) {
@ -35,8 +36,9 @@ validateSchema = function (tableName, model) {
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey)); validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
} }
} }
// TODO: check if mandatory values should be enforced // TODO: check if mandatory values should be enforced
if (model[columnKey]) { if (model[columnKey] !== null && model[columnKey] !== undefined) {
// check length // check length
if (schema[tableName][columnKey].hasOwnProperty('maxlength')) { if (schema[tableName][columnKey].hasOwnProperty('maxlength')) {
if (!validator.isLength(model[columnKey], 0, schema[tableName][columnKey].maxlength)) { if (!validator.isLength(model[columnKey], 0, schema[tableName][columnKey].maxlength)) {
@ -54,7 +56,7 @@ validateSchema = function (tableName, model) {
//check type //check type
if (schema[tableName][columnKey].hasOwnProperty('type')) { if (schema[tableName][columnKey].hasOwnProperty('type')) {
if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(model[columnKey])) { if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(model[columnKey])) {
message = 'Value in [' + tableName + '.' + columnKey + '] is no valid integer.'; message = 'Value in [' + tableName + '.' + columnKey + '] is not an integer.';
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey)); validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
} }
} }
@ -64,6 +66,8 @@ validateSchema = function (tableName, model) {
if (validationErrors.length !== 0) { if (validationErrors.length !== 0) {
return when.reject(validationErrors); return when.reject(validationErrors);
} }
return when.resolve();
}; };
// Validation for settings // Validation for settings
@ -81,6 +85,8 @@ validateSettings = function (defaultSettings, model) {
if (validationErrors.length !== 0) { if (validationErrors.length !== 0) {
return when.reject(validationErrors); return when.reject(validationErrors);
} }
return when.resolve();
}; };
// Validate default settings using the validator module. // Validate default settings using the validator module.
@ -102,6 +108,7 @@ validateSettings = function (defaultSettings, model) {
// available validators: https://github.com/chriso/validator.js#validators // available validators: https://github.com/chriso/validator.js#validators
validate = function (value, key, validations) { validate = function (value, key, validations) {
var validationErrors = []; var validationErrors = [];
_.each(validations, function (validationOptions, validationName) { _.each(validations, function (validationOptions, validationName) {
var goodResult = true; var goodResult = true;
@ -116,7 +123,7 @@ validate = function (value, key, validations) {
// equivalent of validator.isSomething(option1, option2) // equivalent of validator.isSomething(option1, option2)
if (validator[validationName].apply(validator, validationOptions) !== goodResult) { if (validator[validationName].apply(validator, validationOptions) !== goodResult) {
validationErrors.push(new errors.ValidationError('Settings validation (' + validationName + ') failed for ' + key, key)); validationErrors.push(new errors.ValidationError('Validation (' + validationName + ') failed for ' + key, key));
} }
validationOptions.shift(); validationOptions.shift();

View file

@ -49,7 +49,7 @@ Settings = ghostBookshelf.Model.extend({
validate: function () { validate: function () {
var self = this; var self = this;
return when(validation.validateSchema(self.tableName, self.toJSON())).then(function () { return validation.validateSchema(self.tableName, self.toJSON()).then(function () {
return validation.validateSettings(getDefaultSettings(), self); return validation.validateSettings(getDefaultSettings(), self);
}); });
}, },