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:
Kevin Ansfield 2019-07-24 10:52:55 +01:00 committed by Naz Gargol
parent 5bdaf29c89
commit 2b6830b747
6 changed files with 173 additions and 1 deletions

View File

@ -1,7 +1,25 @@
const _ = require('lodash');
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 = {
browse(apiConfig, frame) {
debug('browse');
setDefaultFilter(frame);
},
read(apiConfig, frame) {
debug('read');
setDefaultFilter(frame);
},
add(apiConfig, frame) {
debug('add');

View File

@ -89,6 +89,16 @@ const Integration = ghostBookshelf.Model.extend({
webhooks: function webhooks() {
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({

View File

@ -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) {
request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))

View File

@ -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]');
});
});
});

View 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]);
});
});
});
});

View File

@ -379,6 +379,12 @@ DataGenerator.Content = {
id: ObjectId.generate(),
name: '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 = [
createBasic(DataGenerator.Content.integrations[0])
createBasic(DataGenerator.Content.integrations[0]),
createBasic(DataGenerator.Content.integrations[1])
];
const api_keys = [