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:
parent
f114f4f2f6
commit
13229fb6a4
5 changed files with 22 additions and 17 deletions
|
@ -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');
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
|
@ -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'},
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue