mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-30 21:40:39 +03:00
Ensured Admin API cannot fetch internal integrations (#10501)
no issue - Forced a filter on read and browse requests to the integrations endpoint to limit fetches to only "custom" and "builtin" integration types - Expanded test coverage for "internal" integrations
This commit is contained in:
parent
5bdaf29c89
commit
2b6830b747
@ -1,7 +1,25 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:integrations');
|
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:integrations');
|
||||||
|
|
||||||
|
function setDefaultFilter(frame) {
|
||||||
|
if (frame.options.filter) {
|
||||||
|
frame.options.filter = `(${frame.options.filter})+type:[custom,builtin]`;
|
||||||
|
} else {
|
||||||
|
frame.options.filter = 'type:[custom,builtin]';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
browse(apiConfig, frame) {
|
||||||
|
debug('browse');
|
||||||
|
|
||||||
|
setDefaultFilter(frame);
|
||||||
|
},
|
||||||
|
read(apiConfig, frame) {
|
||||||
|
debug('read');
|
||||||
|
|
||||||
|
setDefaultFilter(frame);
|
||||||
|
},
|
||||||
add(apiConfig, frame) {
|
add(apiConfig, frame) {
|
||||||
debug('add');
|
debug('add');
|
||||||
|
|
||||||
|
@ -89,6 +89,16 @@ const Integration = ghostBookshelf.Model.extend({
|
|||||||
webhooks: function webhooks() {
|
webhooks: function webhooks() {
|
||||||
return this.hasMany('Webhook', 'integration_id');
|
return this.hasMany('Webhook', 'integration_id');
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
permittedOptions(methodName) {
|
||||||
|
let options = ghostBookshelf.Model.permittedOptions.call(this, methodName);
|
||||||
|
|
||||||
|
if (methodName === 'findOne') {
|
||||||
|
options = options.concat(['filter']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Integrations = ghostBookshelf.Collection.extend({
|
const Integrations = ghostBookshelf.Collection.extend({
|
||||||
|
@ -46,6 +46,14 @@ describe('Integrations API', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Can not read internal integration', function () {
|
||||||
|
return request.get(localUtils.API.getApiQuery(`integrations/${testUtils.DataGenerator.Content.integrations[1].id}/`))
|
||||||
|
.set('Origin', config.get('url'))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
|
||||||
it('Can successfully create a single integration with auto generated content and admin api key', function (done) {
|
it('Can successfully create a single integration with auto generated content and admin api key', function (done) {
|
||||||
request.post(localUtils.API.getApiQuery('integrations/'))
|
request.post(localUtils.API.getApiQuery('integrations/'))
|
||||||
.set('Origin', config.get('url'))
|
.set('Origin', config.get('url'))
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
const should = require('should');
|
||||||
|
const serializers = require('../../../../../../../server/api/v2/utils/serializers');
|
||||||
|
|
||||||
|
describe('Unit: v2/utils/serializers/input/pages', function () {
|
||||||
|
describe('browse', function () {
|
||||||
|
it('default', function () {
|
||||||
|
const apiConfig = {};
|
||||||
|
const frame = {
|
||||||
|
options: {
|
||||||
|
context: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
serializers.input.integrations.browse(apiConfig, frame);
|
||||||
|
frame.options.filter.should.eql('type:[custom,builtin]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('combines filters', function () {
|
||||||
|
const apiConfig = {};
|
||||||
|
const frame = {
|
||||||
|
options: {
|
||||||
|
filter: 'type:internal',
|
||||||
|
context: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
serializers.input.integrations.browse(apiConfig, frame);
|
||||||
|
frame.options.filter.should.eql('(type:internal)+type:[custom,builtin]');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('read', function () {
|
||||||
|
it('default', function () {
|
||||||
|
const apiConfig = {};
|
||||||
|
const frame = {
|
||||||
|
options: {
|
||||||
|
context: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
serializers.input.integrations.read(apiConfig, frame);
|
||||||
|
frame.options.filter.should.eql('type:[custom,builtin]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('combines filters', function () {
|
||||||
|
const apiConfig = {};
|
||||||
|
const frame = {
|
||||||
|
options: {
|
||||||
|
filter: 'type:internal',
|
||||||
|
context: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
serializers.input.integrations.read(apiConfig, frame);
|
||||||
|
frame.options.filter.should.eql('(type:internal)+type:[custom,builtin]');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
71
core/test/unit/models/integration_spec.js
Normal file
71
core/test/unit/models/integration_spec.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const should = require('should');
|
||||||
|
const url = require('url');
|
||||||
|
const sinon = require('sinon');
|
||||||
|
const models = require('../../../server/models');
|
||||||
|
const testUtils = require('../../utils');
|
||||||
|
const {knex} = require('../../../server/data/db');
|
||||||
|
|
||||||
|
describe('Unit: models/integration', function () {
|
||||||
|
before(function () {
|
||||||
|
models.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
sinon.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('permittedOptions', function () {
|
||||||
|
let basePermittedOptionsReturnVal;
|
||||||
|
let basePermittedOptionsStub;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
basePermittedOptionsReturnVal = ['super', 'doopa'];
|
||||||
|
basePermittedOptionsStub = sinon.stub(models.Base.Model, 'permittedOptions')
|
||||||
|
.returns(basePermittedOptionsReturnVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the base permittedOptions result', function () {
|
||||||
|
const returnedOptions = models.Integration.permittedOptions();
|
||||||
|
should.deepEqual(returnedOptions, basePermittedOptionsReturnVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the base permittedOptions result plus "filter" when methodName is findOne', function () {
|
||||||
|
const returnedOptions = models.Integration.permittedOptions('findOne');
|
||||||
|
should.deepEqual(returnedOptions, basePermittedOptionsReturnVal.concat('filter'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findOne', function () {
|
||||||
|
const mockDb = require('mock-knex');
|
||||||
|
let tracker;
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
mockDb.mock(knex);
|
||||||
|
tracker = mockDb.getTracker();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function () {
|
||||||
|
mockDb.unmock(knex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates correct query (allows use of options.filter)', function () {
|
||||||
|
const queries = [];
|
||||||
|
tracker.install();
|
||||||
|
|
||||||
|
tracker.on('query', (query) => {
|
||||||
|
queries.push(query);
|
||||||
|
query.response([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return models.Integration.findOne({
|
||||||
|
id: '123'
|
||||||
|
}, {
|
||||||
|
filter: 'type:[custom,builtin]'
|
||||||
|
}).then(() => {
|
||||||
|
queries.length.should.eql(1);
|
||||||
|
queries[0].sql.should.eql('select `integrations`.* from `integrations` where `integrations`.`type` in (?, ?) and `integrations`.`id` = ? limit ?');
|
||||||
|
queries[0].bindings.should.eql(['custom', 'builtin', '123', 1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -379,6 +379,12 @@ DataGenerator.Content = {
|
|||||||
id: ObjectId.generate(),
|
id: ObjectId.generate(),
|
||||||
name: 'Test Integration',
|
name: 'Test Integration',
|
||||||
slug: 'test-integration'
|
slug: 'test-integration'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
name: 'Test Internal Integration',
|
||||||
|
slug: 'test-internal-integration',
|
||||||
|
type: 'internal'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -885,7 +891,8 @@ DataGenerator.forKnex = (function () {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const integrations = [
|
const integrations = [
|
||||||
createBasic(DataGenerator.Content.integrations[0])
|
createBasic(DataGenerator.Content.integrations[0]),
|
||||||
|
createBasic(DataGenerator.Content.integrations[1])
|
||||||
];
|
];
|
||||||
|
|
||||||
const api_keys = [
|
const api_keys = [
|
||||||
|
Loading…
Reference in New Issue
Block a user