0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-08 02:52:39 -05:00

🎨 User is not allowed to add/modify certain fields (#9053)

no issue

- it's not allowed to change/add these attributes via the API
  - created_at = is only once set on adding the resource
  - created_by = is only once set on adding the resource
  - updated_by = is set on the server side when updating the model (based on who is logged in)
  - updated_at = is set on the server side when updating the model

* Revert the usage of the access rules plugin
This commit is contained in:
Katharina Irrgang 2017-09-28 14:59:42 +02:00 committed by Hannah Wolfe
parent d3d04a8e72
commit 42af268d1b
3 changed files with 184 additions and 4 deletions

View file

@ -125,16 +125,29 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
return validation.validateSchema(this.tableName, this.toJSON());
},
/**
* Adding resources implies setting these properties on the server side
* - set `created_by` based on the context
* - set `updated_by` based on the context
* - the bookshelf `timestamps` plugin sets `created_at` and `updated_at`
* - if plugin is disabled (e.g. import) we have a fallback condition
*
* Exceptions: internal context or importing
*/
onCreating: function onCreating(newObj, attr, options) {
// id = 0 is still a valid value for external usage
if (_.isUndefined(newObj.id) || _.isNull(newObj.id)) {
newObj.setId();
}
if (schema.tables[this.tableName].hasOwnProperty('created_by') && !this.get('created_by')) {
this.set('created_by', this.contextUser(options));
if (schema.tables[this.tableName].hasOwnProperty('created_by')) {
if (!options.importing || (options.importing && !this.get('created_by'))) {
this.set('created_by', this.contextUser(options));
}
}
this.set('updated_by', this.contextUser(options));
if (!newObj.get('created_at')) {
newObj.set('created_at', new Date());
}
@ -144,13 +157,37 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
}
},
onSaving: function onSaving(newObj, attr, options) {
onSaving: function onSaving(newObj) {
// Remove any properties which don't belong on the model
this.attributes = this.pick(this.permittedAttributes());
// Store the previous attributes so we can tell what was updated later
this._updatedAttributes = newObj.previousAttributes();
},
/**
* Changing resources implies setting these properties on the server side
* - set `updated_by` based on the context
* - ensure `created_at` never changes
* - ensure `created_by` never changes
* - the bookshelf `timestamps` plugin sets `updated_at` automatically
*
* Exceptions:
* - importing data
* - internal context
* - if no context
*/
onUpdating: function onUpdating(newObj, attr, options) {
this.set('updated_by', this.contextUser(options));
if (options && options.context && !options.internal && !options.importing) {
if (newObj.hasDateChanged('created_at', {beforeWrite: true})) {
newObj.set('created_at', this.previous('created_at'));
}
if (newObj.hasChanged('created_by')) {
newObj.set('created_by', this.previous('created_by'));
}
}
},
/**

View file

@ -1,8 +1,9 @@
var should = require('should'),
supertest = require('supertest'),
testUtils = require('../../../utils'),
moment = require('moment'),
_ = require('lodash'),
ObjectId = require('bson-objectid'),
testUtils = require('../../../utils'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
markdownToMobiledoc = testUtils.DataGenerator.markdownToMobiledoc,
@ -703,6 +704,47 @@ describe('Post API', function () {
});
});
});
it('check which fields can be added', function (done) {
var newPost = {
status: 'draft',
title: 'title',
mobiledoc: markdownToMobiledoc('my post'),
created_at: moment().subtract(2, 'days').toDate(),
updated_at: moment().subtract(2, 'days').toDate(),
created_by: ObjectId.generate(),
updated_by: ObjectId.generate()
};
request
.post(testUtils.API.getApiQuery('posts/'))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.send({posts: [newPost]})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.end(function (err, res) {
if (err) {
return done(err);
}
var response = res.body;
res.headers.location.should.equal('/ghost/api/v0.1/posts/' + response.posts[0].id + '/?status=draft');
should.exist(response.posts);
response.posts.length.should.be.above(0);
response.posts[0].title.should.eql(newPost.title);
response.posts[0].status.should.eql(newPost.status);
response.posts[0].created_at.should.not.eql(newPost.created_at.toISOString());
response.posts[0].updated_at.should.not.eql(newPost.updated_at.toISOString());
response.posts[0].updated_by.should.not.eql(newPost.updated_by);
response.posts[0].created_by.should.not.eql(newPost.created_by);
testUtils.API.checkResponse(response.posts[0], 'post');
done();
});
});
});
// ## edit
@ -1100,6 +1142,55 @@ describe('Post API', function () {
});
});
});
it('check which fields can be modified', function (done) {
var existingPostData, modifiedPostData;
request
.get(testUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.end(function (err, res) {
if (err) {
return done(err);
}
var jsonResponse = res.body;
should.exist(jsonResponse.posts[0]);
existingPostData = _.cloneDeep(jsonResponse.posts[0]);
modifiedPostData = _.cloneDeep(jsonResponse);
modifiedPostData.posts[0].created_by = ObjectId.generate();
modifiedPostData.posts[0].updated_by = ObjectId.generate();
modifiedPostData.posts[0].created_at = moment().add(2, 'days').format();
modifiedPostData.posts[0].updated_at = moment().add(2, 'days').format();
request
.put(testUtils.API.getApiQuery('posts/' + modifiedPostData.posts[0].id + '/'))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.send(modifiedPostData)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
var jsonResponse = res.body;
should.exist(jsonResponse.posts[0]);
// We expect that the changed properties aren't changed, they are still the same than before.
jsonResponse.posts[0].created_by.should.eql(existingPostData.created_by);
jsonResponse.posts[0].updated_by.should.eql(existingPostData.updated_by);
jsonResponse.posts[0].created_at.should.eql(existingPostData.created_at);
// `updated_at` is automatically set, but it's not the date we send to override.
jsonResponse.posts[0].updated_at.should.not.eql(modifiedPostData.updated_at);
done();
});
});
});
});
// ## delete

View file

@ -1,5 +1,7 @@
var should = require('should'),
_ = require('lodash'),
supertest = require('supertest'),
moment = require('moment'),
testUtils = require('../../../utils'),
ObjectId = require('bson-objectid'),
config = require('../../../../../core/server/config'),
@ -478,6 +480,56 @@ describe('User API', function () {
});
});
});
it('check which fields can be modified', function (done) {
var existingUserData, modifiedUserData;
request.get(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.end(function (err, res) {
if (err) {
return done(err);
}
var jsonResponse = res.body;
should.exist(jsonResponse.users[0]);
existingUserData = _.cloneDeep(jsonResponse.users[0]);
modifiedUserData = _.cloneDeep(jsonResponse);
existingUserData.created_by.should.eql('1');
existingUserData.updated_by.should.eql('1');
modifiedUserData.users[0].created_at = moment().add(2, 'days').format();
modifiedUserData.users[0].updated_at = moment().add(2, 'days').format();
modifiedUserData.users[0].created_by = ObjectId.generate();
modifiedUserData.users[0].updated_by = ObjectId.generate();
delete jsonResponse.users[0].id;
request.put(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.send(jsonResponse)
.expect(200)
.end(function (err, res) {
/*jshint unused:false*/
if (err) {
return done(err);
}
var jsonResponse = res.body;
should.exist(jsonResponse.users[0]);
jsonResponse.users[0].created_by.should.eql(existingUserData.created_by);
jsonResponse.users[0].updated_by.should.eql(existingUserData.updated_by);
jsonResponse.users[0].updated_at.should.not.eql(modifiedUserData.updated_at);
jsonResponse.users[0].created_at.should.eql(existingUserData.created_at);
done();
});
});
});
});
describe('Destroy', function () {