0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Sanitize incoming model relation data

refs #9548

- we always receive date strings from the client in ISO format
- we ensure that we transform these strings into JS dates for comparison
- when the client sends relations, we need to ensure that relations are checked as well
- will only work for the post model for now, because this is the only model which uses `bookshelf-relations`
- added unit tests
- removed some model tests, which do the same
This commit is contained in:
kirrg001 2018-04-05 16:11:47 +02:00 committed by Katharina Irrgang
parent 5c5ecfd61d
commit 853b518a51
4 changed files with 97 additions and 51 deletions

View file

@ -496,14 +496,17 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
* @IMPORTANT
* Before the new client data get's inserted again, the dates get's re-transformed into
* proper strings, see `format`.
*
* @IMPORTANT
* Sanitize relations.
*/
sanitizeData: function sanitizeData(data) {
var tableName = _.result(this.prototype, 'tableName'), date;
_.each(data, function (value, key) {
_.each(data, (value, property) => {
if (value !== null
&& schema.tables[tableName].hasOwnProperty(key)
&& schema.tables[tableName][key].type === 'dateTime'
&& schema.tables[tableName].hasOwnProperty(property)
&& schema.tables[tableName][property].type === 'dateTime'
&& typeof value === 'string'
) {
date = new Date(value);
@ -511,12 +514,36 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
// CASE: client sends `0000-00-00 00:00:00`
if (isNaN(date)) {
throw new common.errors.ValidationError({
message: common.i18n.t('errors.models.base.invalidDate', {key: key}),
message: common.i18n.t('errors.models.base.invalidDate', {key: property}),
code: 'DATE_INVALID'
});
}
data[key] = moment(value).toDate();
data[property] = moment(value).toDate();
}
if (this.prototype.relationships && this.prototype.relationships.indexOf(property) !== -1) {
_.each(data[property], (relation, indexInArr) => {
_.each(relation, (value, relationProperty) => {
if (value !== null
&& schema.tables[this.prototype.relationshipBelongsTo[property]].hasOwnProperty(relationProperty)
&& schema.tables[this.prototype.relationshipBelongsTo[property]][relationProperty].type === 'dateTime'
&& typeof value === 'string'
) {
date = new Date(value);
// CASE: client sends `0000-00-00 00:00:00`
if (isNaN(date)) {
throw new common.errors.ValidationError({
message: common.i18n.t('errors.models.base.invalidDate', {key: relationProperty}),
code: 'DATE_INVALID'
});
}
data[property][indexInArr][relationProperty] = moment(value).toDate();
}
});
});
}
});

View file

@ -39,6 +39,12 @@ Post = ghostBookshelf.Model.extend({
relationships: ['tags', 'authors'],
// NOTE: look up object, not super nice, but was easy to implement
relationshipBelongsTo: {
tags: 'tags',
authors: 'users'
},
/**
* The base model keeps only the columns, which are defined in the schema.
* We have to add the relations on top, otherwise bookshelf-relations

View file

@ -592,27 +592,6 @@ describe('Post Model', function () {
});
});
it('draft -> scheduled: invalid published_at update', function (done) {
PostModel.findOne({status: 'draft'}).then(function (results) {
var post;
should.exist(results);
post = results.toJSON();
post.status.should.equal('draft');
// @TODO: add unit test for valid and invalid formats
return PostModel.edit({
status: 'scheduled',
published_at: '0000-00-00 00:00:00'
}, _.extend({}, context, {id: post.id}));
}).catch(function (err) {
should.exist(err);
(err instanceof common.errors.ValidationError).should.eql(true);
err.code.should.eql('DATE_INVALID');
done();
});
});
it('draft -> scheduled: expect update of published_at', function (done) {
var newPublishedAt = moment().add(1, 'day').toDate();
@ -976,30 +955,6 @@ describe('Post Model', function () {
}).catch(done);
});
it('send invalid published_at date', function (done) {
var postId = testUtils.DataGenerator.Content.posts[0].id;
PostModel
.findOne({
id: postId
})
.then(function (results) {
var post;
should.exist(results);
post = results.toJSON();
post.id.should.equal(postId);
return PostModel.edit({published_at: '0000-00-00 00:00:00'}, _.extend({}, context, {id: postId}));
})
.then(function () {
done(new Error('This test should fail.'));
})
.catch(function (err) {
err.statusCode.should.eql(422);
done();
});
});
it('send empty date', function (done) {
var postId = testUtils.DataGenerator.Content.posts[0].id;

View file

@ -18,7 +18,65 @@ describe('Models: base', function () {
sandbox.restore();
});
describe('setEmptyValuesToNull', function () {
describe('fn: sanitizeData', function () {
it('date is invalid', function () {
const data = testUtils.DataGenerator.forKnex.createPost({updated_at: '0000-00-00 00:00:00'});
try {
models.Base.Model.sanitizeData
.bind({prototype: {tableName: 'posts'}})(data);
} catch (err) {
err.code.should.eql('DATE_INVALID');
}
});
it('expect date transformation', function () {
const data = testUtils.DataGenerator.forKnex.createPost({updated_at: '2018-04-01 07:53:07'});
data.updated_at.should.be.a.String();
models.Base.Model.sanitizeData
.bind({prototype: {tableName: 'posts'}})(data);
data.updated_at.should.be.a.Date();
});
it('date is JS date, ignore', function () {
const data = testUtils.DataGenerator.forKnex.createPost({updated_at: new Date()});
data.updated_at.should.be.a.Date();
models.Base.Model.sanitizeData
.bind({prototype: {tableName: 'posts'}})(data);
data.updated_at.should.be.a.Date();
});
it('expect date transformation for nested relations', function () {
const data = testUtils.DataGenerator.forKnex.createPost({
authors: [{
name: 'Thomas',
updated_at: '2018-04-01 07:53:07'
}]
});
data.authors[0].updated_at.should.be.a.String();
models.Base.Model.sanitizeData
.bind({
prototype: {
tableName: 'posts',
relationships: ['authors'],
relationshipBelongsTo: {authors: 'users'}
}
})(data);
data.authors[0].name.should.eql('Thomas');
data.authors[0].updated_at.should.be.a.Date();
});
});
describe('fn: setEmptyValuesToNull', function () {
it('resets given empty value to null', function () {
const base = models.Base.Model.forge({a: '', b: ''});