0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Removed x_by fields from API v2 response

refs #10286

- v2 no longer exposes x_by fields (published_by, updated_by, created_by)
- we will add a brand new concept called activity stream/actions soon
This commit is contained in:
kirrg001 2018-12-17 17:45:07 +01:00 committed by Katharina Irrgang
parent 254a460462
commit 789a3c0715
12 changed files with 210 additions and 83 deletions

View file

@ -76,6 +76,12 @@ module.exports.output = (response = {}, apiConfig, apiSerializers, options) => {
// ##### API VERSION RESOURCE SERIALIZATION
if (apiSerializers.all && apiSerializers.all.before) {
tasks.push(function allSerializeBefore() {
return apiSerializers.all.before(response, apiConfig, options);
});
}
if (apiSerializers[apiConfig.docName]) {
if (apiSerializers[apiConfig.docName].all) {
tasks.push(function serializeOptionsShared() {
@ -90,6 +96,12 @@ module.exports.output = (response = {}, apiConfig, apiSerializers, options) => {
}
}
if (apiSerializers.all && apiSerializers.all.after) {
tasks.push(function allSerializeAfter() {
return apiSerializers.all.after(apiConfig, options);
});
}
debug(tasks);
return sequence(tasks);
};

View file

@ -6,7 +6,7 @@ const urlService = require('../../services/url');
const settingsCache = require('../../services/settings/cache');
const models = require('../../models');
const api = require('./index');
const ALLOWED_INCLUDES = ['created_by', 'updated_by'];
const ALLOWED_INCLUDES = [];
const UNSAFE_ATTRS = ['role_id'];
module.exports = {

View file

@ -1,6 +1,6 @@
const common = require('../../lib/common');
const models = require('../../models');
const ALLOWED_INCLUDES = ['created_by', 'updated_by', 'published_by', 'author', 'tags', 'authors', 'authors.roles'];
const ALLOWED_INCLUDES = ['author', 'tags', 'authors', 'authors.roles'];
module.exports = {
docName: 'pages',

View file

@ -1,7 +1,7 @@
const models = require('../../models');
const common = require('../../lib/common');
const urlService = require('../../services/url');
const allowedIncludes = ['created_by', 'updated_by', 'published_by', 'author', 'tags', 'authors', 'authors.roles'];
const allowedIncludes = ['author', 'tags', 'authors', 'authors.roles'];
const unsafeAttrs = ['author_id', 'status', 'authors'];
module.exports = {

View file

@ -0,0 +1,25 @@
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:all');
const _ = require('lodash');
const removeXBY = (object) => {
_.each(object, (value, key) => {
// CASE: go deeper
if (_.isObject(value) || _.isArray(value)) {
removeXBY(value);
} else if (['updated_by', 'created_by', 'published_by'].includes(key)) {
delete object[key];
}
});
return object;
};
module.exports = {
after(apiConfig, frame) {
debug('all after');
if (frame.response) {
frame.response = removeXBY(frame.response);
}
}
};

View file

@ -1,4 +1,8 @@
module.exports = {
get all() {
return require('./all');
},
get db() {
return require('./db');
},

View file

@ -6,6 +6,7 @@ const moment = require('moment-timezone');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
const config = require('../../../../../../core/server/config');
const models = require('../../../../../../core/server/models');
const ghost = testUtils.startGhost;
let request;
@ -248,7 +249,6 @@ describe('Posts API V2', function () {
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
jsonResponse.posts[0].author.should.be.a.String();
testUtils.API.isISO8601(jsonResponse.posts[0].created_at).should.be.true();
jsonResponse.posts[0].created_by.should.be.a.String();
// Tags aren't included by default
should.not.exist(jsonResponse.posts[0].tags);
done();
@ -300,7 +300,6 @@ describe('Posts API V2', function () {
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
jsonResponse.posts[0].author.should.be.a.String();
jsonResponse.posts[0].created_by.should.be.a.String();
// Tags aren't included by default
should.not.exist(jsonResponse.posts[0].tags);
done();
@ -309,7 +308,7 @@ describe('Posts API V2', function () {
it('with includes', function (done) {
request
.get(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/?include=authors,tags,created_by'))
.get(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/?include=authors,tags'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
@ -384,13 +383,19 @@ describe('Posts API V2', function () {
localUtils.API.checkResponse(res.body.posts[0], 'post');
should.not.exist(res.headers['x-cache-invalidate']);
res.body.posts[0].title.should.eql(post.title);
res.body.posts[0].status.should.eql(post.status);
res.body.posts[0].published_at.should.eql('2016-05-30T07:00:00.000Z');
res.body.posts[0].created_at.should.not.eql(post.created_at.toISOString());
res.body.posts[0].updated_at.should.not.eql(post.updated_at.toISOString());
res.body.posts[0].updated_by.should.not.eql(post.updated_by);
res.body.posts[0].created_by.should.not.eql(post.created_by);
return models.Post.findOne({
id: res.body.posts[0].id,
status: 'draft'
}, testUtils.context.internal);
})
.then((model) => {
model.get('title').should.eql(post.title);
model.get('status').should.eql(post.status);
model.get('published_at').toISOString().should.eql('2016-05-30T07:00:00.000Z');
model.get('created_at').toISOString().should.not.eql(post.created_at.toISOString());
model.get('updated_at').toISOString().should.not.eql(post.updated_at.toISOString());
model.get('updated_by').should.not.eql(post.updated_by);
model.get('created_by').should.not.eql(post.created_by);
});
});
@ -468,13 +473,18 @@ describe('Posts API V2', function () {
res.headers['x-cache-invalidate'].should.eql('/*');
localUtils.API.checkResponse(res.body.posts[0], 'post');
return models.Post.findOne({
id: res.body.posts[0].id
}, testUtils.context.internal);
})
.then((model) => {
// We expect that the changed properties aren't changed, they are still the same than before.
res.body.posts[0].created_by.should.not.eql(post.created_by);
res.body.posts[0].updated_by.should.not.eql(post.updated_by);
res.body.posts[0].created_at.should.not.eql(post.created_at);
model.get('created_at').toISOString().should.not.eql(post.created_at);
model.get('updated_by').should.not.eql(post.updated_by);
model.get('created_by').should.not.eql(post.created_by);
// `updated_at` is automatically set, but it's not the date we send to override.
res.body.posts[0].updated_at.should.not.eql(post.updated_at);
model.get('updated_at').toISOString().should.not.eql(post.updated_at);
});
});

View file

@ -76,7 +76,7 @@ describe('Settings API V2', function () {
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'key', 'value', 'type', 'created_at', 'created_by', 'updated_at', 'updated_by']);
testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'key', 'value', 'type', 'created_at', 'updated_at']);
jsonResponse.settings[0].key.should.eql('title');
testUtils.API.isISO8601(jsonResponse.settings[0].created_at).should.be.true();
done();

View file

@ -328,55 +328,6 @@ describe('User API V2', function () {
});
});
});
it('check which fields can be modified', function (done) {
var existingUserData, modifiedUserData;
request.get(localUtils.API.getApiQuery('users/me/'))
.set('Origin', config.get('url'))
.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 modifiedUserData.users[0].id;
request.put(localUtils.API.getApiQuery('users/me/'))
.set('Origin', config.get('url'))
.send(modifiedUserData)
.expect(200)
.end(function (err, res) {
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 () {

View file

@ -15,26 +15,47 @@ const expectedProperties = {
pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'],
slugs: ['slugs'],
slug: ['slug'],
invites: ['invites', 'meta'],
themes: ['themes'],
post: _(schema.posts)
.keys()
// by default we only return html
.without('mobiledoc', 'plaintext')
// swaps author_id to author, and always returns computed properties: url, comment_id, primary_tag, primary_author
.without('author_id').concat('author', 'url', 'primary_tag', 'primary_author')
.value(),
user: _(schema.users).keys().without('password').without('ghost_auth_access_token').value(),
// Tag API swaps parent_id to parent
tag: _(schema.tags).keys().without('parent_id').concat('parent').value(),
setting: _.keys(schema.settings),
subscriber: _.keys(schema.subscribers),
accesstoken: _.keys(schema.accesstokens),
role: _.keys(schema.roles),
permission: _.keys(schema.permissions),
,
user: _(schema.users)
.keys()
.without('password')
.without('ghost_auth_access_token')
,
tag: _(schema.tags)
.keys()
// Tag API swaps parent_id to parent
.without('parent_id').concat('parent')
,
setting: _(schema.settings)
.keys()
,
subscriber: _(schema.subscribers)
.keys()
,
accesstoken: _(schema.accesstokens)
.keys()
,
role: _(schema.roles)
.keys()
,
permission: _(schema.permissions)
.keys()
,
notification: ['type', 'message', 'status', 'id', 'dismissible', 'location', 'custom'],
theme: ['name', 'package', 'active'],
themes: ['themes'],
invites: ['invites', 'meta'],
invite: _(schema.invites).keys().without('token').value(),
invite: _(schema.invites)
.keys()
.without('token')
,
webhook: _(schema.webhooks)
.keys()
.without(
@ -45,9 +66,25 @@ const expectedProperties = {
'secret',
'integration_id'
)
.value()
};
_.each(expectedProperties, (value, key) => {
if (!value.__wrapped__) {
return;
}
/**
* @deprecated: x_by
*/
expectedProperties[key] = value
.without(
'created_by',
'updated_by',
'published_by'
)
.value();
});
module.exports = {
API: {
getApiQuery(route) {

View file

@ -10,13 +10,14 @@ const expectedProperties = {
tags: ['tags', 'meta'],
authors: ['authors', 'meta'],
pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'],
post: _(schema.posts)
.keys()
// by default we only return html
.without('mobiledoc', 'plaintext')
// swaps author_id to author, and always returns computed properties: url, comment_id, primary_tag, primary_author
.without('author_id').concat('author', 'url', 'primary_tag', 'primary_author')
.value(),
,
author: _(schema.users)
.keys()
.without(
@ -31,12 +32,30 @@ const expectedProperties = {
'last_seen',
'status'
)
.value()
,
// Tag API swaps parent_id to parent
tag: _(schema.tags).keys().without('parent_id').concat('parent').value()
tag: _(schema.tags)
.keys()
.without('parent_id').concat('parent')
};
_.each(expectedProperties, (value, key) => {
if (!value.__wrapped__) {
return;
}
/**
* @deprecated: x_by
*/
expectedProperties[key] = value
.without(
'created_by',
'updated_by',
'published_by'
)
.value();
});
module.exports = {
API: {
getApiQuery(route) {

View file

@ -0,0 +1,69 @@
const should = require('should');
const serializers = require('../../../../../../../server/api/v2/utils/serializers');
describe('Unit: v2/utils/serializers/output/all', () => {
describe('after', function () {
it('x_by', function () {
const apiConfig = {};
let response = {
posts: [
{
created_by: 'xxx',
title: 'xxx'
}
]
};
serializers.output.all.after(apiConfig, {
response: response
});
should.not.exist(response.posts[0].created_by);
should.exist(response.posts[0].title);
response = {
post:
{
created_by: 'xxx',
updated_by: 'yyy',
title: 'xxx'
}
};
serializers.output.all.after(apiConfig, {
response: response
});
should.not.exist(response.post.created_by);
should.not.exist(response.post.updated_by);
should.exist(response.post.title);
response = {
pages: [
{
created_by: 'xxx',
authors: [
{
updated_by: 'yyy',
slug: 'ghost'
}
]
},
{
published_by: 'yyy'
}
]
};
serializers.output.all.after(apiConfig, {
response: response
});
should.not.exist(response.pages[0].created_by);
should.not.exist(response.pages[1].published_by);
should.exist(response.pages[0].authors);
should.exist(response.pages[0].authors[0].slug);
should.not.exist(response.pages[0].authors[0].updated_by);
});
});
});