mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-27 18:52:14 +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 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');
|
||||
|
||||
|
@ -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({
|
||||
|
@ -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'))
|
||||
|
@ -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(),
|
||||
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 = [
|
||||
|
Loading…
Reference in New Issue
Block a user