diff --git a/core/server/api/db.js b/core/server/api/db.js
index 5db32833ca..bde548c53e 100644
--- a/core/server/api/db.js
+++ b/core/server/api/db.js
@@ -6,7 +6,7 @@ var dataExport = require('../data/export'),
when = require('when'),
nodefn = require('when/node/function'),
_ = require('lodash'),
- schema = require('../data/schema').tables,
+ validation = require('../data/validation'),
config = require('../config'),
errors = require('../../server/errorHandling'),
api = {},
@@ -47,8 +47,7 @@ db = {
return nodefn.call(fs.readFile, options.importfile.path);
}).then(function (fileContents) {
var importData,
- error = '',
- tableKeys = _.keys(schema);
+ error = '';
// Parse the json data
try {
@@ -62,28 +61,13 @@ db = {
return when.reject(new Error("Import data does not specify version"));
}
- _.each(tableKeys, function (constkey) {
- _.each(importData.data[constkey], function (elem) {
- var prop;
- for (prop in elem) {
- if (elem.hasOwnProperty(prop)) {
- if (schema[constkey].hasOwnProperty(prop)) {
- if (!_.isNull(elem[prop])) {
- if (elem[prop].length > schema[constkey][prop].maxlength) {
- error += error !== "" ? "
" : "";
- error += "Property '" + prop + "' exceeds maximum length of " + schema[constkey][prop].maxlength + " (element:" + constkey + " / id:" + elem.id + ")";
- }
- } else {
- if (!schema[constkey][prop].nullable) {
- error += error !== "" ? "
" : "";
- error += "Property '" + prop + "' is not nullable (element:" + constkey + " / id:" + elem.id + ")";
- }
- }
- } else {
- error += error !== "" ? "
" : "";
- error += "Property '" + prop + "' is not allowed (element:" + constkey + " / id:" + elem.id + ")";
- }
- }
+ _.each(_.keys(importData.data), function (tableName) {
+ _.each(importData.data[tableName], function (importValues) {
+ try {
+ validation.validateSchema(tableName, importValues);
+ } catch (err) {
+ error += error !== "" ? "
" : "";
+ error += err.message;
}
});
});
diff --git a/core/server/data/schema.js b/core/server/data/schema.js
index 4d31dbda87..1734d0d1f9 100644
--- a/core/server/data/schema.js
+++ b/core/server/data/schema.js
@@ -1,14 +1,14 @@
var db = {
posts: {
id: {type: 'increments', nullable: false, primary: true},
- uuid: {type: 'string', maxlength: 36, nullable: false},
+ uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
title: {type: 'string', maxlength: 150, nullable: false},
slug: {type: 'string', maxlength: 150, nullable: false, unique: true},
markdown: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true},
html: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true},
image: {type: 'text', maxlength: 2000, nullable: true},
featured: {type: 'bool', nullable: false, defaultTo: false},
- page: {type: 'bool', nullable: false, defaultTo: false},
+ page: {type: 'bool', nullable: false, defaultTo: false, validations: {'isIn': ['true', 'false']}},
status: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'draft'},
language: {type: 'string', maxlength: 6, nullable: false, defaultTo: 'en_US'},
meta_title: {type: 'string', maxlength: 150, nullable: true},
@@ -23,15 +23,15 @@ var db = {
},
users: {
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},
slug: {type: 'string', maxlength: 150, nullable: false},
password: {type: 'string', maxlength: 60, nullable: false},
- email: {type: 'string', maxlength: 254, nullable: false, unique: true},
+ email: {type: 'string', maxlength: 254, nullable: false, unique: true, validations: {'isEmail': true}},
image: {type: 'text', maxlength: 2000, nullable: true},
cover: {type: 'text', maxlength: 2000, nullable: true},
bio: {type: 'string', maxlength: 200, nullable: true},
- website: {type: 'text', maxlength: 2000, nullable: true},
+ website: {type: 'text', maxlength: 2000, nullable: true, validations: {'isUrl': true}},
location: {type: 'text', maxlength: 65535, nullable: true},
accessibility: {type: 'text', maxlength: 65535, nullable: true},
status: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'active'},
@@ -46,7 +46,7 @@ var db = {
},
roles: {
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},
description: {type: 'string', maxlength: 200, nullable: true},
created_at: {type: 'dateTime', nullable: false},
@@ -61,7 +61,7 @@ var db = {
},
permissions: {
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},
object_type: {type: 'string', maxlength: 150, nullable: false},
action_type: {type: 'string', maxlength: 150, nullable: false},
@@ -88,10 +88,10 @@ var db = {
},
settings: {
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},
value: {type: 'text', maxlength: 65535, nullable: true},
- type: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'core'},
+ type: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'core', validations: {'isIn': ['core', 'blog', 'theme', 'app', 'plugin']}},
created_at: {type: 'dateTime', nullable: false},
created_by: {type: 'integer', nullable: false},
updated_at: {type: 'dateTime', nullable: true},
@@ -99,7 +99,7 @@ var db = {
},
tags: {
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},
slug: {type: 'string', maxlength: 150, nullable: false, unique: true},
description: {type: 'string', maxlength: 200, nullable: true},
diff --git a/core/server/data/validation/index.js b/core/server/data/validation/index.js
new file mode 100644
index 0000000000..25dd00f7ff
--- /dev/null
+++ b/core/server/data/validation/index.js
@@ -0,0 +1,89 @@
+var schema = require('../schema').tables,
+ _ = require('lodash'),
+ validator = require('validator'),
+ when = require('when'),
+
+ validateSchema,
+ validateSettings,
+ validate;
+
+// Validation validation against schema attributes
+// values are checked against the validation objects
+// form schema.js
+validateSchema = function (tableName, model) {
+ var columns = _.keys(schema[tableName]);
+
+ _.each(columns, function (columnKey) {
+ // check nullable
+ if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable')
+ && schema[tableName][columnKey].nullable !== true) {
+ validator.check(model[columnKey], 'Value in [' + tableName + '.' + columnKey +
+ '] cannot be blank.').notNull();
+ validator.check(model[columnKey], 'Value in [' + tableName + '.' + columnKey +
+ '] cannot be blank.').notEmpty();
+ }
+ // TODO: check if mandatory values should be enforced
+ if (model[columnKey]) {
+ // check length
+ if (schema[tableName][columnKey].hasOwnProperty('maxlength')) {
+ validator.check(model[columnKey], 'Value in [' + tableName + '.' + columnKey +
+ '] exceeds maximum length of %2 characters.').len(0, schema[tableName][columnKey].maxlength);
+ }
+
+ //check validations objects
+ if (schema[tableName][columnKey].hasOwnProperty('validations')) {
+ validate(model[columnKey], columnKey, schema[tableName][columnKey].validations);
+ }
+
+ //check type
+ if (schema[tableName][columnKey].hasOwnProperty('type')) {
+ if (schema[tableName][columnKey].type === 'integer') {
+ validator.check(model[columnKey], 'Value in [' + tableName + '.' + columnKey +
+ '] is no valid integer.' + model[columnKey]).isInt();
+ }
+ }
+ }
+ });
+};
+
+// Validation for settings
+// settings are checked against the validation objects
+// form default-settings.json
+validateSettings = function (defaultSettings, model) {
+ var values = model.toJSON(),
+ matchingDefault = defaultSettings[values.key];
+
+ if (matchingDefault && matchingDefault.validations) {
+ validate(values.value, values.key, matchingDefault.validations);
+ }
+};
+
+// Validate using the validation module.
+// Each validation's key is a name and its value is an array of options
+// Use true (boolean) if options aren't applicable
+//
+// eg:
+// validations: { isUrl: true, len: [20, 40] }
+//
+// will validate that a values's length is a URL between 20 and 40 chars,
+// available validators: https://github.com/chriso/node-validator#list-of-validation-methods
+validate = function (value, key, validations) {
+ _.each(validations, function (validationOptions, validationName) {
+ var validation = validator.check(value, 'Validation [' + validationName + '] of field [' + key + '] failed.');
+
+ if (validationOptions === true) {
+ validationOptions = null;
+ }
+ if (typeof validationOptions !== 'array') {
+ validationOptions = [validationOptions];
+ }
+
+ // equivalent of validation.isSomething(option1, option2)
+ validation[validationName].apply(validation, validationOptions);
+ }, this);
+};
+
+module.exports = {
+ validateSchema: validateSchema,
+ validateSettings: validateSettings
+};
\ No newline at end of file
diff --git a/core/server/models/base.js b/core/server/models/base.js
index 47d47e9b3f..4eab1d6a2b 100644
--- a/core/server/models/base.js
+++ b/core/server/models/base.js
@@ -4,10 +4,10 @@ var Bookshelf = require('bookshelf'),
_ = require('lodash'),
uuid = require('node-uuid'),
config = require('../config'),
- Validator = require('validator').Validator,
unidecode = require('unidecode'),
sanitize = require('validator').sanitize,
schema = require('../data/schema'),
+ validation = require('../data/validation'),
ghostBookshelf;
@@ -15,7 +15,6 @@ var Bookshelf = require('bookshelf'),
ghostBookshelf = Bookshelf.ghost = Bookshelf.initialize(config().database);
ghostBookshelf.client = config().database.client;
-ghostBookshelf.validator = new Validator();
// The Base Model which other Ghost objects will inherit from,
// including some convenience functions as static properties on the model.
@@ -45,7 +44,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
},
validate: function () {
- return true;
+ validation.validateSchema(this.tableName, this.toJSON());
},
creating: function () {
diff --git a/core/server/models/permission.js b/core/server/models/permission.js
index 77064be098..f84f5be610 100644
--- a/core/server/models/permission.js
+++ b/core/server/models/permission.js
@@ -1,6 +1,7 @@
var ghostBookshelf = require('./base'),
User = require('./user').User,
Role = require('./role').Role,
+ validation = require('../data/validation'),
Permission,
Permissions;
@@ -9,11 +10,6 @@ Permission = ghostBookshelf.Model.extend({
tableName: 'permissions',
- validate: function () {
- // TODO: validate object_type, action_type and object_id
- ghostBookshelf.validator.check(this.get('name'), "Permission name cannot be blank").notEmpty();
- },
-
roles: function () {
return this.belongsToMany(Role);
},
diff --git a/core/server/models/post.js b/core/server/models/post.js
index 34b2ad48b3..2fb6c86c2e 100644
--- a/core/server/models/post.js
+++ b/core/server/models/post.js
@@ -10,6 +10,7 @@ var _ = require('lodash'),
Tag = require('./tag').Tag,
Tags = require('./tag').Tags,
ghostBookshelf = require('./base'),
+ validation = require('../data/validation'),
Post,
Posts;
@@ -37,12 +38,7 @@ Post = ghostBookshelf.Model.extend({
},
validate: function () {
- ghostBookshelf.validator.check(this.get('title'), "Post title cannot be blank").notEmpty();
- ghostBookshelf.validator.check(this.get('title'), 'Post title maximum length is 150 characters.').len(0, 150);
- ghostBookshelf.validator.check(this.get('slug'), "Post title cannot be blank").notEmpty();
- ghostBookshelf.validator.check(this.get('slug'), 'Post title maximum length is 150 characters.').len(0, 150);
-
- return true;
+ validation.validateSchema(this.tableName, this.toJSON());
},
saving: function (newPage, attr, options) {
diff --git a/core/server/models/role.js b/core/server/models/role.js
index 66729ef11a..f9d134e441 100644
--- a/core/server/models/role.js
+++ b/core/server/models/role.js
@@ -9,11 +9,6 @@ Role = ghostBookshelf.Model.extend({
tableName: 'roles',
- validate: function () {
- ghostBookshelf.validator.check(this.get('name'), "Role name cannot be blank").notEmpty();
- ghostBookshelf.validator.check(this.get('description'), "Role description cannot be blank").notEmpty();
- },
-
users: function () {
return this.belongsToMany(User);
},
diff --git a/core/server/models/settings.js b/core/server/models/settings.js
index 49a38bdfcf..74a5557925 100644
--- a/core/server/models/settings.js
+++ b/core/server/models/settings.js
@@ -1,10 +1,10 @@
var Settings,
ghostBookshelf = require('./base'),
- validator = ghostBookshelf.validator,
uuid = require('node-uuid'),
_ = require('lodash'),
errors = require('../errorHandling'),
when = require('when'),
+ validation = require('../data/validation'),
defaultSettings;
@@ -41,37 +41,9 @@ Settings = ghostBookshelf.Model.extend({
};
},
-
- // Validate default settings using the validator module.
- // Each validation's key is a name and its value is an array of options
- // Use true (boolean) if options aren't applicable
- //
- // eg:
- // validations: { isUrl: true, len: [20, 40] }
- //
- // will validate that a setting's length is a URL between 20 and 40 chars,
- // available validators: https://github.com/chriso/node-validator#list-of-validation-methods
validate: function () {
- validator.check(this.get('key'), "Setting key cannot be blank").notEmpty();
- validator.check(this.get('type'), "Setting type cannot be blank").notEmpty();
-
- var matchingDefault = defaultSettings[this.get('key')];
-
- if (matchingDefault && matchingDefault.validations) {
- _.each(matchingDefault.validations, function (validationOptions, validationName) {
- var validation = validator.check(this.get('value'));
-
- if (validationOptions === true) {
- validationOptions = null;
- }
- if (typeof validationOptions !== 'array') {
- validationOptions = [validationOptions];
- }
-
- // equivalent of validation.isSomething(option1, option2)
- validation[validationName].apply(validation, validationOptions);
- }, this);
- }
+ validation.validateSchema(this.tableName, this.toJSON());
+ validation.validateSettings(defaultSettings, this);
},
diff --git a/core/server/models/tag.js b/core/server/models/tag.js
index a251dc0e3b..e1daff07fa 100644
--- a/core/server/models/tag.js
+++ b/core/server/models/tag.js
@@ -8,11 +8,6 @@ Tag = ghostBookshelf.Model.extend({
tableName: 'tags',
- validate: function () {
-
- return true;
- },
-
saving: function () {
var self = this;
ghostBookshelf.Model.prototype.saving.apply(this, arguments);
diff --git a/core/server/models/user.js b/core/server/models/user.js
index 165738f57e..0405ae6f50 100644
--- a/core/server/models/user.js
+++ b/core/server/models/user.js
@@ -10,6 +10,7 @@ var _ = require('lodash'),
Permission = require('./permission').Permission,
http = require('http'),
crypto = require('crypto'),
+ validator = require('validator'),
tokenSecurity = {},
User,
@@ -17,11 +18,10 @@ var _ = require('lodash'),
function validatePasswordLength(password) {
try {
- ghostBookshelf.validator.check(password, "Your password must be at least 8 characters long.").len(8);
+ validator.check(password, "Your password must be at least 8 characters long.").len(8);
} catch (error) {
return when.reject(error);
}
-
return when.resolve();
}
@@ -37,16 +37,6 @@ User = ghostBookshelf.Model.extend({
tableName: 'users',
- validate: function () {
- ghostBookshelf.validator.check(this.get('email'), "Please enter a valid email address. That one looks a bit dodgy.").isEmail();
- ghostBookshelf.validator.check(this.get('bio'), "We're not writing a novel here! I'm afraid your bio has to stay under 200 characters.").len(0, 200);
- if (this.get('website') && this.get('website').length > 0) {
- ghostBookshelf.validator.check(this.get('website'), "Looks like your website is not actually a website. Try again?").isUrl();
- }
- ghostBookshelf.validator.check(this.get('location'), 'This seems a little too long! Please try and keep your location under 150 characters.').len(0, 150);
- return true;
- },
-
saving: function () {
var self = this;
// disabling sanitization until we can implement a better version
diff --git a/core/test/unit/import_spec.js b/core/test/unit/import_spec.js
index f5b4c90f82..804e95e284 100644
--- a/core/test/unit/import_spec.js
+++ b/core/test/unit/import_spec.js
@@ -246,7 +246,7 @@ describe("Import", function () {
}).then(function () {
(1).should.eql(0, 'Data import should not resolve promise.');
}, function (error) {
- error.should.eql('Error importing data: Post title maximum length is 150 characters.');
+ error.should.eql('Error importing data: Value in [posts.title] exceeds maximum length of 150 characters.');
when.all([
knex("users").select(),
@@ -292,7 +292,7 @@ describe("Import", function () {
}).then(function () {
(1).should.eql(0, 'Data import should not resolve promise.');
}, function (error) {
- error.should.eql('Error importing data: Setting key cannot be blank');
+ error.should.eql('Error importing data: Value in [settings.key] cannot be blank.');
when.all([
knex("users").select(),
@@ -433,7 +433,7 @@ describe("Import", function () {
}).then(function () {
(1).should.eql(0, 'Data import should not resolve promise.');
}, function (error) {
- error.should.eql('Error importing data: Post title maximum length is 150 characters.');
+ error.should.eql('Error importing data: Value in [posts.title] exceeds maximum length of 150 characters.');
when.all([
knex("users").select(),
@@ -479,7 +479,7 @@ describe("Import", function () {
}).then(function () {
(1).should.eql(0, 'Data import should not resolve promise.');
}, function (error) {
- error.should.eql('Error importing data: Setting key cannot be blank');
+ error.should.eql('Error importing data: Value in [settings.key] cannot be blank.');
when.all([
knex("users").select(),